1. 分层架构概览与工程模板搭建
任务说明
本案例目标是在MCU嵌入式软件开发中,搭建一个最小可复现的三层软件架构工程模板(驱动层、中间层、应用层),并验证层间调用关系清晰、编译通过且具备可扩展性。你将获得一个可直接导入IDE或使用命令行编译的完整工程骨架,而非零散代码片段。
工程结构设计
采用经典三层架构,各层职责严格分离,依赖方向自上而下:应用层→中间层→驱动层。驱动层直接操作寄存器,中间层提供设备抽象接口,应用层仅调用中间层API,不接触硬件寄存器。
最小可复现工程模板
以下代码构成完整工程,共6个源文件。所有代码均使用标准C编写,不依赖特定厂商SDK,寄存器地址通过宏定义模拟,可在任何支持C99的编译器中编译验证。
文件清单与代码
1. driver/gpio_driver.h — 驱动层头文件,定义寄存器映射与GPIO操作接口:
#ifndef GPIO_DRIVER_H
#define GPIO_DRIVER_H
#include <stdint.h>
/* 模拟寄存器基地址 */
#define GPIO_BASE_ADDR 0x40021000UL
#define GPIO_MODER_OFF 0x00
#define GPIO_ODR_OFF 0x14
/* 引脚模式 */
#define GPIO_MODE_OUTPUT 0x01
#define GPIO_MODE_INPUT 0x00
/* 引脚电平 */
#define GPIO_PIN_RESET 0
#define GPIO_PIN_SET 1
/* 驱动层接口 */
void gpio_init(uint32_t pin, uint8_t mode);
void gpio_write(uint32_t pin, uint8_t level);
uint8_t gpio_read(uint32_t pin);
#endif /* GPIO_DRIVER_H */
2. driver/gpio_driver.c — 驱动层实现,直接操作寄存器地址:
#include "gpio_driver.h"
/* 模拟寄存器指针 */
static volatile uint32_t *gpio_moder = (uint32_t *)(GPIO_BASE_ADDR + GPIO_MODER_OFF);
static volatile uint32_t *gpio_odr = (uint32_t *)(GPIO_BASE_ADDR + GPIO_ODR_OFF);
void gpio_init(uint32_t pin, uint8_t mode)
{
uint32_t shift = (pin % 16) * 2;
uint32_t mask = ~(0x3UL << shift);
uint32_t val = (mode & 0x3UL) << shift;
*gpio_moder = (*gpio_moder & mask) | val;
}
void gpio_write(uint32_t pin, uint8_t level)
{
if (level == GPIO_PIN_SET) {
*gpio_odr |= (1UL << pin);
} else {
*gpio_odr &= ~(1UL << pin);
}
}
uint8_t gpio_read(uint32_t pin)
{
return (uint8_t)((*gpio_odr >> pin) & 0x1UL);
}
3. middleware/led_service.h — 中间层头文件,提供LED设备抽象:
#ifndef LED_SERVICE_H
#define LED_SERVICE_H
#include <stdint.h>
/* LED编号 */
#define LED_RED 0
#define LED_GREEN 1
#define LED_BLUE 2
/* 中间层接口 */
void led_service_init(void);
void led_service_on(uint8_t led_id);
void led_service_off(uint8_t led_id);
void led_service_toggle(uint8_t led_id);
#endif /* LED_SERVICE_H */
4. middleware/led_service.c — 中间层实现,将LED编号映射到具体引脚并调用驱动层:
#include "led_service.h"
#include "gpio_driver.h"
/* LED到引脚的映射表 */
static const uint32_t led_pin_map[3] = {5, 6, 7}; /* 假设LED接在GPIO5/6/7 */
void led_service_init(void)
{
uint8_t i;
for (i = 0; i < 3; i++) {
gpio_init(led_pin_map[i], GPIO_MODE_OUTPUT);
gpio_write(led_pin_map[i], GPIO_PIN_RESET);
}
}
void led_service_on(uint8_t led_id)
{
if (led_id < 3) {
gpio_write(led_pin_map[led_id], GPIO_PIN_SET);
}
}
void led_service_off(uint8_t led_id)
{
if (led_id < 3) {
gpio_write(led_pin_map[led_id], GPIO_PIN_RESET);
}
}
void led_service_toggle(uint8_t led_id)
{
if (led_id < 3) {
uint8_t current = gpio_read(led_pin_map[led_id]);
gpio_write(led_pin_map[led_id], current ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
}
5. app/app_config.h — 应用层配置头文件:
#ifndef APP_CONFIG_H
#define APP_CONFIG_H
/* 应用层运行参数 */
#define APP_LED_BLINK_INTERVAL_MS 500
#define APP_LED_ACTIVE_ID LED_GREEN
#endif /* APP_CONFIG_H */
6. app/main.c — 应用层入口,演示层间调用链:
#include <stdio.h>
#include "led_service.h"
#include "app_config.h"
/* 模拟延时函数(非阻塞示例) */
static void delay_ms(volatile uint32_t ms)
{
volatile uint32_t count = ms * 1000;
while (count--) {
__asm volatile ("nop");
}
}
int main(void)
{
/* 初始化中间层(内部会调用驱动层) */
led_service_init();
/* 应用层仅调用中间层API,不直接操作寄存器 */
while (1) {
led_service_on(APP_LED_ACTIVE_ID);
delay_ms(APP_LED_BLINK_INTERVAL_MS);
led_service_off(APP_LED_ACTIVE_ID);
delay_ms(APP_LED_BLINK_INTERVAL_MS);
}
return 0;
}
编译与验证方法
依赖要求:仅需标准C99编译器(GCC/Clang/ARMCC均可),无第三方库。
编译命令:在工程根目录执行:
gcc -I driver -I middleware -I app \
driver/gpio_driver.c \
middleware/led_service.c \
app/main.c \
-o firmware.elf -std=c99
验证步骤与判定标准:
- 编译通过:上述命令无错误、无警告,生成
firmware.elf,证明工程模板语法正确、头文件路径配置无误。 - 层间调用验证:在
main.c中,将led_service_on调用注释掉,编译应报错(未声明函数),证明应用层确实依赖中间层接口;将led_service.c中的gpio_write调用改为不存在的函数名,编译报错,证明中间层依赖驱动层。 - 链接完整性:使用
nm firmware.elf | grep " gpio_write"应看到符号已定义,说明驱动层代码被正确链接。 - 逻辑正确性(可选):若在PC上运行,由于寄存器地址为模拟值,程序会因访问非法地址而崩溃——这是预期行为,证明驱动层确实在操作“硬件地址”。如需PC上完整运行,可将
gpio_driver.c中的指针替换为静态变量,但本案例以编译通过和结构验证为判定标准。
预期输出:编译成功后生成firmware.elf文件,无任何编译错误与警告。此结果由代码逻辑直接推出,非实测数据。