当前位置: 首页 > news >正文

嵌入式开发之轮询机制详细解析

目录

  1. 概念 (Concept)
  2. 原理 (Principle)
  3. 执行过程 (Process)
  4. 典型应用场景 (Scenarios)
  5. 轮询 vs 中断 (Polling vs Interrupt)
  6. STM32 实战示例 (Examples)
  7. 总结与最佳实践

1. 概念 (Concept)

1.1 什么是轮询 (Polling)?

轮询是一种程序设计模式,指CPU 主动、周期性地查询外设或事件的状态,以判断是否需要处理相应的任务。

通俗理解:就像一位服务员每隔几分钟就去每一桌问"需要点餐吗?",而不是等客人举手呼唤。

1.2 核心特征

特征说明
主动性CPU 主动查询,而非被动等待
周期性按照固定时间间隔重复执行
顺序性按预定顺序依次检查各个设备/事件
简单性实现逻辑直观,代码结构简单

1.3 轮询的三种形式

┌─────────────────────────────────────────────────────────────┐ │ 轮询的三种常见形式 │ ├─────────────────────────────────────────────────────────────┤ │ 1. 无限循环轮询 (Super Loop) │ │ while(1) { 查询A; 查询B; 查询C; } │ │ │ │ 2. 定时轮询 (Timed Polling) │ │ 每隔固定时间间隔执行一次查询 │ │ │ │ 3. 事件标志轮询 (Event Flag Polling) │ │ 查询共享的事件标志位来判断是否有任务需要处理 │ └─────────────────────────────────────────────────────────────┘

2. 原理 (Principle)

2.1 轮询机制的工作模型

┌──────────────┐ │ 开始运行 │ └──────┬───────┘ ▼ ┌──────────────┐ │ 初始化系统 │ │ (时钟/GPIO等) │ └──────┬───────┘ ▼ ┌──────────────┐ 否 ┌─────────────┐ ┌───▶│ 查询设备A? │────────▶│ 查询设备B? │────┐ │ │ 状态有变化? │ │ 状态有变化? │ │ │ └──────┬───────┘ └──────┬──────┘ │ │ │ 是 │ 是 │ │ ▼ ▼ │ │ ┌──────────────┐ ┌─────────────┐ │ │ │ 处理设备A │ │ 处理设备B │ │ │ └──────┬───────┘ └──────┬──────┘ │ │ │ │ │ │ └──────────┬─────────────┘ │ │ ▼ │ │ ┌──────────────┐ │ │ │ 查询设备C? │ │ │ │ 状态有变化? │ │ │ └──────┬───────┘ │ │ │ 是 │ │ ▼ │ │ ┌──────────────┐ │ │ │ 处理设备C │ │ │ └──────┬───────┘ │ │ │ │ └──────────────────────┘ (回到开头继续轮询) │

2.2 状态寄存器查询原理

在 STM32 中,每个外设都有对应的状态寄存器 (SR - Status Register)。轮询的本质就是读取这些寄存器的特定位来判断外设状态。

┌─────────────────────────────────────────────────────────────┐ │ USART_SR (USART 状态寄存器) 示例 │ ├────────┬────────┬────────┬────────┬───────┬─────────────────┤ │ 位7 │ 位6 │ 位5 │ 位4 │ ... │ 位0 │ ├────────┼────────┼────────┼────────┼───────┼─────────────────┤ │ TXE │ TC │ RXNE │ IDLE │ ... │ PE │ ├────────┼────────┼────────┼────────┼───────┼─────────────────┤ │发送寄存器│发送完成│接收数据 │空闲线路│ ... │ 校验错误 │ │ 空 │ │ 非空 │ │ │ │ └────────┴────────┴────────┴────────┴───────┴─────────────────┘ 轮询 RXNE 位: while(!(USART1->SR & USART_SR_RXNE)); // 等待接收数据 轮询 TXE 位: while(!(USART1->SR & USART_SR_TXE)); // 等待发送就绪

2.3 轮询的时间开销分析

时间轴 ────────────────────────────────────────────────────────▶ CPU 活动: [查询A][查询B][查询C][查询A][查询B][查询C][查询A]... ↑ ↑ ↑ ↑ ↑ ↑ ↑ │ │ │ │ │ │ │ 外设A: ▲ ▲ ▲ 事件发生 事件发生 事件发生 关键指标: ├── 轮询周期 (T_poll): 完成一轮所有查询的时间 ├── 响应延迟 (T_latency): 事件发生到被处理的最大时间 ≈ T_poll └── CPU 占用率: 持续消耗 CPU 时间,即使没有事件发生

3. 执行过程 (Process)

3.1 标准轮询流程

// ============================================ // 标准轮询模式执行流程 (伪代码) // ============================================ void System_Init(void) { // 1. 系统时钟初始化 HAL_Init(); SystemClock_Config(); // 2. 外设初始化 GPIO_Init(); USART_Init(); ADC_Init(); TIM_Init(); } int main(void) { System_Init(); while (1) { // 主循环 - 超级循环 (Super Loop) // ========== 轮询任务 1: 按键检测 ========== if (GPIO_ReadPin(KEY_GPIO, KEY_PIN) == GPIO_PIN_RESET) { HAL_Delay(20); // 消抖 if (GPIO_ReadPin(KEY_GPIO, KEY_PIN) == GPIO_PIN_RESET) { Key_Process(); // 处理按键事件 while(GPIO_ReadPin(KEY_GPIO, KEY_PIN) == GPIO_PIN_RESET); // 等待释放 } } // ========== 轮询任务 2: 串口数据接收 ========== if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { uint8_t data = (uint8_t)(huart1.Instance->DR & 0xFF); USART_RxBuffer[RxIndex++] = data; USART_DataReceived_Flag = 1; // 设置标志 } // ========== 轮询任务 3: ADC 数据采集 ========== if (ADC_ConversionComplete_Flag) { uint16_t adc_value = HAL_ADC_GetValue(&hadc1); ADC_Process(adc_value); ADC_ConversionComplete_Flag = 0; HAL_ADC_Start(&hadc1); // 启动下一次转换 } // ========== 轮询任务 4: 定时任务 ========== if (HAL_GetTick() - last_tick >= 1000) { last_tick = HAL_GetTick(); LED_Toggle(); // 每秒闪烁 LED Sensor_Read(); // 每秒读取传感器 } } }

3.2 带状态机的轮询 (进阶)

// ============================================ // 状态机轮询模式 - 更适合复杂流程 // ============================================ typedef enum { STATE_IDLE, STATE_WAIT_DATA, STATE_PROCESSING, STATE_SEND_RESPONSE, STATE_ERROR } System_State_t; System_State_t current_state = STATE_IDLE; void StateMachine_Poll(void) { switch (current_state) { case STATE_IDLE: if (USART_DataAvailable()) { current_state = STATE_WAIT_DATA; } break; case STATE_WAIT_DATA: if (USART_PacketComplete()) { current_state = STATE_PROCESSING; } break; case STATE_PROCESSING: if (Process_Data()) { current_state = STATE_SEND_RESPONSE; } else { current_state = STATE_ERROR; } break; case STATE_SEND_RESPONSE: USART_SendResponse(); current_state = STATE_IDLE; break; case STATE_ERROR: Error_Handler(); current_state = STATE_IDLE; break; } } int main(void) { System_Init(); while (1) { StateMachine_Poll(); } }

3.3 轮询中的延时处理

// ============================================ // 非阻塞延时轮询 (避免 HAL_Delay 阻塞) // ============================================ typedef struct { uint32_t start_time; uint32_t delay_ms; uint8_t is_running; } NonBlocking_Timer_t; // 启动非阻塞定时器 void Timer_Start(NonBlocking_Timer_t *timer, uint32_t delay_ms) { timer->start_time = HAL_GetTick(); timer->delay_ms = delay_ms; timer->is_running = 1; } // 查询定时器是否到期 (轮询方式) uint8_t Timer_IsExpired(NonBlocking_Timer_t *timer) { if (!timer->is_running) return 0; if ((HAL_GetTick() - timer->start_time) >= timer->delay_ms) { timer->is_running = 0; return 1; } return 0; } // 使用示例 NonBlocking_Timer_t led_timer; int main(void) { System_Init(); Timer_Start(&led_timer, 500); // 500ms 定时 while (1) { if (Timer_IsExpired(&led_timer)) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); Timer_Start(&led_timer, 500); // 重新启动 } // 其他轮询任务... } }

4. 典型应用场景 (Scenarios)

4.1 适用场景

场景说明STM32 示例
简单系统任务少、逻辑简单小型传感器节点
确定性时序需要严格控制执行顺序步进电机控制
低速外设外设响应速度远低于 CPU按键、温度传感器
初始化阶段等待外设就绪等待晶振稳定、Flash 就绪
调试阶段便于单步跟踪和调试开发初期验证
无中断资源中断向量表已满或禁用中断安全关键系统

4.2 不适用场景

场景原因替代方案
高速数据流CPU 占用率 100%,无法处理其他任务DMA + 中断
低功耗应用CPU 无法进入睡眠模式中断 + 睡眠
实时性要求高响应延迟不确定中断
多任务复杂系统代码难以维护RTOS

4.3 场景决策流程图

┌─────────────────┐ │ 开始选择机制 │ └────────┬────────┘ ▼ ┌─────────────────┐ │ 系统任务数量? │ └────────┬────────┘ │ ┌────────────────┼────────────────┐ ▼ ▼ ▼ 很少(1-3个) 中等(4-10个) 很多(>10个) │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 轮询可行 │ │ 轮询+状态机 │ │ 考虑RTOS │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 实时性要求? │ │ 实时性要求? │ │ 使用RTOS │ └──────┬───────┘ └──────┬───────┘ └──────────────┘ │ │ ┌────┴────┐ ┌────┴────┐ ▼ ▼ ▼ ▼ 低/中等 高 低/中等 高 │ │ │ │ ▼ ▼ ▼ ▼ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ 轮询 │ │中断 │ │轮询 │ │中断 │ │ 推荐 │ │推荐 │ │可行 │ │推荐 │ └──────┘ └──────┘ └──────┘ └──────┘

5. 轮询 vs 中断 (Polling vs Interrupt)

5.1 核心对比

对比维度轮询 (Polling)中断 (Interrupt)
触发方式CPU 主动查询外设主动通知 CPU
响应延迟取决于轮询周期 (ms 级)立即响应 (μs 级)
CPU 占用持续占用,即使没有事件仅在事件发生时占用
代码复杂度简单,线性逻辑复杂,需处理上下文切换
实时性较差,不确定好,可预测
功耗高 (CPU 持续运行)低 (CPU 可睡眠)
调试难度简单,可单步跟踪较难,时序敏感
资源占用无额外硬件资源需要中断控制器、NVIC
适用场景简单、低速、任务少高速、实时、多任务

5.2 工作机制对比图

┌─────────────────────────────────────────────────────────────────────┐ │ 轮询模式 (Polling) │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ CPU: [查询][查询][查询][查询][处理][查询][查询][查询][查询]... │ │ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ │ │ │ │ │ │ │ │ │ │ │ │ │ 事件: ▲ ▲ │ │ 发生 发生 │ │ │ │ 特点: CPU 一直在工作,即使事件未发生也在查询 │ │ │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ 中断模式 (Interrupt) │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ CPU: [睡眠/其他任务][睡眠/其他任务][中断处理][睡眠][中断处理]... │ │ ↑ ↑ ↑ ↑ ↑ │ │ │ │ │ │ │ │ │ 事件: ▲ ▲ ▲ │ │ 发生 发生 发生 │ │ │ │ 特点: CPU 平时可睡眠或执行其他任务,事件发生时自动唤醒处理 │ │ │ └─────────────────────────────────────────────────────────────────────┘

5.3 NVIC 中断控制器简介 (STM32)

┌─────────────────────────────────────────────────────────────────────┐ │ STM32 NVIC (嵌套向量中断控制器) │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ 外设中断源 │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ USART │ │ TIM │ │ EXTI │ │ DMA │ ... │ │ └───┬─────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ │ │ └─────────────┴────────────┴────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ NVIC │ │ │ │ ┌───────────────┐ │ │ │ │ │ 中断优先级 │ │ ← 可配置抢占优先级和子优先级 │ │ │ │ 仲裁与管理 │ │ │ │ │ └───────────────┘ │ │ │ └──────────┬──────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ Cortex-M 内核 │ │ │ │ (处理中断服务程序) │ │ │ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘

5.4 混合使用策略

// ============================================ // 轮询 + 中断 混合策略 (最佳实践) // ============================================ // 原则: 低速/简单任务用轮询,高速/紧急任务用中断 int main(void) { System_Init(); // 启用中断: 用于紧急、高速事件 HAL_UART_Receive_IT(&huart1, rx_buf, 1); // 串口接收中断 HAL_TIM_Base_Start_IT(&htim2); // 定时器中断 (1ms) while (1) { // 轮询: 用于低速、非紧急任务 // 1. 按键扫描 (人操作,速度很慢) Key_Scan_Polling(); // 2. 显示刷新 (几十Hz 足够) Display_Refresh_Polling(); // 3. 处理中断产生的数据 if (uart_rx_flag) { uart_rx_flag = 0; Process_UART_Data(); } // 4. 慢速传感器读取 if (tim2_100ms_flag) { tim2_100ms_flag = 0; Sensor_Read_Polling(); } // 5. 低功耗: 无任务时进入睡眠 __WFI(); // Wait For Interrupt } }

6. STM32 实战示例 (Examples)

6.1 示例一: 轮询方式读取按键

/** * @file main_polling_key.c * @brief 轮询方式检测按键 (基于 STM32F103) * @note 使用 HAL 库 */ #include "stm32f1xx_hal.h" // 按键引脚定义 #define KEY_GPIO_PORT GPIOA #define KEY_GPIO_PIN GPIO_PIN_0 #define LED_GPIO_PORT GPIOC #define LED_GPIO_PIN GPIO_PIN_13 // 按键状态枚举 typedef enum { KEY_RELEASED = 0, KEY_PRESSED = 1 } Key_State_t; // 初始化 GPIO void GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct = {0}; // LED 输出配置 GPIO_InitStruct.Pin = LED_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(LED_GPIO_PORT, &GPIO_InitStruct); // 按键输入配置 (上拉输入) GPIO_InitStruct.Pin = KEY_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(KEY_GPIO_PORT, &GPIO_InitStruct); } // 轮询方式读取按键 (带软件消抖) Key_State_t Key_Poll(void) { // 第一次读取 if (HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN) == GPIO_PIN_RESET) { HAL_Delay(20); // 消抖延时 20ms // 第二次读取 (确认按键确实按下) if (HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN) == GPIO_PIN_RESET) { // 等待按键释放 (阻塞式) while (HAL_GPIO_ReadPin(KEY_GPIO_PORT, KEY_GPIO_PIN) == GPIO_PIN_RESET); HAL_Delay(20); // 释放消抖 return KEY_PRESSED; } } return KEY_RELEASED; } // 主函数 int main(void) { HAL_Init(); SystemClock_Config(); GPIO_Init(); printf("=== 轮询按键检测示例 ===\r\n"); while (1) { // 轮询按键状态 if (Key_Poll() == KEY_PRESSED) { printf("按键被按下!\r\n"); HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_GPIO_PIN); } // 可以在这里添加其他轮询任务... } }

运行结果:

=== 轮询按键检测示例 === 按键被按下! 按键被按下! 按键被按下!

6.2 示例二: 轮询方式发送/接收串口数据

/** * @file main_polling_uart.c * @brief 轮询方式实现 USART 数据收发 (STM32F103) */ #include "stm32f1xx_hal.h" UART_HandleTypeDef huart1; // USART1 初始化 (波特率 115200) void USART1_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&huart1); } // 轮询方式发送单个字符 void UART_Poll_SendChar(char c) { // 轮询等待发送数据寄存器为空 (TXE = 1) while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE) == RESET); // 写入数据寄存器 huart1.Instance->DR = (uint8_t)(c & 0xFF); } // 轮询方式发送字符串 void UART_Poll_SendString(char *str) { while (*str) { UART_Poll_SendChar(*str++); } } // 轮询方式接收单个字符 char UART_Poll_ReceiveChar(void) { // 轮询等待接收数据寄存器非空 (RXNE = 1) while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) == RESET); // 读取数据寄存器 return (char)(huart1.Instance->DR & 0xFF); } // 轮询方式接收指定长度数据 void UART_Poll_ReceiveBuffer(uint8_t *buf, uint16_t len) { for (uint16_t i = 0; i < len; i++) { buf[i] = (uint8_t)UART_Poll_ReceiveChar(); } } int main(void) { HAL_Init(); SystemClock_Config(); USART1_Init(); uint8_t rx_buf[64]; UART_Poll_SendString("=== USART 轮询收发示例 ===\r\n"); UART_Poll_SendString("请输入数据 (回车结束): "); while (1) { // 轮询接收数据 uint16_t idx = 0; while (1) { char c = UART_Poll_ReceiveChar(); // 回显 UART_Poll_SendChar(c); if (c == '\r' || c == '\n') { rx_buf[idx] = '\0'; break; } rx_buf[idx++] = c; if (idx >= 63) break; } UART_Poll_SendString("\r\n收到数据: "); UART_Poll_SendString((char*)rx_buf); UART_Poll_SendString("\r\n请输入数据: "); } }

运行结果:

=== USART 轮询收发示例 === 请输入数据 (回车结束): Hello STM32 收到数据: Hello STM32 请输入数据: Polling 收到数据: Polling

6.3 示例三: 轮询方式 ADC 采集

/** * @file main_polling_adc.c * @brief 轮询方式实现 ADC 采集 (STM32F103) */ #include "stm32f1xx_hal.h" ADC_HandleTypeDef hadc1; // ADC1 初始化 void ADC1_Init(void) { __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // PA0 作为模拟输入 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; hadc1.Init.ContinuousConvMode = DISABLE; // 单次转换模式 hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); // 配置通道 0 ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_71CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); } // 轮询方式读取 ADC 值 uint16_t ADC_Poll_Read(void) { // 1. 启动 ADC 转换 HAL_ADC_Start(&hadc1); // 2. 轮询等待转换完成 (EOC 标志位置位) // HAL_ADC_PollForConversion 内部就是轮询 SR 寄存器的 EOC 位 HAL_StatusTypeDef status = HAL_ADC_PollForConversion(&hadc1, 100); // 超时 100ms if (status != HAL_OK) { printf("ADC 转换超时!\r\n"); return 0; } // 3. 读取转换结果 uint16_t adc_value = HAL_ADC_GetValue(&hadc1); // 4. 停止 ADC (单次模式) HAL_ADC_Stop(&hadc1); return adc_value; } // 将 ADC 值转换为电压 float ADC_ToVoltage(uint16_t adc_value) { // STM32F103: 12位 ADC, 参考电压 3.3V return (float)adc_value * 3.3f / 4095.0f; } int main(void) { HAL_Init(); SystemClock_Config(); ADC1_Init(); printf("=== ADC 轮询采集示例 ===\r\n"); while (1) { // 轮询读取 ADC uint16_t raw = ADC_Poll_Read(); float voltage = ADC_ToVoltage(raw); printf("ADC 原始值: %d, 电压: %.2f V\r\n", raw, voltage); HAL_Delay(1000); // 每秒采集一次 } }

运行结果:

=== ADC 轮询采集示例 === ADC 原始值: 2048, 电压: 1.65 V ADC 原始值: 3100, 电压: 2.50 V ADC 原始值: 1245, 电压: 1.00 V

6.4 示例四: 轮询 vs 中断 对比代码

/** * @file main_compare.c * @brief 轮询与中断方式对比 - 串口接收 */ #include "stm32f1xx_hal.h" UART_HandleTypeDef huart1; volatile uint8_t uart_rx_data; volatile uint8_t uart_rx_flag = 0; // ==================== 方式一: 纯轮询 ==================== void USART1_Polling_Mode(void) { printf("\r\n[模式一: 纯轮询]\r\n"); while (1) { // 轮询等待接收标志位 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { uint8_t data = (uint8_t)(huart1.Instance->DR & 0xFF); printf("轮询收到: %c\r\n", data); if (data == 'Q') break; // 退出命令 } // 轮询期间 CPU 无法做其他事,除非在这里添加 // 但添加太多任务会增加响应延迟 } } // ==================== 方式二: 中断方式 ==================== // 中断服务函数 void USART1_IRQHandler(void) { HAL_UART_IRQHandler(&huart1); } // 接收完成回调 (中断上下文) void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { uart_rx_flag = 1; // 重新启动中断接收 HAL_UART_Receive_IT(&huart1, (uint8_t*)&uart_rx_data, 1); } } void USART1_Interrupt_Mode(void) { printf("\r\n[模式二: 中断方式]\r\n"); // 启动中断接收 HAL_UART_Receive_IT(&huart1, (uint8_t*)&uart_rx_data, 1); while (1) { // 主循环可以做其他任务 // 当数据到达时,中断会自动处理 if (uart_rx_flag) { uart_rx_flag = 0; printf("中断收到: %c\r\n", uart_rx_data); if (uart_rx_data == 'Q') break; } // 这里可以执行其他任务,不影响串口响应 // 例如: 更新显示、读取传感器等 } } // ==================== 性能对比测试 ==================== void Performance_Test(void) { printf("\r\n========== 性能对比测试 ==========\r\n"); // 测试轮询方式 CPU 占用 uint32_t start_tick = HAL_GetTick(); uint32_t poll_count = 0; while (HAL_GetTick() - start_tick < 1000) { __HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE); poll_count++; } printf("轮询方式: 1秒内查询了 %lu 次\r\n", poll_count); printf("CPU 占用率: ~100%% (持续查询)\r\n"); // 中断方式下,CPU 只在接收时工作 printf("中断方式: CPU 空闲时可进入低功耗模式\r\n"); printf("CPU 占用率: 仅在接收数据时占用\r\n"); } int main(void) { HAL_Init(); SystemClock_Config(); USART1_Init(); printf("\r\n=== 轮询 vs 中断 对比示例 ===\r\n"); USART1_Polling_Mode(); USART1_Interrupt_Mode(); Performance_Test(); while (1); }

6.5 示例五: 综合轮询系统 (多任务)

/** * @file main_polling_system.c * @brief 综合轮询系统示例 - 多任务管理 */ #include "stm32f1xx_hal.h" // ========== 任务周期定义 (ms) ========== #define TASK_LED_PERIOD 500 // LED 闪烁 500ms #define TASK_KEY_PERIOD 20 // 按键扫描 20ms #define TASK_ADC_PERIOD 1000 // ADC 采集 1s #define TASK_UART_PERIOD 10 // 串口查询 10ms #define TASK_SENSOR_PERIOD 200 // 传感器 200ms // ========== 任务结构体 ========== typedef struct { uint32_t last_run; // 上次运行时间 uint32_t period; // 执行周期 void (*task_func)(void); // 任务函数指针 uint8_t enabled; // 使能标志 } Polling_Task_t; // ========== 任务函数声明 ========== void Task_LED(void); void Task_KeyScan(void); void Task_ADC(void); void Task_UART(void); void Task_Sensor(void); // ========== 任务表 ========== Polling_Task_t task_table[] = { {0, TASK_LED_PERIOD, Task_LED, 1}, {0, TASK_KEY_PERIOD, Task_KeyScan, 1}, {0, TASK_ADC_PERIOD, Task_ADC, 1}, {0, TASK_UART_PERIOD, Task_UART, 1}, {0, TASK_SENSOR_PERIOD, Task_Sensor, 1}, }; #define TASK_COUNT (sizeof(task_table) / sizeof(task_table[0])) // ========== 任务实现 ========== void Task_LED(void) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); } void Task_KeyScan(void) { static uint8_t key_prev = 1; uint8_t key_now = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); if (key_prev == 1 && key_now == 0) { // 下降沿检测 HAL_Delay(10); // 简单消抖 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == 0) { printf("按键按下\r\n"); } } key_prev = key_now; } void Task_ADC(void) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 100); uint16_t value = HAL_ADC_GetValue(&hadc1); printf("ADC: %d\r\n", value); HAL_ADC_Stop(&hadc1); } void Task_UART(void) { if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) { uint8_t ch = (uint8_t)(huart1.Instance->DR & 0xFF); // 简单命令解析 switch(ch) { case '1': task_table[0].enabled ^= 1; break; // 切换LED任务 case '2': task_table[2].enabled ^= 1; break; // 切换ADC任务 case 'S': printf("系统状态:\r\n"); break; } } } void Task_Sensor(void) { // 模拟传感器读取 static uint32_t sensor_data = 0; sensor_data++; printf("Sensor: %lu\r\n", sensor_data); } // ========== 调度器 ========== void Scheduler_Run(void) { uint32_t current_tick = HAL_GetTick(); for (uint8_t i = 0; i < TASK_COUNT; i++) { if (task_table[i].enabled && (current_tick - task_table[i].last_run) >= task_table[i].period) { task_table[i].last_run = current_tick; task_table[i].task_func(); } } } // ========== 主函数 ========== int main(void) { HAL_Init(); SystemClock_Config(); // 初始化所有外设... printf("=== 综合轮询系统启动 ===\r\n"); printf("命令: 1-切换LED, 2-切换ADC, S-状态\r\n"); while (1) { Scheduler_Run(); // 执行调度器 // 空闲时可进入低功耗 (配合中断唤醒) // __WFI(); } }

7. 总结与最佳实践

7.1 轮询机制优缺点总结

┌─────────────────────────────────────────────────────────────────┐ │ 轮询机制优缺点 │ ├──────────────────────────────┬──────────────────────────────────┤ │ 优点 │ 缺点 │ ├──────────────────────────────┼──────────────────────────────────┤ │ 1. 实现简单,代码直观 │ 1. CPU 占用率高,功耗大 │ │ 2. 无需中断控制器配置 │ 2. 响应延迟不确定 │ │ 3. 易于调试和单步跟踪 │ 3. 高速场景下效率低 │ │ 4. 无中断上下文切换开销 │ 4. 无法进入低功耗模式 │ │ 5. 执行顺序确定,可预测 │ 5. 大量任务时实时性差 │ │ 6. 适合简单系统和初始化 │ 6. 代码耦合度高,扩展性差 │ └──────────────────────────────┴──────────────────────────────────┘

7.2 STM32 轮询最佳实践

实践建议说明
合理设置轮询周期根据外设响应速度设置,避免过频查询
使用非阻塞延时避免HAL_Delay阻塞整个轮询循环
添加超时机制防止轮询死等,提高系统健壮性
混合使用中断高速/紧急事件用中断,低速事件用轮询
状态机设计复杂流程使用状态机,避免嵌套轮询
任务调度器多任务时使用简单调度器管理执行周期
低功耗考虑无任务时调用__WFI()进入睡眠

7.3 快速决策表

条件推荐方案
系统简单 (< 3 个任务)纯轮询
有高速数据流中断 + DMA
需要低功耗中断 + 睡眠模式
实时性要求高中断
任务多且复杂RTOS
调试阶段轮询 (便于跟踪)
生产环境混合模式

7.4 参考资源

  • STM32 参考手册: RM0008 (STM32F103), RM0090 (STM32F4)
  • Cortex-M3/M4 权威指南: Joseph Yiu
  • HAL/LL 驱动用户手册: STM32Cube 官方文档
http://www.jsqmd.com/news/1084372/

相关文章:

  • 如何用开源工具实现跨平台直播自动化录制与监控
  • 3分钟解决Windows运行库问题:VisualCppRedist AIO终极指南
  • Fooocus:5分钟掌握完全免费的AI图像生成神器终极指南
  • 50天50个项目:前端练手资源库
  • 想在东莞定制开发小程序?这些口碑好的服务商值得你深入了解
  • Windows 系统文件d3dx9_29.dll丢失找不到问题解决
  • PostgreSQL 功能大揭秘:众多领域工具与应用全收录!
  • 华为防火墙远程管理三件套:Web、Telnet、SSH配置与安全加固实战
  • YOLO骨干网络改进-第7篇:Swin Transformer块替换C2f的实验研究
  • Mermaid在线编辑器终极指南:3分钟创建专业图表的高效方法
  • 锥形奇点下Hodge原子分解与Stokes矩阵的等价性原理与应用
  • RubyLLM:美观框架支持主流AI供应商,两分钟构建可用Ruby AI聊天应用!
  • 质量管理-IQC是什么?
  • 智慧农业各种水稻害虫检测数据集VOC+YOLO格式615张12类别
  • Python面向对象:实例属性与类属性的区别
  • 解放双手:《崩坏:星穹铁道》自动化助手StarRailAssistant全面解析
  • 2026年靠谱外贸网站建设公司测评,10家外贸独立站搭建选型实战攻略
  • 一键打通Rhino到Blender:import_3dm插件完全指南
  • 如何快速将3DS游戏转换为CIA格式:新手终极指南
  • 对话即界面:TokUI正式开源,AI交互告别“文字墙”时代
  • Hermes-Agent 新手快速上手与实战指南
  • 技术创业者别总想着写代码
  • 江苏省技术先进型服务企业认定条件及材料清单
  • 杰理之蓝牙PA使能配置【篇】
  • 如何快速绕过Windows 11硬件限制:bypass11工具完整解决方案
  • 终极指南:如何在Windows 10/11上复活经典游戏的局域网联机功能
  • 六轴机器人-核山派2
  • 从 Hello World 到生产级服务的 vLLM 部署进阶
  • 游戏串流技术架构:基于Sunshine的自托管低延迟游戏流媒体解决方案
  • 工业自动化设备轻量化设计与节能优化实践