2. 硬件抽象层(HAL) GPIO驱动封装

任务定义

本案例面向MCU嵌入式软件分层架构中的硬件抽象层设计,目标是将寄存器级GPIO操作封装为统一的HAL接口,使上层应用无需感知具体芯片寄存器差异。完成本案例后,你将获得一个可直接编译运行的GPIO HAL驱动,包含初始化、读写、翻转与中断回调注册功能,并通过一个模拟硬件层的测试文件验证封装正确性。

HAL接口设计

HAL层向上层提供以下核心接口,所有操作均以端口号和引脚号作为逻辑标识,屏蔽寄存器细节:

  • HAL_GPIO_Init:配置引脚方向(输入/输出)、默认电平及是否使能内部上拉;
  • HAL_GPIO_WritePin:输出高/低电平;
  • HAL_GPIO_ReadPin:读取引脚电平;
  • HAL_GPIO_TogglePin:翻转引脚输出状态;
  • HAL_GPIO_RegisterCallback:注册引脚中断回调函数(用于演示外部中断场景)。

分层结构与代码实现

工程分为三层:硬件寄存器层(模拟MCU寄存器)、HAL层(本案例核心封装)、应用层(演示调用)。硬件层使用结构体模拟寄存器映射,便于在PC上验证逻辑正确性,实际移植时仅需替换该层为具体芯片的头文件定义。


// hal_gpio.h —— HAL层接口声明
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include <stdint.h>

typedef enum {
    HAL_GPIO_DIR_IN = 0,
    HAL_GPIO_DIR_OUT
} HAL_GPIO_Dir;

typedef enum {
    HAL_GPIO_LEVEL_LOW = 0,
    HAL_GPIO_LEVEL_HIGH
} HAL_GPIO_Level;

typedef enum {
    HAL_GPIO_PULL_NONE = 0,
    HAL_GPIO_PULL_UP
} HAL_GPIO_Pull;

typedef struct {
    uint8_t port;          // 端口号 0~3
    uint8_t pin;           // 引脚号 0~7
    HAL_GPIO_Dir dir;      // 方向
    HAL_GPIO_Level init_level; // 初始电平
    HAL_GPIO_Pull pull;    // 上拉配置
} HAL_GPIO_Config;

typedef void (*HAL_GPIO_Callback)(uint8_t port, uint8_t pin);

void HAL_GPIO_Init(const HAL_GPIO_Config *cfg);
void HAL_GPIO_WritePin(uint8_t port, uint8_t pin, HAL_GPIO_Level level);
HAL_GPIO_Level HAL_GPIO_ReadPin(uint8_t port, uint8_t pin);
void HAL_GPIO_TogglePin(uint8_t port, uint8_t pin);
void HAL_GPIO_RegisterCallback(uint8_t port, uint8_t pin, HAL_GPIO_Callback cb);

#endif

// hal_gpio.c —— HAL层实现
#include "hal_gpio.h"
#include "hw_gpio.h"   // 硬件寄存器层

static HAL_GPIO_Callback s_callbacks[4][8] = {{0}};

void HAL_GPIO_Init(const HAL_GPIO_Config *cfg) {
    if (!cfg || cfg->port > 3 || cfg->pin > 7) return;
    HW_GPIO_SetDir(cfg->port, cfg->pin, 
                   (cfg->dir == HAL_GPIO_DIR_OUT) ? HW_GPIO_DIR_OUT : HW_GPIO_DIR_IN);
    HW_GPIO_SetPull(cfg->port, cfg->pin, 
                    (cfg->pull == HAL_GPIO_PULL_UP) ? HW_GPIO_PULL_UP : HW_GPIO_PULL_NONE);
    if (cfg->dir == HAL_GPIO_DIR_OUT) {
        HW_GPIO_Write(cfg->port, cfg->pin, cfg->init_level);
    }
}

void HAL_GPIO_WritePin(uint8_t port, uint8_t pin, HAL_GPIO_Level level) {
    if (port > 3 || pin > 7) return;
    HW_GPIO_Write(port, pin, level);
}

HAL_GPIO_Level HAL_GPIO_ReadPin(uint8_t port, uint8_t pin) {
    if (port > 3 || pin > 7) return HAL_GPIO_LEVEL_LOW;
    return (HAL_GPIO_Level)HW_GPIO_Read(port, pin);
}

void HAL_GPIO_TogglePin(uint8_t port, uint8_t pin) {
    if (port > 3 || pin > 7) return;
    HAL_GPIO_Level cur = HAL_GPIO_ReadPin(port, pin);
    HW_GPIO_Write(port, pin, (cur == HAL_GPIO_LEVEL_HIGH) ? HAL_GPIO_LEVEL_LOW : HAL_GPIO_LEVEL_HIGH);
}

void HAL_GPIO_RegisterCallback(uint8_t port, uint8_t pin, HAL_GPIO_Callback cb) {
    if (port > 3 || pin > 7) return;
    s_callbacks[port][pin] = cb;
}

// 由硬件层在中断发生时调用
void HAL_GPIO_IRQHandler(uint8_t port, uint8_t pin) {
    if (s_callbacks[port][pin]) {
        s_callbacks[port][pin](port, pin);
    }
}

// hw_gpio.h —— 模拟硬件寄存器层(实际项目中替换为芯片头文件)
#ifndef HW_GPIO_H
#define HW_GPIO_H

#include <stdint.h>

typedef enum {
    HW_GPIO_DIR_IN = 0,
    HW_GPIO_DIR_OUT
} HW_GPIO_Dir;

typedef enum {
    HW_GPIO_PULL_NONE = 0,
    HW_GPIO_PULL_UP
} HW_GPIO_Pull;

void HW_GPIO_SetDir(uint8_t port, uint8_t pin, HW_GPIO_Dir dir);
void HW_GPIO_SetPull(uint8_t port, uint8_t pin, HW_GPIO_Pull pull);
void HW_GPIO_Write(uint8_t port, uint8_t pin, uint8_t level);
uint8_t HW_GPIO_Read(uint8_t port, uint8_t pin);

#endif

// hw_gpio.c —— 模拟寄存器实现,使用静态数组模拟端口寄存器
#include "hw_gpio.h"

static uint8_t s_dir[4] = {0};    // 每bit对应一个引脚,1=输出
static uint8_t s_pull[4] = {0};   // 每bit对应一个引脚,1=上拉
static uint8_t s_out[4] = {0};    // 输出寄存器
static uint8_t s_in[4] = {0};     // 输入寄存器(模拟外部电平)

void HW_GPIO_SetDir(uint8_t port, uint8_t pin, HW_GPIO_Dir dir) {
    if (dir == HW_GPIO_DIR_OUT) s_dir[port] |= (1 << pin);
    else s_dir[port] &= ~(1 << pin);
}

void HW_GPIO_SetPull(uint8_t port, uint8_t pin, HW_GPIO_Pull pull) {
    if (pull == HW_GPIO_PULL_UP) s_pull[port] |= (1 << pin);
    else s_pull[port] &= ~(1 << pin);
}

void HW_GPIO_Write(uint8_t port, uint8_t pin, uint8_t level) {
    if (level) s_out[port] |= (1 << pin);
    else s_out[port] &= ~(1 << pin);
    // 输出模式下,输入寄存器跟随输出值(便于回读)
    if (s_dir[port] & (1 << pin)) {
        if (level) s_in[port] |= (1 << pin);
        else s_in[port] &= ~(1 << pin);
    }
}

uint8_t HW_GPIO_Read(uint8_t port, uint8_t pin) {
    // 输入模式读取外部电平,输出模式读取输出锁存值
    if (s_dir[port] & (1 << pin)) {
        return (s_out[port] >> pin) & 0x01;
    } else {
        return (s_in[port] >> pin) & 0x01;
    }
}

// 测试辅助函数:模拟外部输入电平变化
void HW_GPIO_SimulateExternalInput(uint8_t port, uint8_t pin, uint8_t level) {
    if (level) s_in[port] |= (1 << pin);
    else s_in[port] &= ~(1 << pin);
}

// main.c —— 应用层演示与验证
#include <stdio.h>
#include "hal_gpio.h"

// 中断回调示例函数
void on_button_pressed(uint8_t port, uint8_t pin) {
    printf("[回调] 引脚 P%d.%d 中断触发\n", port, pin);
}

int main(void) {
    // 配置LED引脚(端口0,引脚0)为推挽输出,初始低电平
    HAL_GPIO_Config led_cfg = {
        .port = 0, .pin = 0,
        .dir = HAL_GPIO_DIR_OUT,
        .init_level = HAL_GPIO_LEVEL_LOW,
        .pull = HAL_GPIO_PULL_NONE
    };
    HAL_GPIO_Init(&led_cfg);

    // 配置按键引脚(端口1,引脚3)为输入,使能上拉
    HAL_GPIO_Config btn_cfg = {
        .port = 1, .pin = 3,
        .dir = HAL_GPIO_DIR_IN,
        .init_level = HAL_GPIO_LEVEL_LOW,
        .pull = HAL_GPIO_PULL_UP
    };
    HAL_GPIO_Init(&btn_cfg);

    // 注册按键中断回调
    HAL_GPIO_RegisterCallback(1, 3, on_button_pressed);

    // 验证输出功能
    HAL_GPIO_WritePin(0, 0, HAL_GPIO_LEVEL_HIGH);
    printf("LED输出高电平,读取值=%d(预期1)\n", HAL_GPIO_ReadPin(0, 0));

    HAL_GPIO_TogglePin(0, 0);
    printf("翻转后读取值=%d(预期0)\n", HAL_GPIO_ReadPin(0, 0));

    // 验证输入功能(模拟外部输入低电平触发中断)
    // 注意:实际中断由硬件触发,此处直接调用HAL_GPIO_IRQHandler模拟
    HAL_GPIO_IRQHandler(1, 3);
    printf("输入引脚默认读取值=%d(上拉后预期1)\n", HAL_GPIO_ReadPin(1, 3));

    return 0;
}

编译与运行

将上述5个文件(hal_gpio.h、hal_gpio.c、hw_gpio.h、hw_gpio.c、main.c)置于同一目录,使用任意C编译器编译。以gcc为例:

gcc -o hal_demo main.c hal_gpio.c hw_gpio.c -I.
./hal_demo

验证方法与判定标准

程序运行后,观察标准输出是否依次出现以下内容:


LED输出高电平,读取值=1(预期1)
翻转后读取值=0(预期0)
[回调] 引脚 P1.3 中断触发
输入引脚默认读取值=1(上拉后预期1)

判定标准:四条输出全部出现且数值与括号内预期一致,说明HAL层正确完成了引脚初始化、输出写入、电平翻转、输入读取和回调注册功能。若需验证输入引脚的外部电平变化,可在main.c中调用HW_GPIO_SimulateExternalInput(1, 3, 0)后再读取,观察返回值变为0,以确认输入路径完整。

HAL GPIO驱动封装——分层调用与验证路径 应用层 (main.c) HAL_GPIO_Init() HAL_GPIO_WritePin() HAL_GPIO_ReadPin() HAL_GPIO_TogglePin() HAL层 (hal_gpio.c) 接口封装与参数校验 回调函数注册表 HAL_GPIO_IRQHandler() 逻辑层与寄存器层解耦 硬件寄存器层 (hw_gpio.c) 寄存器位操作 方向/上拉/输出控制 模拟外部输入 API调用 寄存器操作 验证观测点(printf输出) ① LED输出高电平,读取值=1(预期1) ② 翻转后读取值=0(预期0) ③ [回调] 引脚 P1.3 中断触发 ④ 输入引脚默认读取值=1(上拉后预期1) 中断回调注册