3. 驱动层LED驱动模块设计
任务定义
在MCU嵌入式软件分层架构中,驱动层直接操作寄存器,为上层提供硬件抽象接口。本案例设计一个LED驱动模块,目标为:基于寄存器操作实现GPIO引脚控制,提供LED初始化、点亮、熄灭与状态翻转四个接口,并支持多实例(多路LED)独立控制。模块不依赖具体MCU厂商库,以寄存器地址映射方式实现,便于移植到不同平台。
驱动层接口设计
驱动层LED模块向上层暴露以下接口,头文件定义如下:
/* led_driver.h */
#ifndef LED_DRIVER_H
#define LED_DRIVER_H
#include <stdint.h>
/* LED实例句柄:记录引脚所属端口、引脚号及当前状态 */
typedef struct {
volatile uint32_t *gpio_base; /* GPIO端口基地址 */
uint8_t pin; /* 引脚号 0~15 */
uint8_t active_high; /* 1:高电平点亮, 0:低电平点亮 */
uint8_t state; /* 当前逻辑状态: 1=亮, 0=灭 */
} led_t;
/* 初始化LED引脚为推挽输出,初始状态为灭 */
void led_init(led_t *led, volatile uint32_t *gpio_base, uint8_t pin, uint8_t active_high);
/* 点亮LED */
void led_on(led_t *led);
/* 熄灭LED */
void led_off(led_t *led);
/* 翻转LED状态 */
void led_toggle(led_t *led);
#endif /* LED_DRIVER_H */
寄存器层实现
以STM32F4系列GPIO寄存器布局为参考(通用ARM Cortex-M风格),寄存器偏移定义如下。代码通过地址映射访问,不依赖HAL库:
/* led_driver.c */
#include "led_driver.h"
/* GPIO寄存器偏移(适用于STM32F4/GD32F4等) */
#define GPIO_MODER_OFFSET 0x00 /* 模式寄存器 */
#define GPIO_OTYPER_OFFSET 0x04 /* 输出类型 */
#define GPIO_OSPEEDR_OFFSET 0x08 /* 输出速度 */
#define GPIO_PUPDR_OFFSET 0x0C /* 上下拉 */
#define GPIO_BSRR_OFFSET 0x18 /* 置位/复位寄存器 */
#define GPIO_IDR_OFFSET 0x10 /* 输入数据 */
/* MODER: 每引脚2位,01=通用输出 */
#define MODER_OUTPUT_MODE (0x01UL)
/* BSRR: 低16位写1置位,高16位写1复位 */
#define BSRR_SET_PIN(pin) (1UL << (pin))
#define BSRR_RESET_PIN(pin) (1UL << ((pin) + 16))
void led_init(led_t *led, volatile uint32_t *gpio_base, uint8_t pin, uint8_t active_high)
{
if (led == 0 || gpio_base == 0 || pin > 15) {
return; /* 参数错误保护 */
}
led->gpio_base = gpio_base;
led->pin = pin;
led->active_high = active_high;
led->state = 0;
/* 1. 配置为通用输出模式 */
uint32_t moder = gpio_base[GPIO_MODER_OFFSET / 4];
moder &= ~(0x03UL << (pin * 2));
moder |= (MODER_OUTPUT_MODE << (pin * 2));
gpio_base[GPIO_MODER_OFFSET / 4] = moder;
/* 2. 推挽输出(OTYPER对应位写0) */
gpio_base[GPIO_OTYPER_OFFSET / 4] &= ~(1UL << pin);
/* 3. 输出速度设为中速(10MHz档,写01) */
uint32_t ospeedr = gpio_base[GPIO_OSPEEDR_OFFSET / 4];
ospeedr &= ~(0x03UL << (pin * 2));
ospeedr |= (0x01UL << (pin * 2));
gpio_base[GPIO_OSPEEDR_OFFSET / 4] = ospeedr;
/* 4. 无上下拉 */
uint32_t pupdr = gpio_base[GPIO_PUPDR_OFFSET / 4];
pupdr &= ~(0x03UL << (pin * 2));
gpio_base[GPIO_PUPDR_OFFSET / 4] = pupdr;
/* 5. 初始化为灭 */
led_off(led);
}
void led_on(led_t *led)
{
if (led == 0) return;
if (led->active_high) {
led->gpio_base[GPIO_BSRR_OFFSET / 4] = BSRR_SET_PIN(led->pin);
} else {
led->gpio_base[GPIO_BSRR_OFFSET / 4] = BSRR_RESET_PIN(led->pin);
}
led->state = 1;
}
void led_off(led_t *led)
{
if (led == 0) return;
if (led->active_high) {
led->gpio_base[GPIO_BSRR_OFFSET / 4] = BSRR_RESET_PIN(led->pin);
} else {
led->gpio_base[GPIO_BSRR_OFFSET / 4] = BSRR_SET_PIN(led->pin);
}
led->state = 0;
}
void led_toggle(led_t *led)
{
if (led == 0) return;
if (led->state) {
led_off(led);
} else {
led_on(led);
}
}
多实例使用示例
以下代码展示如何创建两个独立LED实例(PA5高电平点亮、PB0低电平点亮),并交替翻转:
/* main.c 示例片段 */
#include "led_driver.h"
/* 假设GPIOA和GPIOB基地址(STM32F407: 0x40020000 / 0x40020400) */
#define GPIOA_BASE_ADDR 0x40020000UL
#define GPIOB_BASE_ADDR 0x40020400UL
/* 使能GPIO时钟(RCC)——不同MCU寄存器不同,此处仅示意 */
extern void system_gpio_clock_enable(void);
void main(void)
{
led_t led_red; /* PA5, 高电平点亮 */
led_t led_green; /* PB0, 低电平点亮 */
system_gpio_clock_enable(); /* 使能GPIOA/GPIOB时钟 */
led_init(&led_red, (volatile uint32_t *)GPIOA_BASE_ADDR, 5, 1);
led_init(&led_green, (volatile uint32_t *)GPIOB_BASE_ADDR, 0, 0);
while (1) {
led_on(&led_red);
led_off(&led_green);
/* 延时省略 */
led_toggle(&led_red);
led_toggle(&led_green);
}
}
验证方法
静态检查:确认代码中GPIO寄存器偏移与目标MCU参考手册一致(尤其BSRR偏移0x18、MODER偏移0x00)。
硬件验证步骤:
- 将LED正极经限流电阻接至PA5,负极接GND(对应active_high=1);另一路LED负极接PB0,正极接VCC(对应active_high=0);
- 编译烧录后,观察两路LED是否按main中逻辑交替亮灭;
- 用示波器测量PA5和PB0引脚电平,确认翻转时波形正确(PA5高电平点亮,PB0低电平点亮);
- 修改main中调用顺序(如连续调用led_toggle两次),验证状态恢复初始,证明state变量跟踪正确。
判定标准:两路LED独立工作互不干扰;引脚电平与active_high配置一致;连续翻转后状态无累积误差。