但这对于许多从51/AVR这类单片机的开发转到STM32平台的开发人员来说,势必有一个不适应的过程。因为程序开发不再是从寄存器层次起始,而要首先去熟悉STM32所提供的固件库。那是否一定要使用固件库呢?当然不是。但STM32微控制器的寄存器规模可不是常见的8位单片机可以比拟,若自己细细琢磨各个寄存器的意义,必然会消耗相当的时间,并且对于程序后续的维护,升级来说也会增加资源的消耗。对于当前“时间就是金钱”的行业竞争环境,无疑使用库函数进行STM32的产品开发是更好的选择。本文将通过一个简单的例子对STM32的库函数做一个简单的剖析。
以最常用的GPIO设备的初始化函数为例,如下程序段一:
GPIO_InitTypeDef GPIO_InitStructure; 1GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; 2 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 4 GPIO_Init(GPIOA , &GPIO_InitStructure); 5
复制代码l 首先是1,该语句显然定义了一个GPIO_InitTypeDef类型的变量,名为GPIO_InitStructure,则找出GPIO_InitTypeDef的原型位于“stm32f10x_gpio.h”文件,原型如下:
typedef enum{ GPIO_Speed_10MHz = 1, GPIO_Speed_2MHz, GPIO_Speed_50MHz }GPIOSpeed_TypeDef;
复制代码同样也在“stm32f10x_gpio.h”文件中找到对GPIOMode_TypeDef的定义:
typedef enum{ GPIO_Mode_AIN = 0x0, GPIO_Mode_IN_FLOATING = 0x04, GPIO_Mode_IPD = 0x28, GPIO_Mode_IPU = 0x48, GPIO_Mode_Out_OD = 0x14, GPIO_Mode_Out_PP = 0x10, GPIO_Mode_AF_OD = 0x1C, GPIO_Mode_AF_PP = 0x18 }GPIOMode_TypeDef;
复制代码至此对程序段一的○1解析可以做一个总结:
该行定义一个结构体类型的变量GPIO_InitStructure,并且该结构体有3个成员,分别为GPIO_Pin、GPIO_Speed和GPIO_Mode,并且GPIO_Pin表示GPIO设备引脚GPIO_Speed表示GPIO设备速率和GPIO_Mode表示GPIO设备工作模式。
接下来是2,此句是一个赋值语句,把GPIO_Pin_4赋给GPIO_InitStructure结构体中的成员GPIO_Pin,可以在“stm32f10x_gpio.h”文件中找到对GPIO_Pin_4做的宏定义:
#define GPIO_Pin_4 ((u16)0x0010)
复制代码3语句和2相似将GPIO_Speed_50MHz赋给GPIO_InitStructure结构体中的成员GPIO_Speed,但注意到此处GPIO_Speed_50MHz只是一个枚举变量,并非具体的某个值。
4语句亦和2语句类似,把GPIO_Mode_Out_PP赋给GPIO_InitStructure结构体中的成员GPIO_Mode,从上文可知GPIO_Mode_Out_PP的值为0x10。
5是一个函数调用,即调用GPIO_Init函数,并提供给该函数2个参数,分别为GPIOA和&GPIO_InitStructure,其中&GPIO_InitStructure表示结构体变量GPIO_InitStructure的地址,而GPIOA则在“stm32f10x_map.h”文件中找到定义:
#ifdef _GPIOA#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) #endif
复制代码#define _GPIOA
复制代码#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
复制代码#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
复制代码#define PERIPH_BASE ((u32)0x40000000)
复制代码GPIOA_BASE = 0x40000000 + 0x10000 + 0x0800
复制代码对○5句代码进行一个总结:调用GPIO_Init函数,并将STM32微控制器的GPIOA设备地址和所定义的结构体变量GPIO_InitStructure的地址传入。
以上是对GPIOA初始化库函数的剖析,现继续转移到函数内部分析,GPIO_Init函数原型如程序段二:
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct){ u32 currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00; u32 tmpreg = 0x00, pinmask = 0x00; /* 检查参数是否正确 */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode)); assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin)); /* 将工作模式暂存至 currentmode 变量中 */ currentmode = ((u32)GPIO_InitStruct->GPIO_Mode) & ((u32)0x0F); /* 如果欲设置为任意一种输出模式,则再检查”翻转速率“参数是否正确 */ if ((((u32)GPIO_InitStruct->GPIO_Mode) & ((u32)0x10)) != 0x00) { assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); currentmode |= (u32)GPIO_InitStruct->GPIO_Speed; } /* 设置低八位引脚(即 pin0 ~ pin7) */ if (((u32)GPIO_InitStruct->GPIO_Pin & ((u32)0x00FF)) != 0x00) { /* 读出当前配置字 */ tmpreg = GPIOx->CRL; for (pinpos = 0x00; pinpos < 0x08; pinpos++) { /* 获取将要配置的引脚号 */ pos = ((u32)0x01) << pinpos; currentpin = (GPIO_InitStruct->GPIO_Pin) & pos; if (currentpin == pos) { /* 先清除对应引脚的配置字 */ pos = pinpos << 2; pinmask = ((u32)0x0F) << pos; tmpreg &= ~pinmask; /* 写入新的配置字 */ tmpreg |= (currentmode << pos); /* 若欲配置为上拉 / 下拉输入,则需要配置 BRR 和 BSRR寄存器 */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((u32)0x01) << pinpos); } else { if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((u32)0x01) << pinpos); } } } } /* 写入低八位引脚配置字 */ GPIOx->CRL = tmpreg; } /* 设置高八位引脚(即 pin8 ~ pin15),流程和第八位引脚配置流程一致,不再作解析 */ if (GPIO_InitStruct->GPIO_Pin > 0x00FF) { tmpreg = GPIOx->CRH; for (pinpos = 0x00; pinpos < 0x08; pinpos++) { pos = (((u32)0x01) << (pinpos + 0x08)); currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos); if (currentpin == pos) { pos = pinpos << 2; pinmask = ((u32)0x0F) << pos; tmpreg &= ~pinmask; tmpreg |= (currentmode << pos); if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((u32)0x01) << (pinpos + 0x08)); } if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((u32)0x01) << (pinpos + 0x08)); } } } GPIOx->CRH = tmpreg; } }
复制代码该寄存器为32位,其中分为8份,每份4位,对应低八位引脚的设置。每一个引脚的设置字分为两部分,分别为CNF和MODE,各占两位空间。当MODE的设置字为0时,表示将对应引脚配置为输入模式,反之设置为输出模式,并有最大翻转速率限制。而当引脚配置为输出模式时,CNF配置字则决定引脚以哪种输出方式工作(通用推挽输出、通用开漏输出等)。通过对程序的阅读和分析不难发现,本文最初程序段中GPIO_InitStructure所传入参数的对寄存器的作用如下:
1、GPIO_Pin_4被宏替换为0x0010,对应图1可看出为用于选择配置GPIOx_CRL的[19:16]位,分别为CNF4[1:0]、MODE4[1:0]。
2、GPIO_Speed_50MHz为枚举类型,包含值0x03,被用于将GPIOx_CRL位中的MODE4[1:0]配置为b11(此处b意指二进制)。
3、GPIO_Mode亦为枚举类型,包含值0x10,被用于将GPIOx_CRL位中的MODE4[1:0]配置为b00。事实上GPIO_Mode的值直接影响寄存器的只有低四位,而高四位的作用可以从程序段二中看出,是用于判断此参数是否用于GPIO引脚输出模式的配置。
至此应不难知道STM32的固件库最后是怎样影响最底层的寄存器的。总结起来就是:固件库首先将各个设备所有寄存器的配置字进行预先定义,然后封装在结构或枚举变量中,待用户调用对应的固件库函数时,会根据用户传入的参数从这些封装好的结构或枚举变量中取出对应的配置字,最后写入寄存器中,完成对底层寄存器的配置。
可以看到,STM32的固件库函数对于程序开发人员来说是十分便利的存在,只需要填写言简意赅的参数就可以在完全不关心底层寄存器的前提下完成相关寄存器的配置,具有相当不错的通用性和易用性,也采取了一定措施保证库函数的安全性(主要引入了参数检查函数assert_param)。但同时也应该知道,通用性、易用性和安全性的代价是加大了代码量,同时增加了一些逻辑判断代码造成了一定的时间消耗,在对时间要求比较苛刻的应用场合需要评估使用固件库函数对程序运行时间所带来的影响。读者在使用STM32的固件库函数进行程序开发时,应该意识到这些问题。
本文转自网络。Pdf格式文章下载链接:http://pan.baidu.com/s/1eRFCTpC
来源:网络