3. GPIO 与基础外设:从按键到中断的实战之路
GPIO 是嵌入式系统最基础、也最容易被轻视的外设。说白了,它就是芯片和外界打交道的「手脚」——拉高拉低、读个电平、触发个中断。但就是这看似简单的操作,我在医疗项目中踩过的坑可不少。
记得有一次,心电采集设备的按键在 ICU 里频繁误触发,护士按一次启动,设备却重启了三次。查到最后,就是 GPIO 消抖没做好。所以这一章,咱们把 GPIO 的输入输出、按键消抖、LED 指示、中断优先级这些基本功,一次性讲透。
3.1 GPIO 工作模式:不只是输入输出
很多新手以为 GPIO 就是「读」和「写」。其实在 STM32 这类 MCU 上,每个 GPIO 引脚可以配置成 8 种模式。我习惯把它们分成四大类:
| 模式类别 | 典型应用 | 注意事项 |
|---|---|---|
| 推挽输出 | LED 驱动、蜂鸣器 | 注意灌电流限制,一般不超过 20mA |
| 开漏输出 | I²C 总线、电平转换 | 必须外接上拉电阻 |
| 浮空输入 | 外部按键(需外接上拉) | 电平不确定,易受干扰 |
| 上拉/下拉输入 | 按键检测、传感器数字输入 | 内部上拉约 40kΩ,弱驱动场景够用 |
| 模拟输入 | ADC 采样 | 关闭数字功能,降低功耗 |
| 复用推挽/开漏 | USART、SPI、定时器输出 | 由外设控制,GPIO 寄存器失效 |
3.2 按键消抖:硬件与软件的双保险
机械按键按下时,触点会弹跳 5-20ms。如果不处理,一次按下可能被识别成多次。我在心电监护仪的「开始/停止」按键上就吃过这个亏——护士按一次,设备认为按了三次,直接死机。
消抖有两种思路:
3.2.1 硬件消抖
在按键两端并联一个 0.1μF 电容,利用电容的充放电特性滤除毛刺。优点是零 CPU 开销,缺点是增加 BOM 成本,且电容值需要根据按键机械特性调整。
3.2.2 软件消抖
这是最常用的方法。检测到电平变化后,延时 10-20ms 再读一次。如果电平一致,才确认按键动作。
// 软件消抖示例(STM32 HAL 库风格)
uint8_t Debounce_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
if (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET) // 第一次检测到按下
{
HAL_Delay(15); // 延时 15ms,避开抖动区间
if (HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET) // 再次确认
{
return 1; // 确认按下
}
}
return 0; // 抖动或未按下
}
3.3 LED 指示:驱动与限流
LED 看起来简单,但医疗设备对指示灯的亮度、颜色、闪烁频率都有严格要求。比如报警指示灯必须是红色,闪烁频率 1-2Hz,亮度不低于 100mcd。
驱动 LED 时,限流电阻的计算公式:
R = (VCC - VF) / IF
其中 VF 是 LED 正向压降(红色约 2V,蓝色约 3.3V),IF 是工作电流(通常 5-10mA)。
举个例子:VCC=3.3V,红色 LED,IF=5mA,则 R = (3.3 - 2) / 0.005 = 260Ω,取标称值 270Ω。
3.4 中断优先级配置:别让紧急事件排队
医疗系统中,中断优先级配置直接决定系统的实时响应能力。比如心率检测中断必须比串口通信中断优先级高——心跳丢了可是要出人命的。
以 ARM Cortex-M 系列为例,中断优先级分为抢占优先级和子优先级。配置时遵循两个原则:
- 时间敏感的中断优先级最高:比如定时器采样、外部触发信号。
- 非关键中断让路:比如按键中断、LED 刷新,可以放在低优先级。
优先级分组由 SCB->AIRCR 寄存器控制,常见分组方式:
| 分组 | 抢占优先级位数 | 子优先级位数 | 适用场景 |
|---|---|---|---|
| NVIC_PriorityGroup_0 | 0 位 | 4 位 | 所有中断同级,无嵌套 |
| NVIC_PriorityGroup_2 | 2 位 | 2 位 | 4 级抢占,每级 4 个子优先级 |
| NVIC_PriorityGroup_4 | 4 位 | 0 位 | 16 级抢占,无子优先级 |
我个人习惯在医疗项目中使用 NVIC_PriorityGroup_2,既保留了足够的抢占层级,又能在同一优先级内做子优先级排序。
// 中断优先级配置示例
HAL_NVIC_SetPriorityGrouping(NVIC_PriorityGroup_2);
// 心率检测中断:抢占优先级 0,子优先级 0(最高)
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
// 按键中断:抢占优先级 2,子优先级 1(较低)
HAL_NVIC_SetPriority(EXTI1_IRQn, 2, 1);
HAL_NVIC_EnableIRQ(EXTI1_IRQn);
3.5 实战中的抗干扰设计
医疗设备经常面临强电磁干扰环境——电刀、除颤仪、MRI 设备。GPIO 作为最外层的接口,必须做好防护。
- 输入引脚加 TVS 管:防止静电放电损坏 GPIO。
- 长线缆使用差分信号:比如 RS-485 或 CAN,避免单端 GPIO 直接驱动长线。
- 未使用的 GPIO 引脚:配置为模拟输入或下拉输出,不要悬空。悬空引脚会像天线一样引入噪声。
- 软件滤波:在中断服务函数中连续采样 3 次,取多数值,避免单次误触发。