[其他ST产品] STM32-串口通信

[复制链接]
2390|58
 楼主| 甲虫666 发表于 2023-9-24 01:15 | 显示全部楼层
4.4 控制寄存器(USART_CRx)
97377650f1d1b3d078.png
   控制寄存器主要是设置USART使能、检验控制使能、校验选择(奇校验偶校验)、PE中断使能、发送缓冲区空中断使能、发送完成中断使能、接收缓冲区非空使能、发送使能、接受使能、字长等等。
 楼主| 甲虫666 发表于 2023-9-24 01:15 | 显示全部楼层
5 USART外设引脚复用
        当使用USART的时候,GPIO需要引脚复用,下图介绍了USART的引脚设置:
35125650f1d3848586.png
 楼主| 甲虫666 发表于 2023-9-24 01:15 | 显示全部楼层
6 波特率计算方法
        学习波特率之前,首先了解一下通讯速率。通讯速率通常是以比特率来表示,即每秒钟传输的二进制位数,单位为比特每秒(bit/s)。容易和比特率混淆的概念是“波特率”,它表示每秒传输了多少码元。
        码元是通讯信号调制的概念,时间间隔相同的符号来表示一个二进制数字,这样的信号就称为码元。如常见的通讯传输中:用0V表示数字0,5V表示数字1,那么一个码元可以表示两种状态0和1,所以一个码元等于一个二进制比特位,此时波特率的大小与比特率一致;若传输中,有0V、2V、4V和6V分别表示00、01、10、11,那么每个码元可以表示四种状态,两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。因为很多常见的通讯中一个码元都是表示两种状态,人们常常直接以波特率来表示比特率,其实二者是有区别的。
 楼主| 甲虫666 发表于 2023-9-24 01:16 | 显示全部楼层
异步通讯由于没有时钟信号,所以两个通讯设备需要规约好波特率,即每个码元的长度,以便对信号进行解码。常见的波特率为4800、9600、115200。 79470650f1d5030e82.png     上面的公式中,fpclkx是给串口的时钟(PCLK1用于USART2、3、4、5,PCLK2用于USART1);USARTDIV是一个无符号定点数。我们只要得到USARTDIV的值,就可以计算出波特率。 68824650f1d5b12d84.png
 楼主| 甲虫666 发表于 2023-9-24 01:16 | 显示全部楼层
串口操作相关库函数
7.1 1个初始化函数
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);

作用:用于串口波特率,数据字长,奇偶校验,硬件流控以及收发使能等配置的初始化。
 楼主| 甲虫666 发表于 2023-9-24 01:16 | 显示全部楼层
7.2 2个使能函数
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
 楼主| 甲虫666 发表于 2023-9-24 01:16 | 显示全部楼层
        作用:前者使能串口,后者使能串口的相关中断。

 楼主| 甲虫666 发表于 2023-9-24 01:17 | 显示全部楼层
7.3 2个数据收发函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
 楼主| 甲虫666 发表于 2023-9-24 01:17 | 显示全部楼层
        作用:前者发送数据到串口,后者从串口接收数据。

 楼主| 甲虫666 发表于 2023-9-24 01:17 | 显示全部楼层
7.4 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);
————————————————

作用:前两者获取(或清除)状态标志位,后两者为获取(或清除)中断状态标志位。
 楼主| 甲虫666 发表于 2023-9-24 01:17 | 显示全部楼层
8 串口操作的一般步骤
        1)GPIO时钟使能,串口时钟使能。调用函数:RCC_APB2PeriphClockCmd();
        2)串口复位(这一步不是必须的)。调用函数:USART_DeInit();
        3)GPIO外设功能下的端口模式设置。调用函数:GPIO_Init();Tx(发送引脚)配置为复用推挽输出(GPIO_Mode_AF_PP)用来发送数据,Rx(接收引脚)配置为浮空输入(GPIO_Mode_IN_FLOATING)用来接收数据。
        4)串口参数初始化。调用函数:USART_Init();
        5)开启中断并且初始化NVIC(如果需要开启中断才需要这个步骤)。调用函数:NVIC_Init();USART_ITConfig();
        6)使能串口。调用函数:USART_Cmd();
        7)编写中断处理函数。调用函数:USARTx_IRQHandler();
        8)串口数据收发。调用函数:USART_SendData();USART_ReceiveData();
        9)串口传输状态获取。调用函数:USART_GetFlagStatus();USART_ClearITPendingBit();
 楼主| 甲虫666 发表于 2023-9-24 01:18 | 显示全部楼层
        下面按照这个一般步骤来进行一个简单的串口程序:
  1. void My_USART1_Init(void)
  2. {
  3.         GPIO_InitTypeDef GPIO_InitStrue;
  4.         USART_InitTypeDef USART_InitStrue;
  5.         NVIC_InitTypeDef NVIC_InitStrue;

  6.                 // 打开串口GPIO的时钟
  7.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//GPIO端口使能
  8.        
  9.             // 将USART Tx的GPIO配置为推挽复用模式  USART1_TX   GPIOA.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.         // 将USART Rx的GPIO配置为浮空输入模式  USART1_RX          GPIOA.10
  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.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//串口端口使能

  21.             // 配置串口的工作参数
  22.             // 配置波特率
  23.         USART_InitStrue.USART_BaudRate=115200;
  24.             // 配置硬件流控制
  25.         USART_InitStrue.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
  26.             // 配置工作模式,收发一起
  27.         USART_InitStrue.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
  28.             // 配置校验位无奇偶校验位
  29.         USART_InitStrue.USART_Parity=USART_Parity_No;
  30.             // 配置停止位一个停止位
  31.         USART_InitStrue.USART_StopBits=USART_StopBits_1;
  32.             // 配置数据字长8位数据格式
  33.         USART_InitStrue.USART_WordLength=USART_WordLength_8b;
  34.                 // 完成串口的初始化配置
  35.         USART_Init(USART1,&USART_InitStrue);//
  36.        
  37.                 // 使能串口
  38.         USART_Cmd(USART1,ENABLE);
  39.             //开启接收中断
  40.         USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
  41.           
  42.         //配置USART为中断源
  43.         NVIC_InitStrue.NVIC_IRQChannel=USART1_IRQn;
  44.         //使能中断
  45.         NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE;
  46.         //抢断优先级
  47.         NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=1;
  48.         //子优先级
  49.         NVIC_InitStrue.NVIC_IRQChannelSubPriority=1;
  50.         //初始化配置NVIC
  51.         NVIC_Init(&NVIC_InitStrue);
  52.        
  53. }

  54. //串口1中断服务程序
  55. void USART1_IRQHandler(void)
  56. {
  57.         u8 Res;
  58.         if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
  59.                 {
  60.                 Res =USART_ReceiveData(USART1);        //读取接收到的数据
  61.                
  62.                 if((USART_RX_STA&0x8000)==0)//接收未完成
  63.                         {
  64.                         if(USART_RX_STA&0x4000)//接收到了0x0d
  65.                                 {
  66.                                 if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
  67.                                 else USART_RX_STA|=0x8000;        //接收完成了
  68.                                 }
  69.                         else //还没收到0X0D
  70.                                 {       
  71.                                 if(Res==0x0d)USART_RX_STA|=0x4000;
  72.                                 else
  73.                                         {
  74.                                         USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
  75.                                         USART_RX_STA++;
  76.                                         if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收          
  77.                                         }                 
  78.                                 }
  79.                         }                    
  80.      }
  81. }

  82. int main(void)
  83. {       
  84.     /* 嵌套向量中断控制器组选择 */
  85.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

  86.         My_USART1_Init();

  87.         while(1);
  88.          
  89. }
 楼主| 甲虫666 发表于 2023-9-24 01:18 | 显示全部楼层
usrt_init函数

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

                1)使能中断配置函数。
                2)我们配置了接收数据寄存器非空中断。我们可以配置很多类型中断,在ST提供的标准库函数中看到。
 楼主| 甲虫666 发表于 2023-9-24 01:18 | 显示全部楼层
  1. /**
  2.   * @摘要   启用或禁用指定的USART中断。
  3.   * @参数   USARTx: 其中x可以是1、2、3、4、5或6,以选择USART或UART外设。
  4.   * @参数   USART_IT: 指定要启用或禁用的USART中断源。
  5.   *          该参数可以是以下值之一:
  6.   *            [url=home.php?mod=space&uid=2817080]@ARG[/url] USART_IT_CTS:  CTS更改中断
  7.   *            @arg USART_IT_LBD:  LIN Break检测中断
  8.   *            @arg USART_IT_TXE:  传输数据寄存器空中断
  9.   *            @arg USART_IT_TC:   传输完成中断
  10.   *            @arg USART_IT_RXNE: 接收数据寄存器不为空中断
  11.   *            @arg USART_IT_IDLE: 空闲线路检测中断
  12.   *            @arg USART_IT_PE:   奇偶校验错误中断
  13.   *            @arg USART_IT_ERR:  错误中断(帧错误、噪声错误、超限错误)
  14.   * @参数    NewState: 指定USARTx中断的新状态。
  15.   *          取值为:“启用”或“禁用”。
  16.   * @返回值  无
  17.   */
 楼主| 甲虫666 发表于 2023-9-24 01:18 | 显示全部楼层
   USART1_IRQHandlar函数

                1)USART1_IRQHandlar函数是中断处理函数,不能随意定义的,需要遵循MDK的定义。这些函数的声明在启动文件startup_stm32f10x_hd.s文件中,可以在其中找到中断处理函数的名称。
                2)中断处理函数中首先进行状态位判断,这是非常有必要的。因为可能我们在串口的中断设置中,设置了不止一处的中断,但是每个中断进入的中断处理函数都是同一个,这就要求我们要在中断处理函数中通过状态位的判断再进行接下来的操作。

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
 楼主| 甲虫666 发表于 2023-9-24 01:19 | 显示全部楼层
       这里通过这个判断来确定是否接收中断。这个函数的返回值是ITStatus类型,它的定义是:

typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;
 楼主| 甲虫666 发表于 2023-9-24 01:19 | 显示全部楼层
SET代表着1(接收到中断),RESET代表着0(未接收中断)。

        除了中断处理函数的ITStatus需要判断之外,通常发送数据、接收数据之后也需要判断。比如,下面一段程序:

USART_SendData(USART1, USART_RX_BUF[t]);     //向串口1发送数据
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);    //等待发送结束
 楼主| 甲虫666 发表于 2023-9-24 01:19 | 显示全部楼层
即运用while循环,在向串口发送数据之后,通过判断等待发送结束。
        这段程序还有一个问题曾经困扰了我很久很久都没有领悟清楚:
        在中断处理函数中,使用一下程序来接收数据:

        u8 res;
    res= USART_ReceiveData(USART1);
        由于res是一个u8类型的数,若一次性向串口发送很多的数据,串口使用此程序进行接收的时候,一个u8很多装不下。而这个函数中使用的是if判断,如果一次装不下,if判断程序就走一遍,怎么把所有的数据全部接受呢?

        解答:其实我们看一下RXNE标志位引发的中断,当RDR移位寄存器中的数据被转移到USART_DR寄存器中时,也就是有数据可以被接收到的时候,该位置1,引发中断,进入中断处理函数。但是我们在这个中断处理函数中,并没有和其他中断一样做清除中断位的操作,这就导致该位一直是1,不断地进入中断。那么什么时候停止呢?当数据接收完毕了,此时该位清零。
 楼主| 甲虫666 发表于 2023-9-24 01:20 | 显示全部楼层
9 传输数据的函数
        开发板与上位机之间的数据传输可以有多种方法,下面一一介绍:

9.1 发送一个字节
        以USART_SendData(pUSARTx,ch); 函数为基础建立的函数可以向上位机发送一个字节的数据,利用FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG) 读取发送数据寄存器的状态来 等待发送寄存器将数据成功发送。
  1. void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
  2. {
  3.         /* 发送一个字节数据到USART */
  4.         USART_SendData(pUSARTx,ch);
  5.                
  6.         /* 等待发送数据寄存器为空 */
  7.         while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);       
  8. }

您需要登录后才可以回帖 登录 | 注册

本版积分规则

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