3. GPIO驱动与板级适配

SAM系列MCU的GPIO子系统由两层构成:PORT控制器负责引脚级的电气属性与复用选择,GPIO控制器负责把引脚组织成可被软件按位操作的端口。理解这两层分工,是写出可移植板级驱动的前提。以SAME70为例,PIOA~PIOE每个端口最多32根引脚,每根引脚在PORT层有独立的配置寄存器组,在GPIO层则通过PIO_ODSR、PIO_OSR、PIO_PDSR等寄存器按位读写。

PORT与GPIO的分工

PORT层的关键寄存器包括PIO_PER(PIO使能)、PIO_OER(输出使能)、PIO_ODR(输出禁止)、PIO_IFER(输入滤波使能)、PIO_SODR/CODR(置位/清零输出)、PIO_PUER/PDER(上拉/下拉使能)、PIO_ABCDSR(外设A/B复用选择)以及PIO_IFSCDR/IFSCER(去抖与毛刺滤波)。GPIO层则通过PIO_ODSR读取或写入输出数据、PIO_PDSR读取引脚实际电平、PIO_ISR读取中断状态。二者共用同一组寄存器基址,区别在于PORT配置改变的是引脚“属性”,GPIO操作改变的是引脚“数据”。

一个常见误区是:把引脚设为输出后直接写PIO_ODSR,却发现电平不变。原因通常是PIO_PER未置位,或PIO_OER未使能,或该引脚被外设复用占用。正确顺序是:先关闭外设复用(PIO_ABCDSR清零对应位),再置PIO_PER,再置PIO_OER,最后写PIO_SODR/CODR。若引脚需要作为外设功能(如UART的TXD),则必须置PIO_PER并设置PIO_ABCDSR选择A或B功能,此时GPIO数据寄存器对该引脚无效。

引脚复用与矩阵映射

SAM的引脚复用不是简单的“一个引脚一个功能”,而是通过PIO_ABCDSR1/2两个寄存器为每根引脚选择A、B、C、D四种外设功能之一。以SAME70的PA9为例,ABCDSR选择不同组合时,该引脚可分别映射为UART0_TXD、SPI0_NPCS1、TWIHS0_TWD或PWMH0。板级适配层必须把“引脚号+功能”映射为具体的ABCDSR位操作,而不是硬编码寄存器地址。

实际工程中建议用一张引脚映射表描述板级连接,例如:

/* board_pinmap.h 示例(假设值,需按实际原理图核对) */
#define LED_STATUS_PIN      PIO_PA0
#define LED_STATUS_FUNC     PIO_PERIPH_A   /* 假设为GPIO,实际按需 */
#define KEY_USER_PIN        PIO_PA1
#define KEY_USER_FUNC       PIO_PERIPH_A
#define UART0_TXD_PIN       PIO_PA9
#define UART0_TXD_FUNC      PIO_PERIPH_A
#define UART0_RXD_PIN       PIO_PA10
#define UART0_RXD_FUNC      PIO_PERIPH_A

驱动层再根据FUNC字段决定是否写ABCDSR。这样当硬件改版把LED从PA0挪到PD5时,只需改头文件,不必动驱动逻辑。

方向与上下拉配置

方向配置由PIO_OER/PIO_ODR完成,但要注意:输出使能前应先确定初始电平,避免上电瞬间产生毛刺。推荐做法是先写PIO_SODR或PIO_CODR设定输出锁存值,再置PIO_OER。输入方向则需根据外部电路决定是否启用上拉或下拉:按键一端接地时启用内部上拉(PIO_PUER),按键一端接VCC时启用内部下拉(PIO_PDER)。内部上下拉典型值为50kΩ~100kΩ,若外部已有强上拉(如4.7kΩ),内部上拉可关闭以降低功耗。

输入滤波对按键尤其重要。PIO_IFER使能输入滤波后,可配合PIO_IFSCER设置去抖采样周期。以SAME70为例,慢时钟约32kHz,若设置IFSCDR分频后采样周期为1ms,则连续多次采样一致才更新PIO_PDSR,可有效滤除机械抖动。但滤波会引入延迟,对高速信号(如外部中断触发)应关闭滤波或缩短采样周期。

中断与唤醒

SAM的GPIO中断由PIO_IER使能,PIO_ISR读取并清除中断标志。每个引脚可独立配置触发方式:PIO_AIMER选择边沿或电平,PIO_ESR选择上升沿/下降沿,PIO_REHLSR选择高电平/低电平。配置顺序建议为:先写PIO_ISR清除旧标志,再设触发条件,最后置PIO_IER。中断服务程序中必须读PIO_ISR以清除标志,否则会反复进入。

低功耗唤醒依赖PIO时钟与唤醒源配置。在Sleep模式下,GPIO中断可配置为唤醒源;在Backup模式下,仅部分引脚(如WKUP0~WKUP13)具备唤醒能力,且需在SUPC中使能对应唤醒输入。板级适配层应把“按键唤醒”抽象为独立接口,屏蔽具体是PIO中断还是WKUP引脚。

按键与LED板级抽象层设计

板级抽象层的目标是让上层应用不直接碰寄存器。建议定义统一接口:

typedef struct {
    uint32_t pin;
    uint32_t func;
    uint8_t  active_level;  /* 0:低有效, 1:高有效 */
} board_gpio_t;

int  board_led_init(const board_gpio_t *led);
void board_led_on(const board_gpio_t *led);
void board_led_off(const board_gpio_t *led);
void board_led_toggle(const board_gpio_t *led);

int  board_key_init(const board_gpio_t *key);
int  board_key_read(const board_gpio_t *key);   /* 返回按下状态 */
int  board_key_register_callback(const board_gpio_t *key,
                                 void (*cb)(void *), void *arg);

LED驱动内部根据active_level决定写SODR还是CODR;按键驱动内部根据active_level和上下拉配置决定PDSR读取后的有效判断。回调注册则把PIO中断服务统一转发到应用层,避免应用直接写PIO_IER。这样同一套应用代码可在不同板卡上复用,只需替换board_pinmap.h和少量初始化参数。

SAM GPIO 板级适配分层结构 应用层 board_led_on() / board_key_read() / 回调注册 板级抽象层 (board_gpio.c / board_pinmap.h) active_level 处理 · 引脚映射表 · 中断回调转发 LED_STATUS_PIN=PA0 · KEY_USER_PIN=PA1 · UART0_TXD=PA9 GPIO 驱动层 PIO_PER / PIO_OER / PIO_ODR PIO_SODR / PIO_CODR / PIO_PDSR PIO_PUER / PIO_PDER / PIO_IFER PIO_IER / PIO_ISR / PIO_AIMER PORT 复用层 PIO_ABCDSR1 / PIO_ABCDSR2 A: UART0_TXD B: SPI0_NPCS1 C: TWIHS0_TWD D: PWMH0 外设功能选择与引脚矩阵映射 硬件层 PA0(LED) · PA1(KEY) · PA9(UART0_TXD) · PA10(UART0_RXD) · 外部上下拉/按键电路 中断 配置顺序:清复用 → 置PER → 设方向 → 设上下拉/滤波 → 配中断 → 写数据

最后强调一个板级适配的实践要点:所有引脚初始化应在系统启动早期集中完成,且初始化函数应幂等——重复调用不改变已配置状态。对于低功耗场景,进入Sleep前应保存GPIO状态,唤醒后按需恢复,避免唤醒瞬间输出错误电平驱动外部电路。按键回调中不宜执行长耗时操作,建议只置标志位,由主循环处理,以保证中断响应实时性。