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,以确认输入路径完整。