4. 轮询与中断对比
任务定义
本案例在STM32F103最小系统板上,使用一个按键(PA0,外部下拉)和一个LED(PC13,低电平点亮),分别以轮询方式和外部中断方式实现按键检测:每次按下按键,LED状态翻转。最终通过代码结构对比两种方式的CPU占用差异,并通过示波器或逻辑分析仪测量按键按下到LED翻转的响应延迟。
硬件连接
实现代码
以下代码包含两个独立工程文件,分别对应轮询与中断方式。两者共用相同的GPIO初始化逻辑,差异仅在按键检测机制。
// ============ 方式一:轮询检测 (polling.c) ============
#include "stm32f1xx_hal.h"
void GPIO_Init_All(void) {
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitTypeDef gpio = {0};
// PA0 输入,下拉
gpio.Pin = GPIO_PIN_0;
gpio.Mode = GPIO_MODE_INPUT;
gpio.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &gpio);
// PC13 推挽输出
gpio.Pin = GPIO_PIN_13;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &gpio);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); // LED灭
}
// 简单消抖:连续读取20次(约2ms@72MHz)
uint8_t Debounce_Read(void) {
uint8_t stable = 0;
for (int i = 0; i < 20; i++) {
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET)
stable++;
// 空转延时约100us
for (volatile int d = 0; d < 1000; d++);
}
return (stable > 15) ? 1 : 0; // 多数一致则有效
}
int main_polling(void) {
HAL_Init();
GPIO_Init_All();
uint8_t last_state = 0;
uint8_t led_on = 0;
while (1) {
uint8_t cur = Debounce_Read();
// 检测上升沿(按下瞬间)
if (cur == 1 && last_state == 0) {
led_on = !led_on;
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13,
led_on ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
last_state = cur;
// 主循环空转,模拟其他任务占用CPU
for (volatile int t = 0; t < 5000; t++);
}
}
// ============ 方式二:外部中断 (exti.c) ============
#include "stm32f1xx_hal.h"
volatile uint8_t g_led_on = 0;
void GPIO_Init_All(void) {
// 与轮询方式相同的GPIO初始化代码
// ...(同上,PA0输入下拉,PC13输出)
}
void EXTI0_IRQHandler(void) {
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
// 消抖:中断触发后延时10ms再确认
HAL_Delay(10);
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
g_led_on = !g_led_on;
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13,
g_led_on ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
}
}
int main_exti(void) {
HAL_Init();
GPIO_Init_All();
// 配置EXTI0中断
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
// 配置PA0为外部中断模式(上升沿触发)
GPIO_InitTypeDef gpio = {0};
gpio.Pin = GPIO_PIN_0;
gpio.Mode = GPIO_MODE_IT_RISING;
gpio.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &gpio);
while (1) {
// 主循环完全空闲,可执行其他任务
__WFI(); // 等待中断,CPU进入休眠
}
}
CPU占用对比方法
在两种方式的while(1)主循环中,各放置一个GPIO翻转指令(使用PB0作为调试引脚),用示波器测量PB0的波形占空比:
- 轮询方式:主循环持续运行,PB0输出连续方波,占空比接近50%,表明CPU始终被占用。
- 中断方式:主循环执行
__WFI()后CPU休眠,PB0仅在中断唤醒瞬间翻转一次,波形为稀疏脉冲,占空比趋近0%。
响应速度测量
将示波器CH1接PA0(按键输入),CH2接PC13(LED输出)。按下按键,测量CH1上升沿到CH2电平翻转的时间差:
- 轮询方式:延迟 = 消抖时间(约2ms) + 主循环空转时间(约0.5ms) ≈ 2.5ms,且随主循环负载变化而波动。
- 中断方式:延迟 = 中断响应(约1μs) + HAL_Delay消抖(10ms) ≈ 10ms,但该延迟固定,不受主循环负载影响。
验证与判定标准
- 编译烧录两种固件,分别按下按键,LED均应每次翻转一次,无漏检、无抖动误触发。
- 用示波器观察PB0调试引脚:轮询方式为连续方波,中断方式为稀疏脉冲,证明CPU占用差异。
- 测量按键按下到LED翻转的延迟:轮询方式延迟随主循环代码量增加而增大;中断方式延迟恒定(约10ms),且主循环即使执行重负载任务(如大量浮点运算),响应延迟不变。
- 判定通过标准:两种方式均能可靠检测按键;示波器波形证明中断方式CPU空闲率显著高于轮询方式;中断方式响应延迟不随主循环负载变化。