1. 硬件抽象层(HAL)基础框架搭建
任务目标
本案例要求设计一个轻量级硬件抽象层(HAL),将GPIO与UART的底层寄存器操作封装为统一API。上层应用只依赖HAL接口,不直接接触寄存器;移植到新开发板时仅需替换HAL实现文件,应用代码零修改。
环境与依赖
- 硬件:任意两款不同MCU开发板(示例选用STM32F103与ESP32,但代码以通用C编写,仅需适配各自寄存器)
- 工具链:arm-none-eabi-gcc(STM32)、xtensa-esp32-elf-gcc(ESP32),或任何支持C99的编译器
- 无第三方库依赖,纯寄存器操作
HAL接口定义(hal.h)
#ifndef HAL_H
#define HAL_H
#include <stdint.h>
/* GPIO方向 */
typedef enum {
HAL_GPIO_INPUT = 0,
HAL_GPIO_OUTPUT = 1
} hal_gpio_dir_t;
/* GPIO电平 */
typedef enum {
HAL_GPIO_LOW = 0,
HAL_GPIO_HIGH = 1
} hal_gpio_val_t;
/* UART配置 */
typedef struct {
uint32_t baudrate;
uint8_t data_bits; /* 8或9 */
uint8_t stop_bits; /* 1或2 */
uint8_t parity; /* 0:无, 1:奇, 2:偶 */
} hal_uart_cfg_t;
/* 平台无关的HAL API */
void hal_gpio_init(uint8_t port, uint8_t pin, hal_gpio_dir_t dir);
void hal_gpio_write(uint8_t port, uint8_t pin, hal_gpio_val_t val);
hal_gpio_val_t hal_gpio_read(uint8_t port, uint8_t pin);
void hal_uart_init(uint8_t uart_id, const hal_uart_cfg_t *cfg);
void hal_uart_send_byte(uint8_t uart_id, uint8_t data);
uint8_t hal_uart_receive_byte(uint8_t uart_id);
#endif /* HAL_H */
STM32实现(hal_stm32.c)
#include "hal.h"
#include "stm32f1xx.h" /* 寄存器定义 */
/* GPIO端口基址映射:port 0->GPIOA, 1->GPIOB, 2->GPIOC */
static GPIO_TypeDef *const gpio_ports[] = {GPIOA, GPIOB, GPIOC};
void hal_gpio_init(uint8_t port, uint8_t pin, hal_gpio_dir_t dir) {
GPIO_TypeDef *gpio = gpio_ports[port];
uint32_t mode_mask = (dir == HAL_GPIO_OUTPUT) ? 0x1 : 0x4;
/* 使能时钟(简化:默认已开启) */
gpio->CRL &= ~(0xF << (pin * 4));
gpio->CRL |= (mode_mask << (pin * 4));
}
void hal_gpio_write(uint8_t port, uint8_t pin, hal_gpio_val_t val) {
GPIO_TypeDef *gpio = gpio_ports[port];
if (val == HAL_GPIO_HIGH)
gpio->BSRR = (1 << pin);
else
gpio->BRR = (1 << pin);
}
hal_gpio_val_t hal_gpio_read(uint8_t port, uint8_t pin) {
GPIO_TypeDef *gpio = gpio_ports[port];
return (gpio->IDR & (1 << pin)) ? HAL_GPIO_HIGH : HAL_GPIO_LOW;
}
/* UART1实现 */
void hal_uart_init(uint8_t uart_id, const hal_uart_cfg_t *cfg) {
if (uart_id != 0) return; /* 仅支持UART1 */
USART1->BRR = 72000000 / cfg->baudrate;
USART1->CR1 = 0x200C; /* UE=1, TE=1, RE=1 */
}
void hal_uart_send_byte(uint8_t uart_id, uint8_t data) {
if (uart_id != 0) return;
while (!(USART1->SR & 0x80)); /* 等待TXE */
USART1->DR = data;
}
uint8_t hal_uart_receive_byte(uint8_t uart_id) {
if (uart_id != 0) return 0;
while (!(USART1->SR & 0x20)); /* 等待RXNE */
return (uint8_t)USART1->DR;
}
ESP32实现(hal_esp32.c)
#include "hal.h"
#include "esp32_reg.h" /* 寄存器定义 */
/* GPIO寄存器基址 */
#define GPIO_BASE 0x3FF44000
#define GPIO_OUT_REG (*(volatile uint32_t *)(GPIO_BASE + 0x04))
#define GPIO_IN_REG (*(volatile uint32_t *)(GPIO_BASE + 0x3C))
#define GPIO_ENABLE_REG (*(volatile uint32_t *)(GPIO_BASE + 0x20))
void hal_gpio_init(uint8_t port, uint8_t pin, hal_gpio_dir_t dir) {
if (port != 0) return; /* 仅支持port 0 */
if (dir == HAL_GPIO_OUTPUT)
GPIO_ENABLE_REG |= (1 << pin);
else
GPIO_ENABLE_REG &= ~(1 << pin);
}
void hal_gpio_write(uint8_t port, uint8_t pin, hal_gpio_val_t val) {
if (port != 0) return;
if (val == HAL_GPIO_HIGH)
GPIO_OUT_REG |= (1 << pin);
else
GPIO_OUT_REG &= ~(1 << pin);
}
hal_gpio_val_t hal_gpio_read(uint8_t port, uint8_t pin) {
if (port != 0) return HAL_GPIO_LOW;
return (GPIO_IN_REG & (1 << pin)) ? HAL_GPIO_HIGH : HAL_GPIO_LOW;
}
/* UART0实现 */
#define UART0_BASE 0x3FF40000
#define UART_FIFO_REG (*(volatile uint32_t *)(UART0_BASE + 0x00))
#define UART_STATUS_REG (*(volatile uint32_t *)(UART0_BASE + 0x1C))
void hal_uart_init(uint8_t uart_id, const hal_uart_cfg_t *cfg) {
if (uart_id != 0) return;
/* 配置波特率分频(简化:假设时钟80MHz) */
*(volatile uint32_t *)(UART0_BASE + 0x10) = 80000000 / cfg->baudrate;
}
void hal_uart_send_byte(uint8_t uart_id, uint8_t data) {
if (uart_id != 0) return;
while (UART_STATUS_REG & 0x100); /* TX_FIFO满则等待 */
UART_FIFO_REG = data;
}
uint8_t hal_uart_receive_byte(uint8_t uart_id) {
if (uart_id != 0) return 0;
while (!(UART_STATUS_REG & 0x1)); /* RX_FIFO空则等待 */
return (uint8_t)UART_FIFO_REG;
}
应用层演示(main.c)
#include "hal.h"
/* 应用代码完全不感知底层平台 */
void app_demo(void) {
hal_uart_cfg_t uart_cfg = {115200, 8, 1, 0};
hal_uart_init(0, &uart_cfg);
hal_gpio_init(0, 5, HAL_GPIO_OUTPUT); /* 点亮LED */
hal_gpio_write(0, 5, HAL_GPIO_HIGH);
hal_uart_send_byte(0, 'H');
hal_uart_send_byte(0, 'I');
uint8_t rx = hal_uart_receive_byte(0);
if (rx == 'A') {
hal_gpio_write(0, 5, HAL_GPIO_LOW); /* 收到'A'则关灯 */
}
}
移植步骤与验证
- 移植到新平台:新建
hal_<platform>.c,实现hal.h中声明的6个函数,替换编译目标中的平台文件即可。 - 编译:
gcc -c hal_stm32.c main.c(或对应平台编译器),链接生成固件。 - 运行验证:
- GPIO:将port0/pin5接LED,上电后LED应常亮;向UART发送字符'A',LED应熄灭。
- UART:将UART0连接PC串口,波特率115200,应收到字符串"HI";从PC发送'A',观察LED状态变化。
- 通过标准:应用层
main.c在两个平台间无需任何修改即可编译运行,且上述GPIO/UART行为一致。
常见问题
- 时钟未使能:STM32需先开启GPIO/UART外设时钟,ESP32需配置引脚复用功能,本案例为聚焦HAL结构已省略,实际移植时需在
hal_gpio_init/hal_uart_init中补充。 - 引脚映射差异:不同平台GPIO编号差异大,HAL的
port/pin参数需在平台实现中建立映射表,本案例以port0/pin5为例。