STM32
直播中

张龙祥

9年用户 1314经验值
擅长:连接器
私信 关注
[问答]

如何利用STM32的USART1去实现数据的收/发呢

状态寄存器和数据寄存器的只要作用是什么?
如何利用STM32的USART1去实现数据的收/发呢?

回帖(2)

李韵鹤

2021-11-17 10:09:28
  前言
  利用STM32的USART1,实现数据的收/发。本文参照文档为正点原子—库函数版本开发指南、STM32中文参考手册。
  工具/参考文档
  使用的编译器:KEIL 4 MDK
  stm32 USART
  串口是MCU重要的外部接口,同时也是软件开发过程中常用的调试手段。STM32同样也具有串口,本实验使用的芯片中一共拥有5个串口。
  寄存器
  本次实验中使用的串口1(USART1),接下来就着重介绍一下串口的几个重要的寄存器。此内容在STM32中文参考手册的P540.
  本次实验主要用到状态寄存器(USART_SR)和数据寄存器(USART_DR)
  状态寄存器(USART_SR)
  状态寄存器的作用主要是,检测串口的状态。
  这里我们主要注意第5、6位RXNE和TC。
  RXNE(读数据寄存器非空),当前位置1,表明已经有数据被接收了,并且可以读出来。这时候我们可以读出USART_DR中的数据,也可向USART_SR寄存器中的RXNE位写入0.
  TC(发送完成),当改位被置1的时候,说明USART_DR中的数据已经发送完成,可进行下一个数据的发送。如果此位设置了中断,则会产生中断。该位也有两种清零方式1、读USART_DR,写USART_DR。2、直接向改位写0。
  这里说一下读取串口状态的函数为:
  FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
  着个函数的第二个入口参数,标志着我们要查看串口的那种状态。如要查看上面这两位的状态。
  USART_GetFlagStatus(USART1,USART_FLAG_RXNE);
  USART_GetFlagStatus(USART1,USART_FLAG_TC);
  这些标识在MDK中都有定义:
  #define USART_IT_PE ((uint16_t)0x0028)
  #define USART_IT_TXE ((uint16_t)0x0727)
  #define USART_IT_TC ((uint16_t)0x0626)
  #define USART_IT_RXNE ((uint16_t)0x0525)
  #define USART_IT_IDLE ((uint16_t)0x0424)
  #define USART_IT_LBD ((uint16_t)0x0846)
  #define USART_IT_CTS ((uint16_t)0x096A)
  #define USART_IT_ERR ((uint16_t)0x0060)
  #define USART_IT_ORE ((uint16_t)0x0360)
  #define USART_IT_NE ((uint16_t)0x0260)
  #define USART_IT_FE ((uint16_t)0x0160)
  
  
  数据寄存器(USART_DR)
  
  
  串口设置流程
  串口设置的一般步骤可分为以下几步:
  串口时钟使能,GPIO 时钟使能
  串口复位
  GPIO 端口模式设置
  串口参数初始化
  使能串口
  1、串口时钟使能,GPIO使能
  通过STM32中文参考手册—2.1 系统构架(P26)图2可以知道USART1挂载在APB2总线上。所以使能函数为:
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
  串口1的引脚为RX-PA9,TX-PA10
  所以GPIO端口时钟使能的是GPIOA。
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
  2、串口复位
  一般在系统刚开始配置外设的时候,都会先执行复位该外设的操作。复位的是在函数 USART_DeInit()中完成:
  串口复位函数为:
  void USART_DeInit(USART_TypeDef* USARTx);//串口复位
  USART_DeInit(USART1);
  3、GPIO端口模式设置
  对于复用功能下的 GPIO 模式怎么判定,这个需要查看《中文参考手册 V10》P110 的表格“8.1.11 外设的 GPIO 配置”。
  
  GPIO配置代码
  GPIO_InitTypeDef GPIO_InitStructure;
  //在《STM32 中文参考手册 V10》的 P110“8.1.11 外设的 GPIO 配置”中有讲解
  //USART1_TX PA.9 复用推挽输出
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//引脚pa.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速率50Mhz
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
  GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化GPIOA.9发送端
  //USART1_RX PA.10 浮空输入
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//引脚pa.10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化GPIOA.10接收端
  4、串口参数初始化
  串口参数初始化函数:
  void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
  第一个入口参数是指定初始化的串口标号,这里选择USART1。
  第二个入口参数是一个USART_InitTypeDef类型指针,这个结构体的成员变量用来设置串口的参数。
  USART_InitTypeDef结构体:
  typedef struct{
  uint32_t USART_BaudRate;
  uint16_t USART_WordLength;
  uint16_t USART_StopBits;
  uint16_t USART_Parity;
  uint16_t USART_Mode;
  uint16_t USART_HardwareFlowControl;
  } USART_InitTypeDef;
  代码实现串口参数设置:
  USART_InitTypeDef USART_InitStructure;
  //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
  5、使能串口
  使能串口的函数为:
  USART_Cmd(USART1,ENABLE);//使能串口
  串口数据的接收与发送
  STM32 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了 TDR 和 RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也是存在该寄存器内。
  STM32 库函数操作 USART_DR 寄存器发送数据的函数是:
  void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
  通过该函数向串口寄存器 USART_DR 写入一个数据。
  STM32 库函数操作 USART_DR 寄存器读取串口接收到的数据的函数是:
  uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
  通过该函数可以读取串口接受到的数据。
  stm32 中断介绍
  因为要完成串口接收数据的功能,就需要通过中断来检测上位机是否发送数据。然后在进行数据的接收和后续的反应。所以这里简要说明以下STM32的中断如何设置。详细设置过程在STM32中文参考手册第9章–P130/STM32开发指南-库函数版本—4.5节–P120
  STM32有60个中断,所以不会像C51那样去使用,要使用STM32的中断首先需要设置中断分组。中断分组在一个工程中只设置一次。STM32将中断分为5组,组0~4.该分组设置由SCB-》ALRCR寄存器的bit0 ~ 8来定义的。分配关系如表:
  [tr]组AIRCR[10:8]bit[7:4]分配情况分配结果[/tr]01110:40 位抢占优先级,4 位响应优先级
  11101:31 位抢占优先级,3 位响应优先级
  21012:22 位抢占优先级,2 位响应优先级
  31003:13 位抢占优先级,1 位响应优先级
  40114:04 位抢占优先级,0 位响应优先级
  这里介绍以下如何判断优先级高低:
  第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;
  第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。
  设置中断分组函数:
  void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
  NVIC_PriorityGroup参数:
  NVIC_PriorityGroup_0 //分组0
  NVIC_PriorityGroup_1 //1
  NVIC_PriorityGroup_2 //2
  NVIC_PriorityGroup_3 //3
  NVIC_PriorityGroup_4 //4
  下面介绍一下比较重要的一个函数,中断初始化函数NVIC_Init,其函数声明为:
  void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
  NVIC_InitTypeDef是一个结构体,其成员变量有:
  typedef struct
  {
  uint8_t NVIC_IRQChannel; //对应中断通道,也就是定义初始化那个中断
  uint8_t NVIC_IRQChannelPreemptionPriority; //设置抢占优先级
  uint8_t NVIC_IRQChannelSubPriority; //设置子优先
  FunctionalState NVIC_IRQChannelCmd; //该中断是否使能
  }NVIC_InitTypeDef;
  如要使能串口1中断,设置抢占优先级为3,子优先为3,则代码为:
  NVIC_InitTypeDef NVIC_InitStructure;
  //中断配置 ------中断管理函数章节 4.5 有讲解中断管理相关的知识--P121
  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);//中断优先级设置
  
举报

宋瑞雪

2021-11-17 10:09:34
1. 系统运行开始的时候设置中断分组。确定组号,也就是确定抢占优先级和子优先级的分配位数。调用函数为 NVIC_PriorityGroupConfig();
  2. 设置所用到的中断的中断优先级别。对每个中断调用函数为 NVIC_Init();
  代码
  uart.c文件完整代码(带注释):
  #include “usart.h”
  #include “sys.h”
  //加入以下代码,支持print函数,而不需要选择use MicroLIB
  #if 1
  #pragma import(__use_no_semihosting)
  //标准库需要的支持函数
  struct __FILE
  {
  int handle;
  };
  FILE __stdout;
  //定义__sys_exit()以避免使用半主机模式
  _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
  /*使用microLib的方法*/
  /*
  int fputc(int ch,FILE *f){
  while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);
  USART_SendData(USART1,(uint8_t)ch);
  return ch;
  }
  int GetKey (void) {
  while (!(USART1-》SR & USART_FLAG_RXNE));
  return ((int)(USART1-》DR & 0x1FF));
  }
  */
  #if EN_USART1_RX //如果使能了接收
  u8 USART_RX_BUF[USART_REC_LEN];//接收缓冲区,最大USART_REC_LEN个字节。
  u16 USART_RX_STA = 0;//接收状态标记
  //串口状态寄存器SR在中文参考手册---P540
  //对用状态的宏定义在stm32f1ox_uart.h中
  //串口发送单个字节数据
  void uart_send_tx(u8 ch){
  USART1-》DR = (u16)ch;
  while(!(USART1-》SR&USART_FLAG_TXE));
  //判断这个字节是否移动到移位寄存器中,也就是判断字节数据有没有发送完成
  }
  //串口发送多个字节数据
  void uart_send_tx_s(u8 *pString){
  while(*pString != ‘’){
  uart_send_tx(*pString++);
  }
  }
  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|RCC_APB2Periph_AFIO,ENABLE);
  //在《STM32 中文参考手册 V10》的 P110“8.1.11 外设的 GPIO 配置”中有讲解
  //USART1_TX PA.9 复用推挽输出
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//引脚pa.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速率50Mhz
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
  GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化GPIOA.9发送端
  //USART1_RX PA.10 浮空输入
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//引脚pa.10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
  GPIO_Init(GPIOA,&GPIO_InitStructure);//初始化GPIOA.10接收端
  //中断配置 ------中断管理函数章节 4.5 有讲解中断管理相关的知识--P121
  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);//中断优先级设置
  //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);//使能串口
  }
  /*******************************************************************************
  void USART1_IRQHandler(void)函数是串口 1 的中断响应函数,当串口 1 发生了相应
  的中断后,就会跳到该函数执行。中断相应函数的名字是不能随便定义的,一般我们都遵
  循 MDK 定义的函数名。这些函数名字在启动文件 startup_stm32f10x_hd.s 文件中可以找到。
  **********************************************************************************/
  void USART1_IRQHandler(void){ //串口1中断服务程序
  u8 Res;
  if(USART_GetFlagStatus(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;//接收完成
  //printf(“接收到数据!!”);
  }
  else//还没接收到0x0d
  {
  if(Res == 0x0d) //判断接收到到的数据是否为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;//接收数据错误重新开始接收
  }
  }
  }
  }
  }
  #endif
  usart.h文件:
  main#ifndef __USART_H
  #define __USART_H
  #include “sys.h”
  #include “stdio.h”
  #define USART_REC_LEN 200 //接收最大字节数为200个字节
  #define EN_USART1_RX 1 //使能-1/禁止-0
  extern u8 USART_RX_BUF[USART_REC_LEN];
  extern u16 USART_RX_STA; //接收状态标志
  void uart_init(u32 bound);//端口配置函数
  void uart_send_tx(u8 ch);
  void uart_send_tx_s(u8 *pString);//发送字符串函数
  #endif
  main.c文件
  #include “sys.h”
  #include “delay.h”
  #include “usart.h”
  #include “led.h”
  int main(void )
  {
  u8 t = 0;
  u8 len; //保存接收数据的长度
  u16 times=0;
  delay_init(); //延时函数初始化
  NVIC_Configuration(); //设置中断分组,在sys.c文件中,如果不设置的的化默认分组为2
  uart_init(9600); //串口初始化,内含中断的初始化
  LED_Init();
  while(1){
  if(USART_RX_STA&0X8000){ //通过自定义寄存的最高位,判断是否成功收到数据
  len = USART_RX_STA&0X3FFF; //获得此次接收数据的长度,USART_RX_STA低14位是记录数据长度的。
  uart_send_tx_s(“发送成功,发送的数据为:rn”);
  for(t = 0;t《len;t++){
  USART_SendData(USART1,USART_RX_BUF[t]); //通过库函数发送数据
  while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//通过获取usart1状态寄存器的第6位确定数据有没有发送成功
  }
  printf(“rn”); //换行
  USART_RX_STA=0; //发送完成,将状态归零,准备下一次接收数据
  }
  else{
  times++;
  if(times%100==0) //1秒发送一次消息,
  uart_send_tx_s(“暂无数据发送!rn”);
  if(times%30==0) //0.3秒进入一次,对times求余数
  LED1 = ~LED1; //闪烁led表示系统正在运行
  delay_ms(10);
  }
  }
  }
  /*************************************************************、
  stm32串口程序在库函数开发指南的第179页
  在uart.c文件中的特殊定义:
  一个接收状态寄存器 USART_RX_STA(此寄存器其实就是一个全局
  变量,由作者自行添加。由于它起到类似寄存器的功能,这里暂且称之为寄存器)实现对
  串口数据的接收管理。USART_RX_BUF 的大小由 USART_REC_LEN 定义,也就是一次接
  收的数据最大不能超过 USART_REC_LEN 个字节。
  USART_RX_STA:
  bit15, 接收完成标志
  bit14, 接收到0x0d
  bit13~0, 接收到的有效字节数目
  *************************************************************/
  调试过程
  
  完整工程
  完整工程可直接编译:
  程序参考正点原子编写,此文章只作为本人笔记。如对你有帮助,那真是再好不过。今天就到这里goodbye!!
举报

更多回帖

发帖
×
20
完善资料,
赚取积分