单片机串口解析数据的几种方式
UART串口是嵌入式开发常见的一种通信方式,但还是有不少人不知道怎么使用串口。今天就来围绕串口,简单分享几点内容:串口接收方式处理接收数据通信协议解析
串口接收方式串口接收(通信另一端)的数据,常见的方式:轮询(查询)接收寄存器
中断接收数据
轮询,就是间隔一定时间(一般ms,甚至us)去查询一下接收寄存器是否有数据,如果有数据,就处理接收到的数据。
中断,平时没有数据接收时,CPU干自己的事。当有接收数据时,UART串口控制器会响应中断,通知CPU有事干了。
轮询方式,大家想过有哪些弊端吗?效率低:CPU大部分时间都是去做查询的工作;
响应不实时:如果短时间内有多个接收数据,CPU正在处理一件相对耗时的事情(比如:发送一个数据包),没来得及查询接收到的数据,此时,数据就可能丢失。(特别是早些年串口没有FIFO功能的时候)
所以,不管是UART串口,还是I2C、 SPI、 CAN等串行通信,用的最多,最常见的还是中断接收,很少有用轮询的方式。
我之前维护一个老代码(坑),CLI串口用轮询方式,出现丢数据、溢出错误等众多问题,让我还加了好几个班。。。 处理接收数据中断有数据来了,大家怎么处理接收到的数据?
我见过有些小项目,直接在中断函数里面做一些应用的情况。比如:串口中断接收一个传感器发过来的数据,显示数据并做一些响应的动作。
中断函数,代码能少尽少,耗时能少尽少,不能处理太多耗时的复杂的逻辑、应用等。
中断有数据来了,一般是通过FIFO方式处理。
1.简单的数组接收、应用解析并处理比如:static uint8_t gRxCnt = 0;static uint8_t gRxBuf;
void USART1_IRQHandler(void){//...gDgus_RxBuf = (uint8_t)USART_ReceiveData(USART1);gRxCnt++;//...}
void App(void){//...if(0 < gRxCnt){ //拷贝接收到的数据 gRxCnt = 0; //解析接收数据并处理}}
2.中断函数接收一帧完整数据再处理
比如:
void USART1_IRQHandler(void){static uint8_t RxCnt = 0; //计数值static uint8_t RxNum = 0; //数量
if((USART1->SR & USART_FLAG_RXNE) == USART_FLAG_RXNE){ gDgus_RxBuf = (uint8_t)USART_ReceiveData(USART1); RxCnt++;
/* 判断帧头 */ if(gDgus_RxBuf != DGUS_FRAME_HEAD1) //接收到帧头1 { RxCnt = 0; return; } if((2 == RxCnt) && (gDgus_RxBuf != DGUS_FRAME_HEAD2)) { RxCnt = 0; return; }
/* 确定一帧数据长度 */ if(RxCnt == 3) { RxNum = gDgus_RxBuf + 3; }
/* 接收完一帧数据 */ if((6 <= RxCnt) && (RxNum <= RxCnt)) { RxCnt = 0; OSMboxPost(EventMBox_Touch, gDgus_RxBuf);//发送消息邮箱(执行触控操作) }}}中断函数解析完一帧数据,可以通过标志位通知应用(裸机时),也可以通过消息队列、邮箱等方式发送到应用(RTOS时)。 3.RTOS队列、邮箱接收比如:void DEBUG_COM_IRQHandler(void){static uint8_t Data;
if(USART_GetITStatus(DEBUG_COM, USART_IT_RXNE) != RESET){ Data = USART_ReceiveData(DEBUG_COM); CLI_RcvDateFromISR(Data); //下面把这个函数分离出来了}}
void CLI_RcvDateFromISR(uint8_t RcvData){static portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
if(xCLIRcvQueue != NULL){ xQueueSendFromISR(xCLIRcvQueue, &RcvData, &xHigherPriorityTaskWoken);}}中断来一字节数据,就通过消息队列发送一个字节数据,如果没有及时出来这个数据,也是存储在队列中。
通信协议解析像上面第2种,简单通信协议,项目相对较小的情况下,可以直接在中断函数里面处理。
但是,如果项目相对较大、复杂一点,协议也先对复杂一点,上面第2种在函数内部出来方式就不可取。
1.裸机环境裸机的情况下,建议用第一种:中断数组缓存数据(FIFO),应用解析通信协议。
2.RTOS环境RTOS情况下,建议用第三种方式:消息队列、邮箱等方式接收数据,然后发送(通知)应用解析协议。
当然,以上说的都只是常见的方式,具体还需要结合你项目实际情况。
同时,其它类似I2C、CAN等通信,如有协议解析,也是类似。
比如之前给大家分享的MavLink,我就用CAN实现过:void CAN_RX_IRQHandler(void){static CanRxMsg RxMessage;static MAVRCV_QUEUE_TypeDef MAVRcvQueue_Union;
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); //拷贝长度、 数据MAVRcvQueue_Union.MAVRcvStruct.MAVLink_Len = RxMessage.DLC;memcpy(&MAVRcvQueue_Union.MAVRcvStruct.MAVLink_Buf, &RxMessage.Data, RxMessage.DLC);
MAVLink_RcvDateFromISR(&MAVRcvQueue_Union.MAVLinkRcv_Queue);}
最后,以上内容,仅提供思路,代码不一定适合项目。 空闲中断+DMA的方式可以尝试下,个人感觉还是比较好用的 在开始接收数据之前,需要对单片机的串口进行初始化。这包括设置波特率、数据位、停止位和奇偶校验等参数。这些参数必须与发送端的配置相匹配,以确保数据的正确传输 串口通信通常以一个起始位开始,这是数据传输的标志。单片机需要检测到这个起始位,然后才能开始接收数据
一旦检测到起始位,单片机就可以开始接收数据。通过一个循环实现的,循环中不断地从串口读取数据,直到接收到所有的数据位
将数据解析为特定的命令或消息,或者将数据存储在内存中。数据处理的方式取决于具体的应用需求和协议规范
:数据接收完成后,单片机需要检测到一帧数据的结束标志
如果在接收过程中检测到任何错误,单片机需要进行适当的错误处理
数据接收完成后,单片机需要检测判断 稍复杂,需要配置中断优先级、写 ISR
页:
[1]