3. GPIO与按键交互设计

在RA智能终端产品中,按键是最常见的人机交互入口,而LED则是最直接的状态反馈通道。本节围绕RA系列MCU的GPIO端口寄存器、FSP引脚配置、按键消抖算法、中断与轮询方式对比,以及按键控制LED与状态指示的完整实现展开。

一、RA的GPIO端口寄存器模型

RA系列MCU的每个GPIO端口由一组寄存器控制,理解这些寄存器是配置按键和LED的基础。以RA6M5为例,每个端口(P0xx~P9xx)都有以下关键寄存器:

  • PORT.PODR(Port Output Data Register):输出数据寄存器,写1输出高电平,写0输出低电平。
  • PORT.PIDR(Port Input Data Register):输入数据寄存器,读取引脚当前电平状态。
  • PORT.PDR(Port Direction Register):方向寄存器,1为输出,0为输入。
  • PORT.PMR(Port Mode Register):模式寄存器,1为GPIO模式,0为外设功能模式。
  • PORT.PCR(Port Control Register):上拉/下拉控制,写1使能内部上拉。
  • PORT.PCNTR(Port Control Register):N沟道开漏控制等。

在FSP中,这些寄存器通常不直接操作,而是通过R_IOPORT_PinCfg()、R_IOPORT_PinWrite()、R_IOPORT_PinRead()等API间接访问。但理解寄存器模型有助于排查配置异常,例如按键读不到电平,往往是PMR未置1或PCR上拉未使能。

二、FSP引脚配置:按键与LED

在e² studio的FSP配置器中,按键和LED的引脚配置分为三步:

  1. Pins标签页:选择具体引脚,设置Mode为GPIO,配置方向、上拉/下拉、初始电平。
  2. Stacks标签页:添加Input或Output模块,绑定引脚。
  3. 生成代码:FSP自动生成hal_data.c和pin_data.c,初始化代码在R_IOPORT_Open()中完成。

典型配置示例:按键接P105,配置为输入、内部上拉、下降沿中断;LED接P106,配置为输出、初始低电平。按键按下时P105被拉低,松开时由上拉保持高电平。

三、按键消抖算法

机械按键在按下和释放瞬间会产生5~20ms的抖动,直接读取会得到多次跳变。常用消抖算法有两种:

1. 延时确认法:检测到电平变化后延时10~20ms再读一次,若仍为变化后电平则确认。实现简单,但阻塞CPU。

2. 定时采样法:每5ms采样一次,连续3次相同才确认状态变化。适合在定时器中断中执行,不阻塞主循环。

/* 定时采样消抖示例:每5ms调用一次 */
typedef struct {
    uint8_t  stable;      /* 稳定状态 */
    uint8_t  count;       /* 连续相同计数 */
    uint8_t  last;        /* 上次采样值 */
} KeyDebounce;

void Key_Scan(KeyDebounce *k, uint8_t raw) {
    if (raw == k->last) {
        if (k->count < 3) k->count++;
        if (k->count == 3) k->stable = raw;
    } else {
        k->count = 0;
        k->last = raw;
    }
}

该算法中,stable为消抖后的稳定电平,count达到3即确认,对应15ms消抖窗口。实际项目中可根据按键特性调整采样周期和确认次数。

四、中断与轮询方式对比

按键检测有两种基本方式,选择取决于系统实时性要求和功耗约束:

对比项轮询方式中断方式
CPU占用持续占用,需在主循环中反复扫描仅在按键触发时响应,空闲时释放CPU
实时性取决于扫描周期,通常5~20ms微秒级响应,适合快速按键
消抖实现可在扫描函数中集成定时采样中断中不宜延时,需配合定时器或状态机
功耗较高,CPU无法进入低功耗可配合低功耗模式,按键唤醒
适用场景按键少、实时性要求低多按键、低功耗、快速响应

在RA智能终端中,若按键数量少且主循环有空闲,轮询足够;若需要低功耗待机或按键响应要求高,应使用IRQ中断配合定时器消抖。

五、按键控制LED与状态指示实现

下面给出一个完整的实现思路:按键P105控制LED P106亮灭,同时用LED闪烁表示系统状态。

/* FSP配置:P105输入上拉,P106输出 */
#include "hal_data.h"

volatile uint8_t g_key_pressed = 0;

/* 按键中断回调 */
void key_irq_callback(external_irq_callback_args_t *p_args) {
    (void)p_args;
    g_key_pressed = 1;
}

void hal_entry(void) {
    R_IOPORT_Open(&g_ioport_ctrl, &g_bsp_pin_cfg);
    R_ICU_ExternalIrqOpen(&g_external_irq105_ctrl,
                          &g_external_irq105_cfg);
    R_ICU_ExternalIrqEnable(&g_external_irq105_ctrl);

    uint8_t led_state = 0;
    while (1) {
        if (g_key_pressed) {
            g_key_pressed = 0;
            led_state = !led_state;
            R_IOPORT_PinWrite(&g_ioport_ctrl,
                              BSP_IO_PORT_01_PIN_06,
                              led_state ? BSP_IO_LEVEL_HIGH
                                        : BSP_IO_LEVEL_LOW);
        }
        /* 状态指示:LED以1Hz闪烁表示运行中 */
        R_BSP_SoftwareDelay(500, BSP_DELAY_UNITS_MILLISECONDS);
        R_IOPORT_PinWrite(&g_ioport_ctrl,
                          BSP_IO_PORT_01_PIN_06,
                          BSP_IO_LEVEL_HIGH);
        R_BSP_SoftwareDelay(500, BSP_DELAY_UNITS_MILLISECONDS);
        R_IOPORT_PinWrite(&g_ioport_ctrl,
                          BSP_IO_PORT_01_PIN_06,
                          BSP_IO_LEVEL_LOW);
    }
}

上述代码中,按键中断置位g_key_pressed,主循环检测后翻转LED状态。但注意:主循环中的闪烁会覆盖按键翻转结果,实际项目中应将状态指示与按键控制分离,例如用双LED或PWM呼吸灯表示不同状态。

调试建议:若按键无响应,先用万用表测量P105在按下/松开时的电平变化;若电平正常但程序无反应,检查FSP中IRQ是否使能、回调函数是否注册、PMR是否配置为GPIO。若LED不亮,检查P106方向是否为输出、初始电平是否与硬件匹配(共阳/共阴)。

RA GPIO按键控制LED与状态指示 — 信号链与配置关系 按键硬件 P105 + 内部上拉 按下→低电平 松开→高电平 消抖处理 5ms采样 / 3次确认 15ms消抖窗口 输出稳定电平 中断/轮询 IRQ下降沿触发 或主循环扫描 置位按键标志 主 循 环 LED硬件 P106 输出 高电平→亮 低电平→灭 状态指示 1Hz闪烁=运行中 常亮=按键锁定 熄灭=待机 输出控制 R_IOPORT_PinWrite 翻转LED状态 更新PODR寄存器 结 果 FSP配置关系 Pins标签页:P105输入上拉 / P106输出 Stacks标签页:IRQ105下降沿 + IOPORT模块 生成代码:pin_data.c / hal_data.c 初始化:R_IOPORT_Open() + R_ICU_ExternalIrqOpen() 按键事件触发LED状态翻转

上图展示了从按键硬件到LED输出的完整信号链:按键电平变化经消抖处理后,通过中断或轮询方式被主循环捕获,最终调用R_IOPORT_PinWrite()更新PODR寄存器,驱动LED亮灭。同时,状态指示逻辑独立于按键控制,用闪烁频率表达系统运行状态。实际开发中,建议将按键扫描和LED控制分别封装为独立模块,通过事件标志或消息队列解耦,便于后续扩展多按键、多LED的复杂交互场景。