[ M3 LN ] FW(固件)库函数USART_Init()

固件库内的函数是以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寄存器。


经过USART_InitStruct结构体参数内的USART_StopBits元素值配置CR2寄存器的bit[13:12],可得数据的停止位位数


[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;
读CR3的值于tmpreg中。CR3_CLEAR_Mask的值为0xFCFF,与tmpreg作与运算并赋给tmpreg后tmpreg的bit[9:8]变为了00。与只设置了bit[9:8]值而其它位为0的USART_HardwareFlowControl作或运算后赋值给tmpreg。将配置好的tmpreg赋给CR3。

通过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;

}
将USARTx的地址赋给usartxbase变量中。然后匹配usartxbase到底是哪一个USART通道的地址,在MDK中,USARTx在对应的外设下被编了地址,USART1_BASE的值为(APB2PERIPH_BASE + 0x3800),APB2PERIPH_BASE的值为(PERIPH_BASE + 0x10000),PERIPH_BASE 的值为((uint32_t)0x40000000),0x40000000这个值是SRAM在bit-band区域的起始地址,总之USARTx的地址被分配好了,可以在MDK的<stm32f10x.h>中找到。如果USARTx参数的地址为USART1的基地址,则使用PCLK2的时钟频率,否则使用PCLK1的时钟频率。

由波特率和时钟频率计算配置BRR的值

可配置的BRR位为bit[15:0],配置给BRR的值称为USARTDIV,bit[15:4]为USARTDIV整数部分,bit[3:0]为USARTDIV小数部分。其中 , 
其中,bratevalue的值为波特率值。BRR的bit[15:4]位的值为USARTDIV的整数值,BRR的bit[3:0]位的值为USARTDIV小数部分乘以16后经4舍5入得到的整数值。当这个4舍5入的值为16时向bit[15:4]进1( bit[3:0]存储数的范围为0x0 ~ 0xf),bit[3:0]为0。
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;
第一条语句:0x19的值为25,integerdivider的值是USARTDIV的100倍。
第二条语句:0x64的值是100,(integerdivider / 0x64) 值为USARTDIV的整数部分。 tmpreg由(integerdivider / 0x64)向左移4位后就对应着BRR的bit[15:4]的值。
第三条语句: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寄存器。

另外,程序中之所以将USARTDIV的值扩大100倍来进行操作是选择了决定4舍5入的精度为两位小数。令USARTDIV小数部分为h,当h = 0.96875时,16 * h = 15.5。只要h > 0.96875就可以产生4舍5入,但当只保留两位小数时,只有h = 0.97才能刚好发生4舍5入。我们也可以在程序中规定决定4舍5入的精度为3位小数,那接下来的跟100倍相关的都要换成1000,在100倍下用50也要换成500了。以此类推。

3 USART_Init()地位

USART_Init()是USART通道的寄存器配置函数。如果想要编程使用USART通道通信,在使用USART_Init()函数之前还需要在APBxENR下打开USARTx的时钟信号,如果需要中断还需要开启中断(NVIC->ISER),此时在使用USART_Init()函数配置好USART_Init()模块通信方式后,还需要在GPIO中配置USART_Init()的TX和RX两个引脚的模式。经过这些步骤后方可在通信平台下实现USART通信。

Learning  Note Over.

你可能感兴趣的:([ M3 LN ] FW(固件)库函数USART_Init())