[其他ST产品] 串口相关配置寄存器、库函数(UART一般步骤)

[复制链接]
3498|32
 楼主| 4c1l 发表于 2023-9-25 18:49 | 显示全部楼层 |阅读模式
串口相关配置寄存器状态寄存器(USART_SR)
28732651165aba0629.png

状态寄存器适用于检测串口此时所处的状态。它能够检测到的状态有:发送寄存器空位、发送完成位、读数据寄存器非空位、检测到主线空闲位、过载错误为等等。

这边主要关注两个位:RXNE和TC(第5、6两位)。

RXNE(读数据寄存器非空):当该位被置1的时候,就是提示已经有数据被接收到了,并且可以读出来了(即RDR移位寄存器中的数据被转移到USART_DR寄存器中)。这时候要做的就是尽快读取USART_DR,从而将该位清零,也可以向该位写0,直接清除。
TC(发送完成):当该位被置1的时候,表示USART_DR内的数据已经被发送完成了。如果设置了这个位的中断,则会产生中断。该位也有两种清零方式:读USART_SR,写USART_DR;直接向该位写0。



 楼主| 4c1l 发表于 2023-9-25 18:49 | 显示全部楼层
数据寄存器(USART_DR)

74568651165c34e592.png

USART_DR实际是包含了两个寄存器,一个专门用于发送的TDR,一个专门用于接收的RDR。进行发送数据操作时,往USART_DR写入数据会自动存储在TDR内;当进行读取数据操作时,向USART_DR读取数据会自动提取RDR数据。

串行通信时一位一位传输的,所以TDR和RDR寄存器都是介于系统总线和移位寄存器间的;发送数据时把TDR内容转移到发送移位寄存器上,接收数据时则是把接收到的每一位顺序保存在接收移位寄存器内进而转移到RDR。
 楼主| 4c1l 发表于 2023-9-25 18:50 | 显示全部楼层
波特率寄存器 (USART_BRR)

43157651165dd56547.png

波特率寄存器包括定义了两个部分:DIV_Mantissa(整数部分)和DIV_Fraction(小数部分)。
 楼主| 4c1l 发表于 2023-9-25 18:50 | 显示全部楼层
控制寄存器(USART_CRx)
3003651165f460b25.png
控制寄存器主要是设置USART使能、检验控制使能、校验选择(奇校验偶校验)、PE中断使能、发送缓冲区空中断使能、发送完成中断使能、接收缓冲区非空使能、发送使能、接受使能、字长等等。
 楼主| 4c1l 发表于 2023-9-25 18:51 | 显示全部楼层
USART外设引脚复用
当使用USART的时候,GPIO需要引脚复用,下图介绍了USART的引脚设置:

687046511660a9e2d8.png
 楼主| 4c1l 发表于 2023-9-25 18:51 | 显示全部楼层
波特率计算方法
学习波特率之前,首先了解一下通讯速率。通讯速率通常是以比特率来表示,即每秒钟传输的二进制位数,单位为比特每秒(bit/s)。容易和比特率混淆的概念是“波特率”,它表示每秒传输了多少码元。

码元是通讯信号调制的概念,时间间隔相同的符号来表示一个二进制数字,这样的信号就称为码元。如常见的通讯传输中:用0V表示数字0,5V表示数字1,那么一个码元可以表示两种状态0和1,所以一个码元等于一个二进制比特位,此时波特率的大小与比特率一致;若传输中,有0V、2V、4V和6V分别表示00、01、10、11,那么每个码元可以表示四种状态,两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。因为很多常见的通讯中一个码元都是表示两种状态,人们常常直接以波特率来表示比特率,其实二者是有区别的。
 楼主| 4c1l 发表于 2023-9-25 18:51 | 显示全部楼层
异步通讯由于没有时钟信号,所以两个通讯设备需要规约好波特率,即每个码元的长度,以便对信号进行解码。常见的波特率为4800、9600、115200。
857806511662816690.png
 楼主| 4c1l 发表于 2023-9-25 18:51 | 显示全部楼层
上面的公式中,fpclkx是给串口的时钟(PCLK1用于USART2、3、4、5,PCLK2用于USART1);USARTDIV是一个无符号定点数。我们只要得到USARTDIV的值,就可以计算出波特率。

971086511663fa9f9b.png

 楼主| 4c1l 发表于 2023-9-25 18:52 | 显示全部楼层
串口操作相关库函数
1个初始化函数
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
作用:用于串口波特率,数据字长,奇偶校验,硬件流控以及收发使能等配置的初始化。
 楼主| 4c1l 发表于 2023-9-25 18:52 | 显示全部楼层
2个使能函数
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
 楼主| 4c1l 发表于 2023-9-25 18:52 | 显示全部楼层
作用:前者使能串口,后者使能串口的相关中断。

2个数据收发函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
作用:前者发送数据到串口,后者从串口接收数据。
 楼主| 4c1l 发表于 2023-9-25 18:53 | 显示全部楼层
4个状态位函数
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);
作用:前两者获取(或清除)状态标志位,后两者为获取(或清除)中断状态标志位。
 楼主| 4c1l 发表于 2023-9-25 18:54 | 显示全部楼层
串口操作的一般步骤
GPIO时钟使能,串口时钟使能。调用函数:RCC_APB2PeriphClockCmd()(可以参阅:【STM32】STM32端口复用和重映射(AFIO辅助功能时钟) 的部分内容);
串口复位(这一步不是必须的)。调用函数:USART_DeInit();
GPIO外设功能下的端口模式设置。调用函数:GPIO_Init();
串口参数初始化。调用函数:USART_Init();
开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)。调用函数:NVIC_Init();USART_ITConfig();
使能串口。调用函数:USART_Cmd();
编写中断处理函数。调用函数:USARTx_IRQHandler();
串口数据收发。调用函数:USART_SendData();USART_ReceiveData();
串口传输状态获取。调用函数:USART_GetFlagStatus();USART_ClearITPendingBit();
 楼主| 4c1l 发表于 2023-9-25 18:54 | 显示全部楼层
下面按照这个一般步骤来进行一个简单的串口程序:
  1. void My_USART1_Init(void)
  2. {
  3.         GPIO_InitTypeDef GPIO_InitStrue;
  4.         USART_InitTypeDef USART_InitStrue;
  5.         NVIC_InitTypeDef NVIC_InitStrue;
  6.        
  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIO端口使能
  8.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口端口使能
  9.        
  10.         GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;
  11.         GPIO_InitStrue.GPIO_Pin=GPIO_Pin_9;
  12.         GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;
  13.         GPIO_Init(GPIOA,&GPIO_InitStrue);
  14.        
  15.         GPIO_InitStrue.GPIO_Mode=GPIO_Mode_IN_FLOATING;
  16.         GPIO_InitStrue.GPIO_Pin=GPIO_Pin_10;
  17.         GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;
  18.         GPIO_Init(GPIOA,&GPIO_InitStrue);
  19.        
  20.         USART_InitStrue.USART_BaudRate=115200;
  21.         USART_InitStrue.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
  22.         USART_InitStrue.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
  23.         USART_InitStrue.USART_Parity=USART_Parity_No;
  24.         USART_InitStrue.USART_StopBits=USART_StopBits_1;
  25.         USART_InitStrue.USART_WordLength=USART_WordLength_8b;
  26.        
  27.         USART_Init(USART1,&USART_InitStrue);//
  28.        
  29.         USART_Cmd(USART1,ENABLE);//使能串口1
  30.        
  31.         USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启接收中断
  32.        
  33.         NVIC_InitStrue.NVIC_IRQChannel=USART1_IRQn;
  34.         NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE;
  35.         NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=1;
  36.         NVIC_InitStrue.NVIC_IRQChannelSubPriority=1;
  37.         NVIC_Init(&NVIC_InitStrue);
  38.        
  39. }

  40. void USART1_IRQHandler(void)
  41. {
  42.         u8 res;
  43.          if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
  44. {
  45.      res= USART_ReceiveData(USART1);
  46.      USART_SendData(USART1,res);   
  47.   }
  48. }

  49. int main(void)
  50. {       
  51.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  52.         My_USART1_Init();
  53.          while(1);
  54.          
  55. }
 楼主| 4c1l 发表于 2023-9-25 18:55 | 显示全部楼层
usrt_init函数

此处的GPIO端口模式设置,是在端口复用情况下的端口模式设置。至于在复用功能下,GPIO的模式怎么设置,可以查看手册《STM32中文参考手册》p110的内容;
USART_BaudRate波特率的设定是直接写值进去的,MDK5是没有预设的宏定义来选择的;
USART_Mode模式选择用USART_Mode_Tx|USART_Mode_Rx来表示发送使能和接收使能;
NVIC_IRQChannel中断通道,这是在stm32f10x.h开头部分以IRQn结尾的宏定义。
USART1_IRQHandlar函数
 楼主| 4c1l 发表于 2023-9-25 18:57 | 显示全部楼层
USART1_IRQHandlar函数

USART1_IRQHandlar函数是中断处理函数,不能随意定义的,需要遵循MDK的定义。这些函数的声明在启动文件startup_stm32f10x_hd.s文件中,可以在其中找到中断处理函数的名称。
中断处理函数中首先进行状态位判断,这是非常有必要的。因为可能我们在串口的中断设置中,设置了不止一处的中断,但是每个中断进入的中断处理函数都是同一个,这就要求我们要在中断处理函数中通过状态位的判断再进行接下来的操作。
 楼主| 4c1l 发表于 2023-9-25 18:57 | 显示全部楼层
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
 楼主| 4c1l 发表于 2023-9-25 18:57 | 显示全部楼层
这里通过这个判断来确定是否接收中断。这个函数的返回值是ITStatus类型,它的定义是:

typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;
 楼主| 4c1l 发表于 2023-9-25 18:57 | 显示全部楼层
SET代表着1(接收到中断),RESET代表着0(未接收中断)。

除了中断处理函数的ITStatus需要判断之外,通常发送数据、接收数据之后也需要判断。比如,下面一段程序:
USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
 楼主| 4c1l 发表于 2023-9-25 18:57 | 显示全部楼层
即运用while循环,在向串口发送数据之后,通过判断等待发送结束。

这段程序还有一个问题曾经困扰了我很久很久都没有领悟清楚:

在中断处理函数中,使用一下程序来接收数据:

        u8 res;
     res= USART_ReceiveData(USART1);
您需要登录后才可以回帖 登录 | 注册

本版积分规则

65

主题

703

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部