1 前言
USART全称universal synchronous asynchronous receiver transmitter通用同步异步接收发送器;速率最高可达4.5Mbits/s,波特率460800;
数据按位顺序发送的串行通信接口简称串口,USART模块是采用串行通信接口最常见的模块,为了方便,就把USART简称为串口;
USART接口通过RX,TX,GND同其他设备相连;当TX引脚被禁止时,该引脚恢复GPIO的配置;当TX引脚使能且未发送数据时,该引脚处于高电平(空闲态);
USART接口的数据字长度可编程,停止位长度可编程;可配置为DMA多缓冲通信;
2 USART的帧格式
串口数据应该遵循USART帧的格式,才能被串口识别;
首先总线需要持续至少一个空闲帧,然后连续发送数据帧,数据帧与数据帧之间有时会有断开帧,断开帧后需要接1-2bit停止位,连接下个数据帧;
断开帧只能为10bit或11bit低电平的帧(CR1_SBK[0]);然后接1或2bit的高电平作为停止位,然后接下一个数据帧;
数据帧的数据字有两种格式,(1)8 bit 数据位;(2)8bit 数据位 + 1 bit 奇偶校验位;
3 USART的寄存器使用
每个USART都有7个自己的寄存器;用来配置该USART的所有功能;
有许多功能诸如硬件流控制,LIN模式,智能卡模式等,由于没用过或是用不上,实在晦涩难懂费时费力,故在此全部跳过;
以下给出了USART作为常用串口收发数据的工作框图,以及相关的寄存器配置;
3.1 工作框图
3.2 相关寄存器配置
1)首先需要配置USART的6个参数:
波特率USART_BRR,字长M,停止位STOP,校验位PCE,PS,PEIE,USART的收发模式TE和RE和硬件流控制CTSIE,CTSE,RTSE;
2)USART提供了8个中断:TXEIE, TCIE, RXNEIE, PEIE, IDLEIE, CTSIE, LBDIE, EIE;
8个中断使能均可以进入USART的中断函数,根据需要配置合适的中断使能位为1;通常为RXNEIE位;
3)然后使能接收器RE和发送器TE;
4)然后使能UE中断;
4 USART的代码示例
4.1 标准库提供的常用USART接口
标准库为所有的外设都提供了封装寄存器的API接口函数,文件名为stm32f10x_peripheral.c;以下为usart外设的常用函数;
//串口USARTx的参数配置初始化函数; void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct); //使能串口,(主要是分频器和输出的设置) void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState); //使能串口中断,(就是那8个中断,均可以进入中断函数) void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState); //都是处理一个字节; void USART_SendData(USART_TypeDef* USARTx, uint16_t Data); uint16_t USART_ReceiveData(USART_TypeDef* USARTx); //读取SR寄存器的状态,SR的状态都是硬件设置的; FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG); //读取SR寄存器和CRx控制寄存器的状态,和上面一个功能相同的; ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT); //修改SR寄存器的状态,单功能通讯用不上; void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
4.2 USART1使用代码
#include "usartDemo.h" u8 USART1_RX_BUF[256]; //接收缓存 u8 USART1_RX_CNT = 0; //接收字节计数 u8 USART1_REV_0D = 0; //收到 u8 USART1_REV_0A = 0; //收到 和 //usart1初始化之后,便可以通过串口读写了; void Usart1_Init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 0-3; //USART1外设中断配置 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); //GPIO初始化 USART1_TX PA9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //输出需要配置速率,输入不需要配置速率; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出,<中文..手册>8.1.11外设的GPIO配置 GPIO_Init(GPIOA, &GPIO_InitStructure); //GPIO初始化 USART1_RX PA10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //USART1初始化 USART_InitStructure.USART_BaudRate = bound; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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; //CR1中的TE,RE USART_Init(USART1, &USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//CR1中的RXNEIE中断 USART_Cmd(USART1, ENABLE); //CR1中的UE } void USART1_Send_Data(u8 *buf,u16 len) { u16 t; for(t=0;t<len;t++) { USART_SendData(USART1,buf[t]); while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); //发送字节完成后,TC硬件置1; } //先读SR,后写DR清除TC位; USART1_RX_CNT = 0; USART1_REV_0D = 0; USART1_REV_0A = 0; } void USART1_IRQHandler(void) { u8 Res; if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { Res =USART_ReceiveData(USART1); //读DR,硬件清0 RXNE位; USART1_RX_BUF[USART1_RX_CNT]=Res; //接收数据 USART1_RX_CNT++; if(Res==0x0d) USART1_REV_0D = 1; if(USART1_REV_0D&&(Res==0x0a)) USART1_REV_0A = 1; } //RXNE为1,读数据的同时又来了数据,那么新的数据丢失;产生溢出错误,读完数据后RXNE为0,但ORE标志还在; //RXNE为1,又来了数据,产生接收溢出错误,置位ORE; if(USART_GetFlagStatus(USART1,USART_FLAG_ORE) == SET) { USART_ReceiveData(USART1); // USART_ClearFlag(USART1,USART_FLAG_ORE);//先读SR,后读DR,可以复位ORE位;应该不用软件清除了; } // USART_ClearFlag(USART1,USART_IT_RXNE); //读DR可以清除RXNE,应该不用软件清除了; } int main(void) { Usart1_Init(460800); while(1) { if(USART1_REV_0A) { USART1_Send_Data(USART1_RX_BUF,USART1_RX_CNT); } } }
4.2.1 在前面代码的基础上不使用串口中断,直接通过SR状态位来判断数据的收发;
将上面代码的usart1初始化代码中CR1的RXNEIE配置行注释掉,然后修改main函数如下即可;
int main(void) { Usart1_Init(460800); while(1) { if ((USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)) { USART1_RX_BUF[USART1_RX_CNT] = USART_ReceiveData(USART1); if(USART1_RX_BUF[USART1_RX_CNT]==0x0a) USART1_REV_0A = 1; USART1_RX_CNT++; } if(USART1_REV_0A) { USART1_REV_0A = 0; for(int i=0;i<USART1_RX_CNT;i++) { USART_SendData(USART1, USART1_RX_BUF[i]); while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET); } USART1_RX_CNT=0; } } }
5 总结
5.1 USART的功能没想到还挺多的,寄存器看起来就有些费时了,很多概念都是新的,不好理解,直接拉低了效率;
于是觉得这样不行,应该用什么看什么,用到再看,学海无涯,精力有限;
另外人家费心费力写好标准库不就是为了帮开发人员省时间吗?了解一下即可,以后没必要深入;
5.2 代码测试,参考网页以及正点原子的例程;
本来是尝试直接在接收函数里将接收到的函数发送回去的;大概因为都是寄存器的操作,并且又是在中断函数里容易被中断打断;
所以不稳定,容易出现寄存器操作到一半被中断打断;读写序列打乱,导致程序丢帧或不能正常运行;
参考了别人的代码,还是要用标志变量来表示接收完成,然后接收和发送分开来执行;这样寄存器操作比较稳定,逻辑也清楚;
5.3 关于NVIC中断优先级管理和串口的DMA传输功能,占个坑;
5.4 参考网页:https://www.cnblogs.com/pertor/p/9488446.html
5.5 本文代码github:https://github.com/caesura-k/stm32f1_usart