单片机采用轮询方式读取串口能否避免数据丢失?
单片机串口轮询方式的可行性分析与实现方法
核心结论
“只要有其他任务运行,轮询接收串口数据就无法实现”这种说法不正确。轮询方式能否正常工作,核心是能否保证在串口硬件缓冲区被覆盖前(即下一个字节到来前)完成数据读取,而非完全禁止其他任务运行。
纯轮询在多任务环境下的风险根源
对于仅配备1字节硬件缓冲区的UART:
- 115200波特率下,单个字节传输耗时约70us,必须在该时间窗内读取已接收的字节,否则新字节会覆盖旧数据;
- 9600波特率下,这个时间窗也仅约1ms。
纯轮询如果被其他高优先级任务长时间抢占,或者轮询间隔超过上述时间窗,就会导致丢数据,同时还可能引发低优先级任务饥饿。
多任务(RTOS如FreeRTOS)环境下的可行轮询实现方案
1. 高优先级轻量轮询任务
将串口轮询任务设为系统最高优先级,任务逻辑仅做“串口状态检查+数据缓存”,复杂数据处理交给低优先级任务:
void vUartPollTask(void *pvParameters) { uint8_t rxByte; for(;;) { // 检查串口是否有接收数据 if(UART_FLAG_RXNE & USART_GetFlagStatus(USART1, UART_FLAG_RXNE)) { rxByte = USART_ReceiveData(USART1); // 写入环形软件缓冲区,用临界区保护并发访问 portENTER_CRITICAL(); ring_buf_put(&rxRingBuf, rxByte); portEXIT_CRITICAL(); } // 主动放弃CPU,给同优先级或低优先级任务运行机会 taskYIELD(); } }
- 优势:既保证了轮询响应速度(最高优先级不会被低优先级任务抢占),又通过
taskYIELD()避免了其他任务完全饥饿; - 注意:轮询任务必须极简,不能包含任何阻塞或耗时操作,否则仍会导致丢数据。
2. 结合中断唤醒的“半轮询”方式
用串口接收中断触发任务唤醒,轮询任务仅在有数据时才执行读取操作,平时进入阻塞状态:
// 串口接收中断服务函数 void USART1_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 发送信号量唤醒轮询任务 xSemaphoreGiveFromISR(xUartRxSemaphore, &xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); } } // 串口处理任务(本质是等待信号量的轮询逻辑) void vUartTask(void *pvParameters) { uint8_t rxByte; for(;;) { // 阻塞等待中断触发的信号量 xSemaphoreTake(xUartRxSemaphore, portMAX_DELAY); // 此时必然有数据,直接读取 rxByte = USART_ReceiveData(USART1); // 调用数据处理函数(可放在低优先级任务中) process_rx_data(rxByte); } }
- 优势:兼顾了轮询的简单性和中断的低CPU占用,既不会丢数据,也不会让CPU空转浪费资源。
3. 时间片调度下的同优先级轮询
如果RTOS开启了时间片调度(如FreeRTOS的configUSE_TIME_SLICING),可将轮询任务设为与其他任务同优先级:
- 需确保时间片长度小于串口的字节接收间隔(比如115200波特率下时间片不超过70us);
- 适用场景:低波特率(如9600)、其他任务实时性要求不高的场景,高波特率下不推荐。
单任务无RTOS环境下的纯轮询实现
单任务系统中,只要保证主循环内的轮询间隔满足波特率要求即可:
- 避免在主循环中使用超过接收时间窗的阻塞延时,若必须使用长延时,需在延时过程中插入串口轮询检查;
- 示例:
void main(void) { uint8_t rxByte; uart_init(); for(;;) { // 轮询串口 if(UART_FLAG_RXNE & USART_GetFlagStatus(USART1, UART_FLAG_RXNE)) { rxByte = USART_ReceiveData(USART1); process_rx_data(rxByte); } // 短延时,确保轮询间隔不超过70us delay_us(10); // 其他业务逻辑(需控制耗时) other_task(); } }
轮询方式的适用场景
- 单任务无RTOS的简单系统;
- CPU资源充足、其他任务实时性要求极低的场景;
- 快速原型验证,追求实现简单而非最优性能的场景。
内容的提问来源于stack exchange,提问作者SRobertJames
相关产品推荐
相关产品推荐

