Stm32 第二十五~二十七讲:串口通信


USART框图



波特率计算方法

波特率计算方法
波特率

常用的串口相关寄存器

  • USART_SR:状态寄存器
  • USART_DR:数据寄存器
  • USART_BRR:波特率寄存器

状态寄存器USART_SR

串口_SR

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

相关库函数:

FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx,uint16_t USART_FLAG);
//获取状态标志位

数据寄存器USART_DR

串口_DR
相关库函数:

void USART_SendData(USART_TypeDef USARTx, uint16_t Data);
//发送数据到串口
uint16_t USART_ReceiveData(USART_TypeDef
USARTx);
//接收数据

波特率寄存器USART_BRR

串口_BRR
相关库函数:

void USART_Init(USART_TypeDef USARTx, USART_InitTypeDef USART_InitStruct);
//串口初始化:波特率、数据字长、奇偶检验、硬件流控以及收发使能

时钟使能寄存器APB2ENR

串口1在APB2ENR的第14位。除了串口1的时钟使能在APB2ENR,其它串口的时钟使能位都在APB1ENR。

时钟复位寄存器APB2RSTR

当外设出现异常的时候可以通过复位寄存器里面的对应位设置,实现该外设的复位,然后重新配置这个外设达到其重新工作的目的。
串口1的复位是通过配置APB2RSTR寄存器的第14位来实现的,APB2RSTR寄存器的各位描述如下:
串口_RSTR
从上图可以看出串口1的复位设置位在APB2RSTR的第14位,通过向该位写1复位串口1,写0结束复位。其它串口的复位设置位在APB1RSTR。

串口控制寄存器USART_CR1

STM32的每个串口都有3个控制寄存器USART_CR1~3,串口的很多配置都是通过这3个寄存器来设置的。这里只要用到USART_CR1就可以实现我们的功能了。USART_CR1寄存器的各位描述如下:
串口_CR1

  • UE为串口使能位,通过把该位置为1来使能串口。
  • M为字长选择位,当该位为0的时候设置串口为8个字长外加n个停止位,停止位的个数n是根据USART_CR2寄存器的[13:12]位设置来决定的,默认为0。
  • PCE为校验使能位,设置为0则禁止校验,否则使能校验。
  • PS为校验位选择,设置为0则为偶校验,否则为奇校验。
  • TXIE为发送缓冲区空中断使能位,设置该位为1,当USART_SR中的TXE位为1时,将产生串口中断。
  • TCIE为发送完成中断使能位,设置该位为1,当USART_SR中的TC位为1时,将产生串口中断。
  • RXNEIE为接收缓冲区非空中断使能,设置该位为1,当USART_SR中的ORE或RXNE位为1时,将产生串口中断。
  • TE为发送使能位,设置为1,将开启串口的发送功能。
  • RE为接收使能位,设置为1,将开启串口的接收功能。

串口操作相关库函数

(省略入口参数)

  • void USART_Init():串口初始化:波特率、数据字长、奇偶检验、硬件流控以及收发使能
  • void USART_Cmd():使能串口
  • void USART_ITConfig():使能相关中断
  • void USART_SendData:发送数据到串口,DR寄存器
  • uint16_t USART_ReceiveData():接收数据,从DR读取接收到的数据
  • FlagStatus USART_GetFlagStatus():获取状态标志位
  • void USART_ClearFlag():清除状态标志位
  • ITStatus USART_GetITStatus():获取中断状态标志位
  • void USART_ClearITPendingBit():清除中断状态标志位

串口配置的一般步骤

  1. 串口时钟使能,GPIO时钟使能。

    RCC_APB2PeriphClockCmd();

  2. 串口复位(不是必须的)。

    USART_DeInit();

  3. GPIO端口模式设置。

    GPIO_Init();

  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();

usart文件夹介绍

usart文件夹内包含了usart.c和usart.h两个文件,这两个文件用于串口的初始化和中断接收 ,这里只是针对串口1,如果要用串口2或其他的串口,只要对代码稍作修改。
usart.c里面包含了2个函数,一个是void USART_IRQHandler(void);另外一个是void uart_init(u32 bound);里面还有一段对串口printf的支持代码,这段代码不可修改,去掉之后软件编译虽然不会报错,但是硬件上STM32是无法启动的。

printf函数

这段代码加入后便可以通过printf函数向串口发送我们需要的内容,方便开发过程中查看代码执行以及一些变量值。这段代码为(不需要看明白):

  //加入以下代码,支持printf函数,而不需要选择use MicroLIB      
  #if 1
  #pragma import(__use_no_semihosting)             
  //标准库需要的支持函数                 
  struct __FILE 
  { 
      int handle; 

  }; 

  FILE __stdout;       
  //定义_sys_exit()以避免使用半主机模式    
  void _sys_exit(int x) 
  { 
      x = x; 
  } 
  //重定义fputc函数 
  int fputc(int ch, FILE *f)
  {      
      while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
      USART1->DR = (u8) ch;      
      return ch;
  }
  #endif 

uart_init()函数

这个函数是串口1初始化函数,这样我们以后再用串口1函数的时候就不需要再从头写初始化函数了,另外如果需要其它串口也可以对此函数稍作修改即可。

by库函数

  void uart_init(u32 bound)
  {
      //GPIO端口设置
      GPIO_InitTypeDef GPIO_InitStructure;
      USART_InitTypeDef USART_InitStructure;
      NVIC_InitTypeDef NVIC_InitStructure;

      RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);    
      //使能USART1,GPIOA时钟

      //USART1_TX   GPIOA.9
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽输出
      GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9

      //USART1_RX      GPIOA.10初始化
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
      GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

      //Usart1 NVIC 配置
      NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;        //子优先级3
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //IRQ通道使能
      NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器

      //USART 初始化设置

      USART_InitStructure.USART_BaudRate = bound;//串口波特率
      USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
      USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
      USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
      USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
      USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;    //收发模式

    USART_Init(USART1, &USART_InitStructure); //初始化串口1
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
    USART_Cmd(USART1, ENABLE);                    //使能串口1 

  }

by寄存器

  //初始化IO 串口1
  //pclk2:PCLK2时钟频率(Mhz)
  //bound:波特率 
  void uart_init(u32 pclk2,u32 bound)
  {       
      float temp;
      u16 mantissa;
      u16 fraction;       
      temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV
      mantissa=temp;                 //得到整数部分
      fraction=(temp-mantissa)*16; //得到小数部分     
      mantissa<<=4;
      mantissa+=fraction; 
      RCC->APB2ENR|=1<<2;   //使能PORTA口时钟  
      RCC->APB2ENR|=1<<14;  //使能串口时钟 
      GPIOA->CRH&=0XFFFFF00F;//IO状态设置
      GPIOA->CRH|=0X000008B0;//IO状态设置 
      RCC->APB2RSTR|=1<<14;   //复位串口1
      RCC->APB2RSTR&=~(1<<14);//停止复位              
      //波特率设置
       USART1->BRR=mantissa; // 波特率设置     
      USART1->CR1|=0X200C;  //1位停止,无校验位.
  #if EN_USART1_RX          //如果使能了接收
      //使能接收中断 
      USART1->CR1|=1<<5;    //接收缓冲区非空中断使能            
      MY_NVIC_Init(3,3,USART1_IRQn,2);//组2,最低优先级 
  #endif
  }

USART1_IROHandler函数

此函数是串口1的中断响应函数,当串口1发生了相应的中断后,就会跳到该函数执行。
中断相应函数的名字是不能随便定义的,一般都遵循MDK定义的函数名。这些函数名字在启动文件startup_stm32f10x_hd.s文件中可以找到。
这里设计了一个接受协议:通过这个函数,配合一个数组USART_RX_BUF[],一个接受状态寄存器USART_RX_STA(此寄存器其实就是一个全局变量,为作者自行添加。它起到类似寄存器的作用,此处称之为寄存器)实现对串口数据的接受管理。USART_RX_BUF的大小由USART_REC_LEN定义,也就是一次接受的数据最大不能超过USART_REC_LEN个字节。USART_RX_STA是一个接收状态寄存器,其各位的定义如下表:
串口_STA
设计思路如下
当接收到上位机发来的数据,将数据保存在USART_RX_BUF中,同时在接收状态寄存器USART_RX_STA中计数接收到的有效数据个数,当收到回车(回车的表示由两个字节组成:0X0D和0X0A)的第一个字节0X0D时,计数器将不再增加,等待0X0A的到来,而如果0X0A没到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到0X0A,则标记USART_RX_STA的第15位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到0X0D,那么在接收数据超过USART_REC_LEN的时候,则会丢弃前面的数据,重新接收。

中断函数相应代码如下:

by库函数

  #if EN_USART1_RX   //如果使能了接收
  //串口1中断服务程序
  //注意,读取USARTx->SR能避免莫名其妙的错误       
  u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
  //接收状态
  //bit15,    接收完成标志
  //bit14,    接收到0x0d
  //bit13~0,    接收到的有效字节数目
  u16 USART_RX_STA=0;       //接收状态标记      

  void USART1_IRQHandler(void)                    //串口1中断服务程序
  {
      u8 Res;
  #if SYSTEM_SUPPORT_OS         //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
      OSIntEnter();    
  #endif
      if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  
          //接收中断(接收到的数据必须是0x0d 0x0a结尾)
          {
          Res =USART_ReceiveData(USART1);    //读取接收到的数据

          if((USART_RX_STA&0x8000)==0)//接收未完成
              {
              if(USART_RX_STA&0x4000)//接收到了0x0d
                  {
                  if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
                  else USART_RX_STA|=0x8000;    //接收完成了 
                  }
              else //还没收到0X0D
                  {    
                  if(Res==0x0d)USART_RX_STA|=0x4000;
                  else
                      {
                      USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
                      USART_RX_STA++;
                      if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
                      //接收数据错误,重新开始接收      
                      }         
                  }
              }            
       } 
  #if SYSTEM_SUPPORT_OS     //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
      OSIntExit();                                               
  #endif
  } 

by寄存器

  #if EN_USART1_RX   //如果使能了接收
  //串口1中断服务程序
  //注意,读取USARTx->SR能避免莫名其妙的错误       
  u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
  //接收状态
  //bit15,    接收完成标志
  //bit14,    接收到0x0d
  //bit13~0,    接收到的有效字节数目
  u16 USART_RX_STA=0;       //接收状态标记      

  void USART1_IRQHandler(void)
  {
      u8 res;    
  #if SYSTEM_SUPPORT_OS         //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
      OSIntEnter();    
  #endif
      if(USART1->SR&(1<<5))    //接收到数据
      {     
          res=USART1->DR; 
          if((USART_RX_STA&0x8000)==0)//接收未完成
          {
              if(USART_RX_STA&0x4000)//接收到了0x0d
              {
                  if(res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
                  else USART_RX_STA|=0x8000;    //接收完成了 
              }else //还没收到0X0D
              {    
                  if(res==0x0d)USART_RX_STA|=0x4000;
                  else
                  {
                      USART_RX_BUF[USART_RX_STA&0X3FFF]=res;
                      USART_RX_STA++;
                      if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;
                      //接收数据错误,重新开始接收      
                  }         
              }
          }                                                    
      }
  #if SYSTEM_SUPPORT_OS     //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
      OSIntExit();                                               
  #endif
  } 

其中EN_USART1_RX和USART_REC_LEN都是在usart.h里面定义的:

  #define USART_REC_LEN              200      //定义最大接收字节数 200
  #define EN_USART1_RX             1        //使能(1)/禁止(0)串口1接收

当需要使用串口接收的时候,只要在usart.h里设置EN_USART1_RX为1即可;不使用的时候,设置为0即可,这样可以省出部分sram和flash;默认EN_USART1_RX是1。

库函数和寄存器代码的两处不同

第一处

USART_GetITStatus(USART1, USART_IT_RXNE) != RESET //by库函数
USART1->SR&(1<<5) //by寄存器

这两处意思都是读取状态寄存器RXNE(第五位)的电平,当接收到数据后,RXNE会为高电平,通过这样来判断串口是否接收到了数据。RESET就是低电平的意思。

第二处

Res =USART_ReceiveData(USART1); //by库函数
res=USART1->DR; //by寄存器

这两处的意思是把接收到的数据赋值给res(Res)变量,DR为数据寄存器,它包含了发送的和接收的数据,兼具读和写的功能。

一个简单的串口通信代码(by库函数)

因为有函数名字重复,所以需要把System文件夹中的usart.c先删除

main.c

  #include "stm32f10x.h"


  void My_USART1_Init(void)
  {
      GPIO_InitTypeDef GPIO_InitStructure;
      USART_InitTypeDef USART_InitStructure;
      NVIC_InitTypeDef NVIC_InitStructure;

      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA时钟
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能串口时钟

      GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
      GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;//PA.9
      GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
      GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化PA.9

      GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;//浮空输入
      GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//PA.10
      GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化PA/10

      USART_InitStructure.USART_BaudRate=115200;//波特率115200
      USART_InitStructure.USART_WordLength=USART_WordLength_8b;//字长8bits
      USART_InitStructure.USART_StopBits=USART_StopBits_1;//一个停止位
      USART_InitStructure.USART_Parity=USART_Parity_No;//无奇偶校验
      USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;
      USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
      //不使用硬件流控制
      USART_Init(USART1,&USART_InitStructure);//串口1初始化

      USART_Cmd(USART1,ENABLE);//串口使能

      USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//打开接收中断

      NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;//串口1通道
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//抢占优先级为1
      NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;//响应优先级为1
      NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//开启中断
      NVIC_Init(&NVIC_InitStructure);//中断初始化

  }

  void USART1_IRQHandler(void)
  {
      u8 res;
      if(USART_GetITStatus(USART1,USART_IT_RXNE))//判断是否触发了串口接收中断
      {
          res = USART_ReceiveData(USART1);
          USART_SendData(USART1,res);//串口发送数据

      }
  }

  int main(void)
  {
       NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC中断优先级分组
      My_USART1_Init();//串口初始化函数
      while(1);
  }

串口通信实验

by库函数

main.c

  #include "led.h"
  #include "sys.h"
  #include "stm32f10x.h"
  #include "delay.h"
  #include "usart.h"

  int main(void)
  {
      u8 t;
      u8 len;
      u16 times=0;
      delay_init();
      NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置NVIC中断分组为2
      uart_init(115200);//串口1初始化,并设置波特率为115200
      LED_Init();
      while(1)
      {
          if(USART_RX_STA&0x8000)//判断是否接受完成
          {
              len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
              printf("\r\n你发送的消息为\r\n\r\n");
              for(t=0;t<len;t++)
              {
                  USART_SendData(USART1,USART_RX_BUF[t]); //向串口1发送数据
                  while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);//等待发送完成
              }
              printf("\r\n\r\n");
              USART_RX_STA=0;
          }else
          {
              times++;
              if(times%5000==0)
              {
                  printf("\r\n战舰STM32开发板 串口实验\r\n");
                  printf("串口实验库函数版本\r\n\r\n");
              }
              if(times%200==0) printf("请输入数据,以回车键结束\n");
              if(times%30==0) LED0=!LED0; //LED0闪烁,提示系统正在运行
              delay_ms(10);
          }
      }

  }

led.h

  #ifndef __LED_H
  #define __LED_H
  #include "sys.h"

  #define LED0 PBout(5)
  #define LED1 PEout(5)

  void LED_Init(void);

  #endif

led.c

  #include "led.h"

  void LED_Init()
  {
      GPIO_InitTypeDef GPIO_InitStructure;

      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE,ENABLE);

      GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
      GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
      GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
      GPIO_Init(GPIOB,&GPIO_InitStructure);

      GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
      GPIO_Init(GPIOE,&GPIO_InitStructure);    
  }

by寄存器

main.c

  #include "stm32f10x.h"
  #include "delay.h"
  #include "sys.h"
  #include "led.h"
  #include "usart.h"

  int main(void)
  {
      u16 t;
      u16 len;
      u16 times=0;
      Stm32_Clock_Init(9);
      delay_init(72);
      uart_init(72,115200);//串口1初始化,并设置波特率为115200
      LED_Init();
      while(1)
      {
          if(USART_RX_STA&0x8000)//判断是否接受完成
          {
              len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
              printf("\r\n你发送的消息为\r\n\r\n");
              for(t=0;t<len;t++)
              {
                  USART1->DR=USART_RX_BUF[t]; //向串口1发送数据
                  while((USART1->SR&0X40)==0);//等待发送完成
              }
              printf("\r\n\r\n");
              USART_RX_STA=0;
          }else
          {
              times++;
              if(times%5000==0)
              {
                  printf("\r\n战舰STM32开发板 串口实验\r\n");
                  printf("串口实验寄存器版本\r\n\r\n");
              }
              if(times%200==0) printf("请输入数据,以回车键结束\n");
              if(times%30==0) LED0=!LED0; //LED0闪烁,提示系统正在运行
              delay_ms(10);
          }
      }

  }

led.h

  #ifndef __LED_H
  #define __LED_H
  #include "sys.h"

  #define LED0 PBout(5)
  #define LED1 PEout(5)

  void LED_Init(void);

  #endif

led.c

  #include "led.h"
  #include "sys.h"
  void LED_Init()
  {
      //注意:寄存器不能直接等号!!需要加|或者&!!
      RCC->APB2ENR|=1<<3;//PB使能
      RCC->APB2ENR|=1<<6;//PE使能

      GPIOB->CRL&=0XFF0FFFFF;
      GPIOB->CRL|=0X00300000;
      LED0=1;
      GPIOE->CRL&=0XFF0FFFFF;
      GPIOE->CRL|=0X00300000;
      LED1=1;
  }

文章作者: Mat Jenin
文章链接: http://matjenin.xyz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mat Jenin !
  目录