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'则关灯 */
    }
}

移植步骤与验证

  1. 移植到新平台:新建hal_<platform>.c,实现hal.h中声明的6个函数,替换编译目标中的平台文件即可。
  2. 编译:gcc -c hal_stm32.c main.c(或对应平台编译器),链接生成固件。
  3. 运行验证:
    • GPIO:将port0/pin5接LED,上电后LED应常亮;向UART发送字符'A',LED应熄灭。
    • UART:将UART0连接PC串口,波特率115200,应收到字符串"HI";从PC发送'A',观察LED状态变化。
  4. 通过标准:应用层main.c在两个平台间无需任何修改即可编译运行,且上述GPIO/UART行为一致。

常见问题

  • 时钟未使能:STM32需先开启GPIO/UART外设时钟,ESP32需配置引脚复用功能,本案例为聚焦HAL结构已省略,实际移植时需在hal_gpio_init/hal_uart_init中补充。
  • 引脚映射差异:不同平台GPIO编号差异大,HAL的port/pin参数需在平台实现中建立映射表,本案例以port0/pin5为例。