嵌入式开发之轮询机制详细解析
目录概念 (Concept)原理 (Principle)执行过程 (Process)典型应用场景 (Scenarios)轮询 vs 中断 (Polling vs Interrupt)STM32 实战示例 (Examples)总结与最佳实践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 无法进入睡眠模式中断 睡眠实时性要求高响应延迟不确定中断多任务复杂系统代码难以维护RTOS4.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 收到数据: Polling6.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 V6.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 YiuHAL/LL 驱动用户手册: STM32Cube 官方文档

相关新闻

Windows 系统文件d3dx9_29.dll丢失找不到问题解决

Windows 系统文件d3dx9_29.dll丢失找不到问题解决

在使用电脑系统时经常会出现丢失找不到某些文件的情况,由于很多常用软件都是采用 Microsoft Visual Studio 编写的,所以这类软件的运行需要依赖微软Visual C运行库,比如像 QQ、迅雷、Adobe 软件等等,如果没有安装VC运行库或者安装…

2026/6/26 23:28:45阅读更多 →
3步将手机变身高清直播摄像头:DroidCam OBS插件完全指南

3步将手机变身高清直播摄像头:DroidCam OBS插件完全指南

3步将手机变身高清直播摄像头:DroidCam OBS插件完全指南 【免费下载链接】droidcam-obs-plugin DroidCam OBS Source 项目地址: https://gitcode.com/gh_mirrors/dr/droidcam-obs-plugin 想让你的智能手机瞬间变身专业级直播摄像头吗?DroidCam OB…

2026/6/26 23:28:45阅读更多 →
Windows系统文件d3dx9_28.dll丢失找不到问题解决

Windows系统文件d3dx9_28.dll丢失找不到问题解决

在使用电脑系统时经常会出现丢失找不到某些文件的情况,由于很多常用软件都是采用 Microsoft Visual Studio 编写的,所以这类软件的运行需要依赖微软Visual C运行库,比如像 QQ、迅雷、Adobe 软件等等,如果没有安装VC运行库或者安装…

2026/6/26 23:28:45阅读更多 →
【Springboot毕设全套源码+文档】基于SpringBoot的智能家居管理系统设计与实现(丰富项目+远程调试+讲解+定制)

【Springboot毕设全套源码+文档】基于SpringBoot的智能家居管理系统设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/6/27 0:44:09阅读更多 →
SU(2)规范理论构建引力模型:动机、策略与挑战

SU(2)规范理论构建引力模型:动机、策略与挑战

1. 从“力”的统一到“几何”的再诠释:引力规范理论的动机在理论物理的探索中,统一始终是一个核心的驱动力。我们熟知的电磁力、弱力和强力,最终在规范理论的框架下得到了优美而统一的描述:它们都是某种内部对称性(如U…

2026/6/27 0:44:09阅读更多 →
基于4G和GPS的智慧养殖物联网终端设计与优化

基于4G和GPS的智慧养殖物联网终端设计与优化

1. 项目背景与核心功能这个智慧养殖盒子项目本质上是一个基于4G和GPS技术的物联网终端设备,专门针对养殖行业的远程监控需求设计。我在实际部署中发现,很多养殖场位于偏远地区,有线网络覆盖差,而传统的人工巡检方式又存在效率低、…

2026/6/27 0:44:09阅读更多 →
PCB与FPC的本质差异及设计制造要点解析

PCB与FPC的本质差异及设计制造要点解析

1. PCB与FPC的本质差异刚拿到第一块柔性电路板时,我下意识想用剪钳直接裁剪——直到看见同事惊恐的表情才意识到,这种能像纸片般弯曲的"电路板"完全颠覆了我对传统PCB的认知。在消费电子追求轻薄化的今天,柔性电路(FPC&…

2026/6/27 0:44:09阅读更多 →
杰理之时钟信号同步性排查【篇】

杰理之时钟信号同步性排查【篇】

◦ 用示波器测量 MCLK、LRCLK、SCLK 的频率,验证是否符合预设值:▪ LRCLK 频率 ≡ 音频采样率(如 44.1kHz 采样率对应 LRCLK44.1kHz);▪ SCLK 频率 采样率 位深度(如 16bit 对应 SCLK44.1kHz16705.6kHz&a…

2026/6/27 0:44:09阅读更多 →
【紧急预警】IntelliJ IDEA 2024新版已悄然变更Spring Boot项目默认配置!3类高危兼容性风险正在爆发,立即自查这4个关键节点

【紧急预警】IntelliJ IDEA 2024新版已悄然变更Spring Boot项目默认配置!3类高危兼容性风险正在爆发,立即自查这4个关键节点

更多请点击: https://kaifayun.com 第一章:IntelliJ IDEA 2024新版Spring Boot项目创建机制重大变更概览 IntelliJ IDEA 2024.1 起彻底重构了 Spring Boot 项目初始化流程,弃用旧版基于 Spring Initializr Web API 的向导式表单,…

2026/6/27 0:39:09阅读更多 →
【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体

【人工智能】一文搞定到底什么是智能体 一文搞定到底什么是智能体【人工智能】一文搞定到底什么是智能体一. LM,WorkFlow,Agent分别有什么么不同二. Agent的思考过程是怎样的三. Agent的五个核心部分1)LLM2)Prompt3)Me…

2026/6/26 11:03:22阅读更多 →
嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

嵌入式GUI控件实战:ROTARY、SCROLLBAR、SLIDER原理与应用

1. 嵌入式GUI控件:从原理到实战的深度解析在嵌入式系统开发中,图形用户界面(GUI)的设计与实现往往是项目从“能用”到“好用”的关键一跃。不同于资源充沛的PC或移动平台,嵌入式设备的GUI需要在有限的CPU性能、内存空间…

2026/6/26 4:15:25阅读更多 →
Google AI Studio 300美元额度的真相与实战指南

Google AI Studio 300美元额度的真相与实战指南

1. 这300美金不是“送钱”,而是Google埋下的第一道技术门槛 你看到标题里那个醒目的“$300美金”时,第一反应可能是:又一个免费额度?领完就完事?我亲手试过——这300美金根本不是红包,而是一张入场券&…

2026/6/26 9:29:01阅读更多 →
10分钟AI语音克隆与实时变声:Retrieval-based-Voice-Conversion-WebUI完整指南

10分钟AI语音克隆与实时变声:Retrieval-based-Voice-Conversion-WebUI完整指南

10分钟AI语音克隆与实时变声&#xff1a;Retrieval-based-Voice-Conversion-WebUI完整指南 【免费下载链接】Retrieval-based-Voice-Conversion-WebUI Easily train a good VC model with voice data < 10 mins! 项目地址: https://gitcode.com/GitHub_Trending/re/Retrie…

2026/6/27 0:04:03阅读更多 →
Layerdivider:3分钟AI智能分层,彻底告别手动抠图时代

Layerdivider:3分钟AI智能分层,彻底告别手动抠图时代

Layerdivider&#xff1a;3分钟AI智能分层&#xff0c;彻底告别手动抠图时代 【免费下载链接】layerdivider A tool to divide a single illustration into a layered structure. 项目地址: https://gitcode.com/gh_mirrors/la/layerdivider 还在为复杂的图像分层工作烦…

2026/6/27 0:04:03阅读更多 →
Tomcat中X-Frame-Options配置实战:防御点击劫持的四种方法与最佳实践

Tomcat中X-Frame-Options配置实战:防御点击劫持的四种方法与最佳实践

1. 项目概述&#xff1a;为什么X-Frame-Options是Web安全的“防盗门”&#xff1f;最近在排查一个老项目的安全审计报告时&#xff0c;又被提到了“点击劫持”风险&#xff0c;矛头直指缺失的X-Frame-Options响应头。这已经不是第一次了&#xff0c;很多开发团队&#xff0c;尤…

2026/6/27 0:04:03阅读更多 →