3. GPIO驱动开发:GPIO模式配置与中断实战

各位同学,今天我们来聊聊医疗贴片开发中最基础也最容易踩坑的部分——GPIO驱动。说实话,很多人觉得GPIO不就是拉高拉低嘛,有什么好讲的?但我在医疗设备项目中吃过不少亏,才明白一个道理:GPIO用好了,系统就稳了一半;用不好,半夜被叫起来改Bug的就是你。

3.1 GPIO模式配置:推挽与开漏

先说说推挽输出。这是最常用的模式,内部有两个MOS管,一个推高,一个拉低。输出高电平时,上管导通,直接怼到VDD;输出低电平时,下管导通,拉到GND。驱动能力强,响应快。

我个人的习惯是:驱动LED、蜂鸣器这类负载,直接用推挽。但要注意一点——两个管不能同时导通,否则就是短路。芯片内部有保护,但别指望它。

推挽输出特点:

  • 输出高低电平都有驱动能力
  • 适合驱动数字输入引脚
  • 不适合多设备共用一条线(会打架)

再说开漏输出。这个模式只拉低,不拉高。想输出高电平?得靠外部上拉电阻。我在做心电贴片时,I2C总线必须用开漏,因为多个设备要共用SDA和SCL线。你想想看,如果都用推挽,一个设备拉高,另一个拉低,那不就短路了吗?

// 推挽模式配置示例(STM32 HAL库)
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 开漏模式配置
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;  // 开漏输出
GPIO_InitStruct.Pull = GPIO_PULLUP;          // 外部上拉
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

3.2 上拉与下拉:别让引脚悬空

嗯,这里要注意。输入引脚最怕什么?悬空!悬空的引脚电平不确定,外界一点干扰就能让读数乱跳。我在做血糖仪按键检测时,就遇到过这个问题——手指还没碰到按键,电平就自己跳了。

解决办法就是上拉或下拉电阻。上拉电阻把默认电平拉到高,按键按下才拉低;下拉则相反。芯片内部通常有可配置的上拉/下拉电阻,阻值一般在40kΩ左右。

模式 默认电平 适用场景
上拉输入 高电平 按键接地、开漏总线
下拉输入 低电平 按键接VCC、唤醒引脚
浮空输入 不确定 外部已有上下拉

我的经验:医疗贴片功耗敏感,内部上拉电阻一直耗电。如果引脚长期处于固定状态,建议用外部大阻值电阻(100kΩ以上),省电。

3.3 中断配置:别让CPU空转

为什么用中断?说白了,就是让CPU该干嘛干嘛,别一直轮询等着。医疗贴片里,按键、传感器数据就绪、通信完成,这些都用中断。我见过有人用轮询检测按键,CPU占用率直接飙到30%,电池半天就没了。

中断配置有几个关键点:

  • 触发方式:上升沿、下降沿、双边沿、高电平、低电平。按键一般用下降沿(按下触发)或双边沿(按下和松开都触发)。
  • 优先级:医疗设备中,安全相关的中断(如电池过放)优先级要高。按键这种,中等就行。
  • 中断服务函数:越短越好,别在里面做复杂运算。我习惯只设一个标志位,主循环去处理。
// 外部中断配置示例
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_13;           // 按键引脚
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发
GPIO_InitStruct.Pull = GPIO_PULLUP;          // 内部上拉
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

// 配置中断优先级
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

// 中断服务函数(简短!)
void EXTI15_10_IRQHandler(void)
{
    if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET)
    {
        // 只设标志,不做处理
        key_pressed_flag = 1;
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);
    }
}

警告:中断服务函数里别做延时!别调用printf!别操作复杂外设!我曾经在中断里调了个I2C读取,结果死锁了,整台设备卡死。血的教训。

3.4 按键消抖实战:别让一次按下变十次

机械按键有个毛病——按下时触点会弹跳,持续5-20ms。如果不处理,一次按下可能触发十几次中断。你想想看,医疗贴片上的启动键如果这样,病人按一次,设备重启十次,那还得了?

消抖方法有两种:

  • 硬件消抖:加RC滤波电路,电容充电慢,滤掉毛刺。适合对功耗不敏感的场景。
  • 软件消抖:检测到电平变化后,延时10-20ms再读一次。确认电平稳定了,才认为是有效按键。

我个人偏爱软件消抖,因为医疗贴片空间有限,加电容太占地方。下面是我常用的消抖代码:

// 按键消抖函数
uint8_t Key_Debounce(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    if(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET)  // 第一次检测到按下
    {
        delay_ms(10);  // 延时10ms,避开抖动期
        if(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET)  // 再次确认
        {
            // 等待按键松开(可选)
            while(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) == GPIO_PIN_RESET);
            return 1;  // 有效按键
        }
    }
    return 0;  // 抖动或误触
}

实战要点:

  • 延时时间根据按键特性调整,一般10-20ms。我测过几种国产按键,有的要25ms才稳。
  • 如果用了中断触发,消抖要在中断里做吗?不建议。中断里做延时会阻塞其他中断。我习惯在中断里设标志,主循环里调用消抖函数。
  • 长按和短按怎么区分?记录按下时间,超过1秒算长按。这个在医疗贴片的“关机确认”场景很常用。

最后说一句,GPIO看似简单,但它是硬件和软件的交汇点。配置错了,轻则功能异常,重则烧芯片。我刚开始做医疗设备时,就因为开漏输出忘了加上拉,导致I2C总线一直卡死,查了两天才找到原因。嗯,从那以后,我每次配GPIO都会拿示波器看一眼波形,确认没问题再往下走。

下一章我们讲定时器驱动,这东西在医疗贴片里用来做PWM驱动振动马达和ADC采样触发,也是重头戏。各位先把GPIO练熟,后面才好办。