STM32(4):基于构件库的点亮LED

概述

第一、二章节中,STM32是纯裸开发,通过自定义地址来进行写寄存器;STM32其实提供了底层固件库,定义好了通用功能,所以如果是常规功能只需要调用固件库的API即可实现功能。所以我在番外篇说了,其实熬过了前两章,后面的内容反而要简单。
从本章开始,我们的绝大多数的开发内容都是基于STM32的固件库进行的。

从main函数说起

用c编写函数,都知道入口函数是main函数,程序跑起来一定会找main函数;所以我们的编译器在编译的时候还会做强制的main函数重复检测,避免定义多个main函数执行的时候导致不可预知的结果;

但是,为什么选择的是main呢?我们觉得理所当然,其实有人替你负重前行,如果你做gcc编译的c代码,然后在Linux/ Windows上面执行,能够直接跑进main函数是因为底层的操作系统定义了可执行规范(Windows是COFF规范,而Linux则是ELF规范),如果你做过Java,通过java -jar可以运行可执行jar文件里面main函数,则是因为JVM规范里面有定义规则;

但是对于STM32的板子而言,没有JVM,更没有操作系统,有的只是CPU的操作指令;而可以直接让CPU执行的是机器码;可以直接编译为机器码的是汇编语言;所以,定义相应的规范汇编代码是最佳的选择,在STM32固件库里面,第一个就是封装定义各种底层规则的汇编代码:startup_stm32f10x_md.S(汇编文件一般都是.S结尾的);

不需要逐行理解.S文件,你只需要关注几个地方即可,后面我们会逐渐展开。本节你需要知道的为什么会是main函数被选择:

... ...
         IMPORT  __main
         IMPORT  SystemInit
                                 LDR     R0, =SystemInit
                                 BLX     R0
                                 LDR     R0, =__main
                                 BX      R0
                                 ENDP
... ...

.S文件可以直接编译为机器码执行,而且是从头到尾顺序执行的,除非有goto语句;强调这一点是要说.S文件执行规则不是找main函数,就是从头一条一条的执行。这里面你将会看到IMPORT __main的语句,这里就是定义了入口函数地址,当main.c被编译之后,将在到内存里面,所有的函数其实是地址的索引,这里import __main其实就是记录一下main函数的地址,当.S文件执行完毕后(环境初始化工作完成了),将会执行main函数地址后面的代码语句。

小贴士

IMPORT main之后就是IMPORT SystemInit,这里需要注意,如果你没有引用STM32官方固件库之前(一堆.c,.h文件),示例代码3行到5行是需要被注释的,因为这里IMPORT的函数地址SystemInit是固件库的函数,没有引用固件库就没有编译文件,没有编译文件就不会加载到内存里面,这里IMPORT其实是找不到函数地址的,于是执行到这里就会发生异常;

所以,在没有引入固件库之前不要IMPORT SystemInit;如果引入了固件库之后,在放开System_Init,可以节省你的很多工作量,比如RCC的配置,默认情况其实已经很好了,就不需要你在手工对各个寄存器进行配置了。

基于固件库的LED点亮

代码一览

#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"

void delay(unsigned int time)
{    
     unsigned int i=0;  
     while(time--)
     {
            i=1000000;
            while(i--) ;    
     }
}

int main(void)
{
        GPIO_InitTypeDef led;

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
        led.GPIO_Pin = GPIO_Pin_13;
        led.GPIO_Mode = GPIO_Mode_Out_PP;
        led.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOC, &led);
        GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);

        while (1)
        {
                GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
                delay(1);
                GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);
                delay(1);
        }
}

只有这些;是的,你没有看错,如果引入了固件库,实现前面两章的内容只需要这30多行代码;

定义GPIO初始化结构体

第一行代码是定义GPIO初始化所有的结构体

GPIO_InitTypeDef led;

后面你可以以着属性赋值的方式来设置寄存器;

小贴士

对于变量的声明需要放在一个函数体的最前面,如果把16行代码和18行代码交换,使用Keil5的ARM编译器编译会报错的,所以对于结构体的声明要放在前面:

使能APB总线时钟

第二行代码则是使能APB总线时钟,

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

有了前面两章基础,你在看这句代码会不会更加有画面感?

1.通过查手册,进行地址的计算;

2.通过查手册确认RCC_APB2ENR所在章节;

3.查看寄存器里面定义,确认IOPC_EN位置;

4.通过位运算来为寄存器赋值。

GPIO配置

接下来就是做GPIO相关配置:

led.GPIO_Pin = GPIO_Pin_13;
led.GPIO_Mode = GPIO_Mode_Out_PP;
led.GPIO_Speed = GPIO_Speed_50MHz;

引脚配置

是配置目标引脚的编号是13(GPIOC_13);

输出模式配置

然后是配置GPIO的模式,类型为PP,是Push Pull,中文直译是推拉,不过他的学名叫推挽,挽,就是拉的意思,手挽手。这里配置的是输出模式,为什么要配置输出模式,要明白PC13和VCC3.3是在LED灯的两端,两端所谓的电压差其实都是输出才会形成电压差;所以这里要配置PC13为输出;

在STM32里面,GPIO的输出模式有两种,一种是开漏,另外一种就是推挽;对于推挽和开漏的理解要从电子电路的角度来理解,这里就不做详细解释,你只要记住两者的应用场景即可,推挽用于需要高速/频繁切换开关的场景,类似于开关,可以直接软件层面配置,直接输出高电平和低电平;在我们这里要实现的呼吸灯,PC13电平是在高低电平之间做切换的,所以推挽模式是适合的;

而开漏,应为是open drain,drain是排水,外流的意思,所以open drain就开漏,可以理解为开闸放水,为什么这么比喻呢?开漏输出模式一般用于采集多个引脚状态,通过与逻辑运算了计算结果来指导电路行为,例如在I2C总线,当PIN_A,PIN_B以及PIN_C任意一个是低电平,“与运算”结果就是0,开漏输出逻辑值就是0,即可判断总线处于占用状态。

所以,大多数场景输出都是推挽模式。

频率配置

最后一个配置是GPIO的频率,这里选择的是50MHz;这里频率大小是要看应用频率,按照奎斯特定理,采样率是要高于信号频率2倍以上的,在实际的操作过程中,一般采用5~10倍;所以引脚(采样)频率设置高是没有问题,但是低可能会有反应延迟问题;不过采样频率过高也有一个能耗问题;

我们此次应用是呼吸灯,每秒亮灭切换1次,即两个周期,所以周期=1/2;频率是周期(时钟)的倒数,所以有2/1 =2 ;所以时钟理论只要大于2Hz即可,为了确保可靠,一般需要设置为20Hz;50MHz远远超过了20Hz范围内的,不过配置再小一些没有问题。

我们可以看一下stm32固件库提供的几种频率:

typedef enum
{ 
    GPIO_Speed_10MHz = 1,
    GPIO_Speed_2MHz, 
    GPIO_Speed_50MHz
} GPIOSpeed_TypeDef;

从大到小依次是50MHz,10MHz以及2MHz;

GPIO配置生效

上面做的仅是记录对于GPIO的配置,下面代码则是将配置生效:

GPIO_Init(GPIOC, &led);

这个初始化函数将会执行对于相应的寄存器的位的设置;

通过参数可以看到明确了要初始化的PC系列引脚,具体的配置信息则是在led结构体中;注意这里传递是led的地址;为什么呢?因为人家函数定义的参数类型是指针类型,注意最后一个参数GPIO_InitStruct的类型是GPIO_InitTypeDef(注意类型最后有一个“”,这个*就代表指针类型):

// stm32f10x_gpio.c
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
        ... ...
}    

为什么要定义为指针类型呢?因为节省空间,因为函数的参数分配内存空间是在栈上面,而内存栈空间一般都是比较小;如果直接复杂度数据类型作为函数参数会占用比较大的栈空间,甚至可能导致栈空间内存溢出,但是如果类型是指针,那么传递就是地址,最多占用的就是32bit(STM32地址占位32bit),即4个字节,比较节省空间,所以如果要在函数间传递复杂的数据类型,比如结构体,联合体等都是设置为指针类型。

呼吸灯效果

接着while内部实现的则是呼吸灯的效果:

while (1)
        {
                GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);
                delay(1);
                GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);
                delay(1);
        }

GPIO_WriteBit函数用于向设置指定引脚的高低电平,通过函数的参数指定了要给PC13引脚高电平(Bit_SET)或者低电平(Bit_RESET);怎么知道Bit_SET是代表高电平还是低电平?查看他的定义:

// stm32f10x_gpio.c
typedef enum
{ 
    Bit_RESET = 0,
    Bit_SET
}BitAction;

Bit_RESET值是0,代表低电平;默认情况下,由c语言中enum规则,后续的元素依次+1,所以Bit_SET值就是1,代表高电平。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容