You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单片机采用轮询方式读取串口能否避免数据丢失?

单片机串口轮询方式的可行性分析与实现方法

核心结论

“只要有其他任务运行,轮询接收串口数据就无法实现”这种说法不正确。轮询方式能否正常工作,核心是能否保证在串口硬件缓冲区被覆盖前(即下一个字节到来前)完成数据读取,而非完全禁止其他任务运行。


纯轮询在多任务环境下的风险根源

对于仅配备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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 10:15:12