固件库内的函数是以STM32F10XXX内的每一个模块而设计的,它们给用户的感觉直观而灵活,使用户能够更方便的配置STM32F10XXX寄存器。这种针对于模块而编写的固件库函数包含“数据结构”和“操作寄存器算法”两个方面的设计。它们被实现的方式值得我学习,如USART_Init()函数能够通用于如USARTx[x=1,2,3,]的每一个通道,而不必分别为每一个通道都编写一个函数。
1 USART_Init()原型
USART_Init()函数在stm32f10x_usart.c内。原型为
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct); |
(1) 功能
根据USART_InitStruct结构体参数内的元素初始化USARTx外设。
(2) 参数
A. USARTx
USARTx为USART_TypeDef结构体指针变量。USART_TypeDef在MDK的<stm32f10x.h>中定义,
typedef struct { __IO uint16_t SR; uint16_t RESERVED0; __IO uint16_t DR; uint16_t RESERVED1; __IO uint16_t BRR; uint16_t RESERVED2; __IO uint16_t CR1; uint16_t RESERVED3; __IO uint16_t CR2; uint16_t RESERVED4; __IO uint16_t CR3; uint16_t RESERVED5; __IO uint16_t GTPR; uint16_t RESERVED6; } USART_TypeDef; |
B. USART_InitStruct
USART_InitStruct 为结构体USART_InitTypeDef指针型变量。USART_InitTypeDef在固件库的<stm32f10x_usart.h>中定义,
typedef struct { uint32_t USART_BaudRate; //USART传输数据的波特率值 uint16_t USART_WordLength; //USART传输数据的长度 uint16_t USART_StopBits; //USART传输数据的停止位数 uint16_t USART_Parity; //USART校验位 uint16_t USART_Mode; //USART传送和接收模式 uint16_t USART_HardwareFlowControl; //USART硬件控制模式设置 } USART_InitTypeDef; |
2 USART_Init()源码
(1) 断言assert_param()
assert_param()是一个宏,它在固件库的<stm32f10x_conf.h>文件中被这样定义,
#ifdef USE_FULL_ASSERT #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__)) void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr) ((void)0) #endif |
功能:assert_param()宏用来检查函数参数。在定义了USE_FULL_ASSERT情况下,如果expr为假,就会调用assert_failed输出正在调用assert_param宏的文件名及调用asser_param宏的行号。如果expr为真,不返回任何值。如果没有定义USE_FULL_ASSERT,则该宏不返回任何值。__FILE__, __LINE__属C中定义的宏,表示当前文件名及所在行的行号。
参数:expr可以为任何逻辑表达式。expr一般为宏,这个宏列举了该参数的所有情况,如果该参数在这些情况之内则expr为真,否则为假。
assert_failed:这个函数需要用户在主函数文件(如main.c)中定义,如:
void assert_failed(uint8_t* file, uint32_t line) { //Print DEBUG information printf(“%s ‘s %d line parameters error \n”); while (1){ } } |
断言的作用是供用户在调试(DEBUG)模式下更加方便的找到错误,在Debug中设置或者在程序中定义诸如USE_FULL_ASSERT之类的宏,断言代码就会运行,断言的运行需要占用一部分时间。在Release模式下,断言就不在需要,就可以被关闭,断言就变为诸如assert_param(expr) ((void)0)一样的语句。《C编程精粹》中有讲断言强大功效的。
(2) 配置寄存器
配置寄存器时需要注意两个方面:A. 确保寄存器被配置位被准确地配置。B. 不要影响寄存器非配置位的原有状态。做到A,B两个方面一般采用“将需要配置的位用与运算清0,再用或运算配置需要配置位”步骤。
[1] 配置CR2
tmpreg = USARTx->CR2; tmpreg &= CR2_STOP_CLEAR_Mask; tmpreg |= (uint32_t)USART_InitStruct->USART_StopBits; USARTx->CR2 = (uint16_t)tmpreg; |
读CR2的值于tmpreg中。CR2_STOP_CLEAR_Mask的值为0xCFFF,使tmpreg的bit[13:12]为00,其它位保持不变。tmpreg与USART_InitStuct结构体元素USART_StopBits作或运算,USART_StopBits的值为0x0000,0x0100,0x0200,0x0300类,它们除在bit[13:12]之上有特殊的状态值之外其它位都为0。目的是配置CR2的bit[13:12]位已得到停止位数。然后再将tmpreg的值赋给CR2寄存器。
[2] 配置CR1
tmpreg =USARTx->CR1; tmpreg &=CR1_CLEAR_Mask; tmpreg |=(uint32_t)USART_InitStruct->USART_WordLength |USART_InitStruct->USART_Parity | USART_InitStruct->USART_Mode; USARTx->CR1 = (uint16_t)tmpreg; |
读CR1的值于tmpreg中。CR1_CLEAR_Mask的值为0xE9F3,与tmpreg作与运算使得bit[12](M),bit[10:9](PCE,PS),bit[3:2](TE,RE)这些需要被配置的位的值为0,00,00。然后与只设置了bit[12],bit[10:9],bit[3:2]值而其它位皆为0的数据位数位(USART_WordLength)、校验位(USART_Parity)、发/收模式位(USART_Mode)的状态值作或运算。然后将配置值tmpreg赋给CR1。
根据USART_InitStruct结构体参数内的USART_WordLength、USART_Parity、USART_Mode元素值分别配置CR1的bit[12,10:9,3:2]就得到相应的数据长度、校验方式、USARTx的工作模式。
[3] 配置CR3
tmpreg =USARTx->CR3; tmpreg &=CR3_CLEAR_Mask; tmpreg |=USART_InitStruct->USART_HardwareFlowControl; USARTx->CR3 = (uint16_t)tmpreg; |
通过USART_InitStruct结构体参数内的USART_HardwareFlowControl元素值配置CR3的bit[9:8]就可以选择CTSE/RTSE的开启与关闭。
[4] 配置BRR
配置BRR的目的是得到数据通信的波特率。而BRR的配置需要USARTx的时钟频率和波特率值。所以需要先获取USARTx的时钟频率,再根据波特率值配置BRR。获取USARTx时钟频率
这个功能由”stm32f10x_rcc.c”文件内的固件库函数RCC_GetClocksFreq()得来。它可以获取SYSCLK/HCLK/PCLKx等时钟频率值,并将它们保存在结构体RCC_ClocksTypeDef*类型的结构体中。
除USART1使用PCLK2外,其它的USARTx都使用PCLK1。所以,我们需要判断当前的USARTx是USART1还是其它,如果是USART1则使用PCLK2的时钟频率,否则使用PCLK1的时钟频率。
usartxbase =(uint32_t)USARTx; if (usartxbase == USART1_BASE){ apbclock =RCC_ClocksStatus.PCLK2_Frequency; }else{ apbclock = RCC_ClocksStatus.PCLK1_Frequency; } |
由波特率和时钟频率计算配置BRR的值
可配置的BRR位为bit[15:0],配置给BRR的值称为USARTDIV,bit[15:4]为USARTDIV整数部分,bit[3:0]为USARTDIV小数部分。其中 ,integerdivider = ((0x19 * apbclock) / (0x04 * (USART_InitStruct->USART_BaudRate))); tmpreg = (integerdivider / 0x64) << 0x04; fractionaldivider = integerdivider - (0x64 * (tmpreg >> 0x04)); tmpreg |= ((((fractionaldivider * 0x10) + 0x32) / 0x64)) & ((uint8_t)0x0F); USARTx->BRR = (uint16_t)tmpreg; |
第三条语句:fractionaldivider的值就是小数位在两位精度下扩大100倍后的值(如USARTDIV为468.75时,integerdivider为46875,此时的fractionaldivider就为75)。
第四条语句:((((fractionaldivider * 0x10) + 0x32) / 0x64))表示将fractionaldivider乘以16后加上50,再除以100。USARTDIV小数部分乘以16再4舍5入得到BRR的bit[3:0]值,见《stm32f10xx参考手册》“25.3.4分数波特率的产生”一节。
令USARTDIV小数部分乘以16后的小数部分为k,fractionaldivider * 16的十位和个位表示的两位数( (fractionaldivider * 16 )% 100)为j,j和k是100倍的关系。按照4舍5入的原则,如果k < 0.5(j < 50),就直接舍弃;如果k > 0.5( j >50 )那么就应该向整数部分进1并舍弃k(向百位进1,然后舍弃j)。第四条语句中的((((fractionaldivider * 0x10) + 0x32),系统 会根据j是否大于50来决定是否向百位进1,再利用整数除法(’/’)的特点除以100舍掉十位和个位得到4舍5入后的值。利用程序中整数除法的特点实现了一个浮点数4舍5入的功能。将整个值再与)0x0F作与运算是为了保证BRR的bit[3:0]部分不会超过15,在将USARTDIV乘以100的情况下操作,在USARTDIV小数部分大于等于0.97时,经4舍5入的小数部分值就会为16,此时应该向整数部分进1,并且小数部分为0。不过这里没有向参考手册之上讲的那样,当USARTDIV小数部分溢出时向整数部分进1。我觉得第四个语句应该为:tmpreg|= ((((fractionaldivider * 0x10) + 0x32) / 0x64)) & ((uint8_t)0x1F);
第五条语句:将计算好的值赋给BRR寄存器。