1. LED闪烁的分层实现
任务说明
本案例以LED闪烁为最小可验证场景,演示单片机软件分层架构的基本形态。目标不是“如何点亮LED”,而是建立清晰的调用边界:驱动层(Driver)负责操作寄存器或GPIO外设,硬件抽象层(HAL)对上层屏蔽具体芯片寄存器细节,应用层(App)只调用HAL接口完成闪烁逻辑。最终代码可在STM32F103系列(或等效Cortex-M3)上直接编译运行,通过观察板载LED以500ms周期闪烁来验证分层调用链是否打通。
分层结构与调用关系
代码实现
以下代码包含三个文件:drv_gpio.c/h(驱动层)、hal_led.c/h(HAL层)、app_led.c(应用层)以及main.c(入口)。驱动层直接操作STM32F103的GPIO寄存器,HAL层封装LED对应的引脚与电平逻辑,应用层实现延时翻转。为保持最小可复现,延时函数使用简单的SysTick阻塞方式。
/* ========== drv_gpio.h ========== */
#ifndef DRV_GPIO_H
#define DRV_GPIO_H
#include "stm32f10x.h" /* 芯片寄存器定义 */
typedef enum {
DRV_GPIO_LOW = 0,
DRV_GPIO_HIGH = 1
} drv_gpio_level_t;
void drv_gpio_init(void);
void drv_gpio_write(drv_gpio_level_t level);
#endif
/* ========== drv_gpio.c ========== */
#include "drv_gpio.h"
/* 板载LED:PC13(STM32F103最小系统板) */
#define LED_GPIO_PORT GPIOC
#define LED_GPIO_PIN GPIO_Pin_13
#define LED_GPIO_CLK RCC_APB2Periph_GPIOC
void drv_gpio_init(void)
{
GPIO_InitTypeDef gpio;
RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE);
gpio.GPIO_Pin = LED_GPIO_PIN;
gpio.GPIO_Mode = GPIO_Mode_Out_PP; /* 推挽输出 */
gpio.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(LED_GPIO_PORT, &gpio);
}
void drv_gpio_write(drv_gpio_level_t level)
{
if (level == DRV_GPIO_HIGH) {
LED_GPIO_PORT->BSRR = LED_GPIO_PIN; /* 置高 */
} else {
LED_GPIO_PORT->BRR = LED_GPIO_PIN; /* 置低 */
}
}
/* ========== hal_led.h ========== */
#ifndef HAL_LED_H
#define HAL_LED_H
#include "drv_gpio.h"
void hal_led_init(void);
void hal_led_toggle(void);
#endif
/* ========== hal_led.c ========== */
#include "hal_led.h"
static drv_gpio_level_t led_state = DRV_GPIO_LOW;
void hal_led_init(void)
{
drv_gpio_init();
drv_gpio_write(DRV_GPIO_LOW); /* 初始熄灭 */
led_state = DRV_GPIO_LOW;
}
void hal_led_toggle(void)
{
/* 翻转状态并写入驱动层 */
led_state = (led_state == DRV_GPIO_LOW) ? DRV_GPIO_HIGH : DRV_GPIO_LOW;
drv_gpio_write(led_state);
}
/* ========== app_led.h ========== */
#ifndef APP_LED_H
#define APP_LED_H
void app_led_blink(void);
#endif
/* ========== app_led.c ========== */
#include "app_led.h"
#include "hal_led.h"
/* 简单阻塞延时:SysTick 1ms 中断由启动文件配置 */
static void delay_ms(uint32_t ms)
{
volatile uint32_t i;
for (i = 0; i < ms * 4000; i++) {
__NOP();
}
}
void app_led_blink(void)
{
hal_led_toggle();
delay_ms(250); /* 亮250ms + 灭250ms = 周期500ms */
}
/* ========== main.c ========== */
#include "hal_led.h"
#include "app_led.h"
int main(void)
{
hal_led_init();
while (1) {
app_led_blink();
}
}
工程组织与编译
将上述文件加入STM32标准外设库工程(使用STM32F10x_StdPeriph_Lib_V3.5或等效环境),确保包含stm32f10x.h头文件路径。编译后通过ST-Link下载到目标板。若使用其他Cortex-M3芯片,仅需修改drv_gpio.c中的端口、引脚和时钟宏定义,HAL层与应用层无需改动——这正是分层架构的核心收益。
验证方法与判定标准
验证步骤:
- 编译工程,确认无错误、无警告(或仅有无害的类型转换提示);
- 将程序下载至STM32F103最小系统板,板载LED(PC13)连接正常;
- 上电或复位后,观察LED状态。
判定标准:
- LED以约500ms为周期持续闪烁(亮250ms、灭250ms),说明应用层→HAL层→驱动层的调用链完整打通;
- 若LED常亮或常灭,使用调试器在
hal_led_toggle()处设置断点,单步执行确认函数是否被循环调用、状态变量是否翻转; - 若LED完全不亮,检查
drv_gpio_init()中的时钟使能和引脚配置是否与硬件实际连接一致。
预期输出(逻辑推导):由于代码中delay_ms(250)在每次翻转后执行,且hal_led_toggle()每次调用必然翻转一次电平状态,因此LED必然呈现周期性亮灭。具体周期时间取决于delay_ms中循环次数与内核时钟频率的对应关系,若系统时钟为8MHz内部RC,实际周期可能偏离500ms,但闪烁行为必然存在。若需精确周期,应使用SysTick定时器替代空循环延时。