分层架构设计实践¶
概述¶
分层架构是嵌入式系统中最常用且最重要的架构模式。通过将系统划分为不同的层次,每层负责特定的功能,可以大大提高代码的可维护性、可移植性和可测试性。本教程将通过一个完整的实例项目,带你深入理解和实践分层架构的设计方法。
完成本教程后,你将能够:
- 理解分层架构的核心原则和设计思想
- 掌握HAL层(硬件抽象层)的设计和实现方法
- 学会设计清晰的驱动层接口
- 理解中间件层和应用层的职责划分
- 能够独立设计和实现一个分层架构的嵌入式系统
- 掌握层与层之间的接口定义和数据传递方法
前置要求¶
在开始本教程之前,你需要:
- 掌握C语言基础知识(指针、结构体、函数指针)
- 了解基本的嵌入式开发概念
- 阅读过嵌入式软件架构概述
- 有一定的单片机开发经验(如STM32、Arduino等)
准备工作¶
硬件准备¶
本教程使用以下硬件(可根据实际情况调整):
- STM32开发板(或其他MCU开发板)
- LED灯(至少2个)
- 按键(至少1个)
- 温湿度传感器(如DHT11或DHT22)
- USB转串口模块(用于调试输出)
软件准备¶
- Keil MDK或STM32CubeIDE
- 串口调试工具(如PuTTY、SecureCRT)
- 代码编辑器(推荐VS Code)
项目目标¶
我们将构建一个环境监测系统,具备以下功能:
- 定时读取温湿度传感器数据
- 通过LED指示系统状态
- 按键控制数据采集的启动和停止
- 通过串口输出监测数据
- 支持不同的工作模式切换
步骤1: 理解分层架构的设计原则¶
1.1 分层架构的基本结构¶
在开始编码之前,我们先明确分层架构的基本结构。一个典型的嵌入式系统分层架构包含以下层次:
┌─────────────────────────────────────┐
│ 应用层 (Application) │ 业务逻辑、状态机、用户交互
├─────────────────────────────────────┤
│ 中间件层 (Middleware) │ 协议栈、算法库、工具函数
├─────────────────────────────────────┤
│ 驱动层 (Driver) │ 设备驱动、外设控制
├─────────────────────────────────────┤
│ 硬件抽象层 (HAL) │ 寄存器操作、硬件初始化
├─────────────────────────────────────┤
│ 硬件层 (Hardware) │ MCU、外设、传感器
└─────────────────────────────────────┘
1.2 各层的职责¶
硬件抽象层(HAL): - 封装寄存器操作 - 提供硬件初始化接口 - 屏蔽不同MCU的差异 - 不包含业务逻辑
驱动层(Driver): - 基于HAL层实现设备驱动 - 提供设备操作的高级接口 - 管理设备状态 - 处理设备相关的错误
中间件层(Middleware): - 提供通用的功能模块 - 实现协议栈和算法 - 数据处理和转换 - 与具体硬件无关
应用层(Application): - 实现业务逻辑 - 用户交互处理 - 系统状态管理 - 调用下层接口完成功能
1.3 分层设计的核心原则¶
原则1: 单向依赖 - 上层可以调用下层接口 - 下层不能直接调用上层 - 通过回调函数实现下层向上层通知
原则2: 接口稳定 - 每层对外提供稳定的接口 - 内部实现可以修改,接口尽量不变 - 使用头文件定义接口
原则3: 职责单一 - 每层只负责特定的功能 - 不要跨层调用 - 避免层次混乱
原则4: 最小知识 - 每层只需要知道相邻层的接口 - 不需要了解其他层的实现细节 - 降低耦合度
步骤2: 设计HAL层(硬件抽象层)¶
2.1 HAL层的设计目标¶
HAL层的主要目标是: - 封装所有寄存器操作 - 提供统一的硬件接口 - 便于移植到不同的MCU平台
2.2 GPIO HAL层设计¶
首先,我们设计GPIO的HAL层接口:
// hal_gpio.h - GPIO硬件抽象层接口
#ifndef HAL_GPIO_H
#define HAL_GPIO_H
#include <stdint.h>
// GPIO端口定义
typedef enum {
HAL_GPIO_PORT_A = 0,
HAL_GPIO_PORT_B,
HAL_GPIO_PORT_C,
HAL_GPIO_PORT_D
} HAL_GPIO_Port_t;
// GPIO引脚定义
typedef enum {
HAL_GPIO_PIN_0 = 0,
HAL_GPIO_PIN_1,
HAL_GPIO_PIN_2,
HAL_GPIO_PIN_3,
HAL_GPIO_PIN_4,
HAL_GPIO_PIN_5,
// ... 其他引脚
} HAL_GPIO_Pin_t;
// GPIO模式定义
typedef enum {
HAL_GPIO_MODE_INPUT = 0,
HAL_GPIO_MODE_OUTPUT,
HAL_GPIO_MODE_AF, // 复用功能
HAL_GPIO_MODE_ANALOG // 模拟模式
} HAL_GPIO_Mode_t;
// GPIO上下拉配置
typedef enum {
HAL_GPIO_PULL_NONE = 0,
HAL_GPIO_PULL_UP,
HAL_GPIO_PULL_DOWN
} HAL_GPIO_Pull_t;
// GPIO配置结构体
typedef struct {
HAL_GPIO_Port_t port;
HAL_GPIO_Pin_t pin;
HAL_GPIO_Mode_t mode;
HAL_GPIO_Pull_t pull;
} HAL_GPIO_Config_t;
// HAL层接口函数
void HAL_GPIO_Init(const HAL_GPIO_Config_t *config);
void HAL_GPIO_WritePin(HAL_GPIO_Port_t port, HAL_GPIO_Pin_t pin, uint8_t state);
uint8_t HAL_GPIO_ReadPin(HAL_GPIO_Port_t port, HAL_GPIO_Pin_t pin);
void HAL_GPIO_TogglePin(HAL_GPIO_Port_t port, HAL_GPIO_Pin_t pin);
#endif // HAL_GPIO_H
2.3 GPIO HAL层实现¶
// hal_gpio.c - GPIO硬件抽象层实现
#include "hal_gpio.h"
#include "stm32f4xx.h" // 根据实际MCU修改
// 端口映射表
static GPIO_TypeDef* const GPIO_PORT_MAP[] = {
GPIOA, GPIOB, GPIOC, GPIOD
};
/**
* @brief 初始化GPIO引脚
* @param config: GPIO配置结构体指针
* @retval None
*/
void HAL_GPIO_Init(const HAL_GPIO_Config_t *config) {
GPIO_TypeDef *GPIOx = GPIO_PORT_MAP[config->port];
uint32_t pin_pos = config->pin;
// 1. 使能GPIO时钟
switch(config->port) {
case HAL_GPIO_PORT_A:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
break;
case HAL_GPIO_PORT_B:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
break;
case HAL_GPIO_PORT_C:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;
break;
case HAL_GPIO_PORT_D:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
break;
}
// 2. 配置GPIO模式
GPIOx->MODER &= ~(3U << (pin_pos * 2));
GPIOx->MODER |= (config->mode << (pin_pos * 2));
// 3. 配置上下拉
GPIOx->PUPDR &= ~(3U << (pin_pos * 2));
GPIOx->PUPDR |= (config->pull << (pin_pos * 2));
// 4. 配置输出速度(默认低速)
GPIOx->OSPEEDR &= ~(3U << (pin_pos * 2));
}
/**
* @brief 写GPIO引脚
* @param port: GPIO端口
* @param pin: GPIO引脚
* @param state: 引脚状态(0或1)
* @retval None
*/
void HAL_GPIO_WritePin(HAL_GPIO_Port_t port, HAL_GPIO_Pin_t pin, uint8_t state) {
GPIO_TypeDef *GPIOx = GPIO_PORT_MAP[port];
if (state) {
GPIOx->BSRR = (1U << pin); // 设置引脚
} else {
GPIOx->BSRR = (1U << (pin + 16)); // 清除引脚
}
}
/**
* @brief 读GPIO引脚
* @param port: GPIO端口
* @param pin: GPIO引脚
* @retval 引脚状态(0或1)
*/
uint8_t HAL_GPIO_ReadPin(HAL_GPIO_Port_t port, HAL_GPIO_Pin_t pin) {
GPIO_TypeDef *GPIOx = GPIO_PORT_MAP[port];
return (GPIOx->IDR & (1U << pin)) ? 1 : 0;
}
/**
* @brief 翻转GPIO引脚
* @param port: GPIO端口
* @param pin: GPIO引脚
* @retval None
*/
void HAL_GPIO_TogglePin(HAL_GPIO_Port_t port, HAL_GPIO_Pin_t pin) {
GPIO_TypeDef *GPIOx = GPIO_PORT_MAP[port];
GPIOx->ODR ^= (1U << pin);
}
2.4 UART HAL层设计¶
接下来设计UART的HAL层接口:
// hal_uart.h - UART硬件抽象层接口
#ifndef HAL_UART_H
#define HAL_UART_H
#include <stdint.h>
// UART端口定义
typedef enum {
HAL_UART_1 = 0,
HAL_UART_2,
HAL_UART_3
} HAL_UART_Port_t;
// UART配置结构体
typedef struct {
HAL_UART_Port_t port;
uint32_t baudrate;
uint8_t data_bits; // 8 or 9
uint8_t stop_bits; // 1 or 2
uint8_t parity; // 0:None, 1:Odd, 2:Even
} HAL_UART_Config_t;
// HAL层接口函数
void HAL_UART_Init(const HAL_UART_Config_t *config);
void HAL_UART_SendByte(HAL_UART_Port_t port, uint8_t data);
void HAL_UART_SendData(HAL_UART_Port_t port, const uint8_t *data, uint16_t len);
uint8_t HAL_UART_ReceiveByte(HAL_UART_Port_t port);
uint16_t HAL_UART_ReceiveData(HAL_UART_Port_t port, uint8_t *buffer, uint16_t max_len);
#endif // HAL_UART_H
设计要点: - 使用枚举类型定义端口和配置选项 - 使用结构体封装配置参数 - 接口函数命名清晰,参数类型明确 - 提供完整的注释说明
步骤3: 设计驱动层¶
3.1 驱动层的设计目标¶
驱动层基于HAL层,提供更高级的设备操作接口: - 封装设备的初始化和配置 - 提供设备的功能接口 - 管理设备状态 - 处理设备错误
3.2 LED驱动设计¶
// drv_led.h - LED驱动接口
#ifndef DRV_LED_H
#define DRV_LED_H
#include <stdint.h>
// LED ID定义
typedef enum {
LED_STATUS = 0, // 状态指示LED
LED_ERROR, // 错误指示LED
LED_MAX
} LED_ID_t;
// LED状态定义
typedef enum {
LED_STATE_OFF = 0,
LED_STATE_ON,
LED_STATE_BLINK_SLOW, // 慢闪(1Hz)
LED_STATE_BLINK_FAST // 快闪(5Hz)
} LED_State_t;
// 驱动层接口函数
void DRV_LED_Init(void);
void DRV_LED_SetState(LED_ID_t led, LED_State_t state);
LED_State_t DRV_LED_GetState(LED_ID_t led);
void DRV_LED_Process(void); // 需要在主循环中定期调用
#endif // DRV_LED_H
// drv_led.c - LED驱动实现
#include "drv_led.h"
#include "hal_gpio.h"
// LED配置表
typedef struct {
HAL_GPIO_Port_t port;
HAL_GPIO_Pin_t pin;
LED_State_t state;
uint16_t counter;
uint8_t output;
} LED_Control_t;
// LED控制数据
static LED_Control_t led_ctrl[LED_MAX] = {
{HAL_GPIO_PORT_A, HAL_GPIO_PIN_5, LED_STATE_OFF, 0, 0}, // LED_STATUS
{HAL_GPIO_PORT_A, HAL_GPIO_PIN_6, LED_STATE_OFF, 0, 0} // LED_ERROR
};
/**
* @brief 初始化LED驱动
* @retval None
*/
void DRV_LED_Init(void) {
HAL_GPIO_Config_t gpio_config;
// 初始化所有LED引脚
for (int i = 0; i < LED_MAX; i++) {
gpio_config.port = led_ctrl[i].port;
gpio_config.pin = led_ctrl[i].pin;
gpio_config.mode = HAL_GPIO_MODE_OUTPUT;
gpio_config.pull = HAL_GPIO_PULL_NONE;
HAL_GPIO_Init(&gpio_config);
HAL_GPIO_WritePin(led_ctrl[i].port, led_ctrl[i].pin, 0);
}
}
/**
* @brief 设置LED状态
* @param led: LED ID
* @param state: LED状态
* @retval None
*/
void DRV_LED_SetState(LED_ID_t led, LED_State_t state) {
if (led >= LED_MAX) return;
led_ctrl[led].state = state;
led_ctrl[led].counter = 0;
// 立即更新输出
if (state == LED_STATE_OFF) {
HAL_GPIO_WritePin(led_ctrl[led].port, led_ctrl[led].pin, 0);
led_ctrl[led].output = 0;
} else if (state == LED_STATE_ON) {
HAL_GPIO_WritePin(led_ctrl[led].port, led_ctrl[led].pin, 1);
led_ctrl[led].output = 1;
}
}
/**
* @brief 获取LED状态
* @param led: LED ID
* @retval LED状态
*/
LED_State_t DRV_LED_GetState(LED_ID_t led) {
if (led >= LED_MAX) return LED_STATE_OFF;
return led_ctrl[led].state;
}
/**
* @brief LED处理函数(需要定期调用,建议10ms)
* @retval None
*/
void DRV_LED_Process(void) {
for (int i = 0; i < LED_MAX; i++) {
LED_Control_t *ctrl = &led_ctrl[i];
switch (ctrl->state) {
case LED_STATE_BLINK_SLOW:
// 慢闪: 500ms周期
ctrl->counter++;
if (ctrl->counter >= 50) { // 500ms / 10ms
ctrl->counter = 0;
ctrl->output = !ctrl->output;
HAL_GPIO_WritePin(ctrl->port, ctrl->pin, ctrl->output);
}
break;
case LED_STATE_BLINK_FAST:
// 快闪: 100ms周期
ctrl->counter++;
if (ctrl->counter >= 10) { // 100ms / 10ms
ctrl->counter = 0;
ctrl->output = !ctrl->output;
HAL_GPIO_WritePin(ctrl->port, ctrl->pin, ctrl->output);
}
break;
default:
// LED_STATE_OFF 和 LED_STATE_ON 不需要处理
break;
}
}
}
3.3 按键驱动设计¶
// drv_button.h - 按键驱动接口
#ifndef DRV_BUTTON_H
#define DRV_BUTTON_H
#include <stdint.h>
// 按键ID定义
typedef enum {
BUTTON_USER = 0,
BUTTON_MAX
} BUTTON_ID_t;
// 按键事件定义
typedef enum {
BUTTON_EVENT_NONE = 0,
BUTTON_EVENT_PRESS, // 按下
BUTTON_EVENT_RELEASE, // 释放
BUTTON_EVENT_CLICK, // 单击
BUTTON_EVENT_LONG_PRESS // 长按
} BUTTON_Event_t;
// 按键回调函数类型
typedef void (*BUTTON_Callback_t)(BUTTON_ID_t button, BUTTON_Event_t event);
// 驱动层接口函数
void DRV_BUTTON_Init(void);
void DRV_BUTTON_RegisterCallback(BUTTON_Callback_t callback);
void DRV_BUTTON_Process(void); // 需要在主循环中定期调用
#endif // DRV_BUTTON_H
// drv_button.c - 按键驱动实现
#include "drv_button.h"
#include "hal_gpio.h"
#define BUTTON_DEBOUNCE_TIME 20 // 消抖时间(ms)
#define BUTTON_LONG_PRESS_TIME 1000 // 长按时间(ms)
// 按键状态
typedef enum {
BUTTON_STATE_IDLE = 0,
BUTTON_STATE_DEBOUNCE,
BUTTON_STATE_PRESSED,
BUTTON_STATE_LONG_PRESS
} BUTTON_State_t;
// 按键控制数据
typedef struct {
HAL_GPIO_Port_t port;
HAL_GPIO_Pin_t pin;
BUTTON_State_t state;
uint16_t counter;
uint8_t last_level;
} BUTTON_Control_t;
// 按键配置
static BUTTON_Control_t button_ctrl[BUTTON_MAX] = {
{HAL_GPIO_PORT_C, HAL_GPIO_PIN_13, BUTTON_STATE_IDLE, 0, 1} // BUTTON_USER
};
// 按键回调函数
static BUTTON_Callback_t button_callback = NULL;
/**
* @brief 初始化按键驱动
* @retval None
*/
void DRV_BUTTON_Init(void) {
HAL_GPIO_Config_t gpio_config;
// 初始化所有按键引脚
for (int i = 0; i < BUTTON_MAX; i++) {
gpio_config.port = button_ctrl[i].port;
gpio_config.pin = button_ctrl[i].pin;
gpio_config.mode = HAL_GPIO_MODE_INPUT;
gpio_config.pull = HAL_GPIO_PULL_UP; // 上拉输入
HAL_GPIO_Init(&gpio_config);
}
}
/**
* @brief 注册按键回调函数
* @param callback: 回调函数指针
* @retval None
*/
void DRV_BUTTON_RegisterCallback(BUTTON_Callback_t callback) {
button_callback = callback;
}
/**
* @brief 按键处理函数(需要定期调用,建议10ms)
* @retval None
*/
void DRV_BUTTON_Process(void) {
for (int i = 0; i < BUTTON_MAX; i++) {
BUTTON_Control_t *ctrl = &button_ctrl[i];
uint8_t current_level = HAL_GPIO_ReadPin(ctrl->port, ctrl->pin);
switch (ctrl->state) {
case BUTTON_STATE_IDLE:
if (current_level == 0) { // 按键按下(低电平)
ctrl->state = BUTTON_STATE_DEBOUNCE;
ctrl->counter = 0;
}
break;
case BUTTON_STATE_DEBOUNCE:
ctrl->counter++;
if (ctrl->counter >= BUTTON_DEBOUNCE_TIME / 10) {
if (current_level == 0) {
// 确认按下
ctrl->state = BUTTON_STATE_PRESSED;
ctrl->counter = 0;
if (button_callback) {
button_callback(i, BUTTON_EVENT_PRESS);
}
} else {
// 抖动,返回空闲
ctrl->state = BUTTON_STATE_IDLE;
}
}
break;
case BUTTON_STATE_PRESSED:
ctrl->counter++;
if (current_level == 1) {
// 按键释放
ctrl->state = BUTTON_STATE_IDLE;
if (button_callback) {
button_callback(i, BUTTON_EVENT_RELEASE);
button_callback(i, BUTTON_EVENT_CLICK);
}
} else if (ctrl->counter >= BUTTON_LONG_PRESS_TIME / 10) {
// 长按
ctrl->state = BUTTON_STATE_LONG_PRESS;
if (button_callback) {
button_callback(i, BUTTON_EVENT_LONG_PRESS);
}
}
break;
case BUTTON_STATE_LONG_PRESS:
if (current_level == 1) {
// 按键释放
ctrl->state = BUTTON_STATE_IDLE;
if (button_callback) {
button_callback(i, BUTTON_EVENT_RELEASE);
}
}
break;
}
ctrl->last_level = current_level;
}
}
驱动层设计要点: - 封装设备的状态管理 - 提供事件回调机制 - 实现消抖、长按等高级功能 - 接口简洁易用
步骤4: 设计中间件层¶
4.1 中间件层的设计目标¶
中间件层提供与具体硬件无关的通用功能: - 数据处理和转换 - 协议实现 - 算法库 - 工具函数
4.2 数据格式化中间件¶
// mid_format.h - 数据格式化中间件
#ifndef MID_FORMAT_H
#define MID_FORMAT_H
#include <stdint.h>
// 格式化缓冲区大小
#define FORMAT_BUFFER_SIZE 128
// 中间件接口函数
int MID_FormatTemperature(char *buffer, uint16_t size, float temperature);
int MID_FormatHumidity(char *buffer, uint16_t size, float humidity);
int MID_FormatDateTime(char *buffer, uint16_t size, uint32_t timestamp);
int MID_FormatSensorData(char *buffer, uint16_t size, float temp, float hum);
#endif // MID_FORMAT_H
// mid_format.c - 数据格式化中间件实现
#include "mid_format.h"
#include <stdio.h>
#include <string.h>
/**
* @brief 格式化温度数据
* @param buffer: 输出缓冲区
* @param size: 缓冲区大小
* @param temperature: 温度值
* @retval 格式化后的字符串长度
*/
int MID_FormatTemperature(char *buffer, uint16_t size, float temperature) {
return snprintf(buffer, size, "Temperature: %.1f°C", temperature);
}
/**
* @brief 格式化湿度数据
* @param buffer: 输出缓冲区
* @param size: 缓冲区大小
* @param humidity: 湿度值
* @retval 格式化后的字符串长度
*/
int MID_FormatHumidity(char *buffer, uint16_t size, float humidity) {
return snprintf(buffer, size, "Humidity: %.1f%%", humidity);
}
/**
* @brief 格式化传感器数据
* @param buffer: 输出缓冲区
* @param size: 缓冲区大小
* @param temp: 温度值
* @param hum: 湿度值
* @retval 格式化后的字符串长度
*/
int MID_FormatSensorData(char *buffer, uint16_t size, float temp, float hum) {
return snprintf(buffer, size,
"[Sensor] Temp: %.1f°C, Hum: %.1f%%\r\n",
temp, hum);
}
4.3 数据滤波中间件¶
// mid_filter.h - 数据滤波中间件
#ifndef MID_FILTER_H
#define MID_FILTER_H
#include <stdint.h>
// 滑动平均滤波器
typedef struct {
float *buffer;
uint16_t size;
uint16_t index;
uint16_t count;
float sum;
} MovingAvgFilter_t;
// 中间件接口函数
void MID_Filter_Init(MovingAvgFilter_t *filter, float *buffer, uint16_t size);
float MID_Filter_Update(MovingAvgFilter_t *filter, float value);
void MID_Filter_Reset(MovingAvgFilter_t *filter);
#endif // MID_FILTER_H
// mid_filter.c - 数据滤波中间件实现
#include "mid_filter.h"
#include <string.h>
/**
* @brief 初始化滑动平均滤波器
* @param filter: 滤波器结构体指针
* @param buffer: 数据缓冲区
* @param size: 缓冲区大小
* @retval None
*/
void MID_Filter_Init(MovingAvgFilter_t *filter, float *buffer, uint16_t size) {
filter->buffer = buffer;
filter->size = size;
filter->index = 0;
filter->count = 0;
filter->sum = 0.0f;
memset(buffer, 0, size * sizeof(float));
}
/**
* @brief 更新滤波器数据
* @param filter: 滤波器结构体指针
* @param value: 新的数据值
* @retval 滤波后的值
*/
float MID_Filter_Update(MovingAvgFilter_t *filter, float value) {
// 减去最旧的值
if (filter->count >= filter->size) {
filter->sum -= filter->buffer[filter->index];
}
// 添加新值
filter->buffer[filter->index] = value;
filter->sum += value;
// 更新索引和计数
filter->index = (filter->index + 1) % filter->size;
if (filter->count < filter->size) {
filter->count++;
}
// 返回平均值
return filter->sum / filter->count;
}
/**
* @brief 重置滤波器
* @param filter: 滤波器结构体指针
* @retval None
*/
void MID_Filter_Reset(MovingAvgFilter_t *filter) {
filter->index = 0;
filter->count = 0;
filter->sum = 0.0f;
memset(filter->buffer, 0, filter->size * sizeof(float));
}
中间件层设计要点: - 与硬件无关,可复用 - 提供通用的算法和工具 - 接口清晰,易于测试 - 可以独立开发和维护
步骤5: 设计应用层¶
5.1 应用层的设计目标¶
应用层实现具体的业务逻辑: - 系统状态管理 - 业务流程控制 - 用户交互处理 - 调用下层接口完成功能
5.2 系统状态定义¶
// app_main.h - 应用层主程序
#ifndef APP_MAIN_H
#define APP_MAIN_H
#include <stdint.h>
// 系统工作模式
typedef enum {
SYS_MODE_IDLE = 0, // 空闲模式
SYS_MODE_MONITORING, // 监测模式
SYS_MODE_ERROR // 错误模式
} SystemMode_t;
// 系统状态
typedef struct {
SystemMode_t mode;
uint8_t is_running;
uint32_t sample_count;
float last_temperature;
float last_humidity;
} SystemState_t;
// 应用层接口函数
void APP_Init(void);
void APP_Process(void);
SystemMode_t APP_GetMode(void);
void APP_SetMode(SystemMode_t mode);
#endif // APP_MAIN_H
5.3 应用层实现¶
// app_main.c - 应用层主程序实现
#include "app_main.h"
#include "drv_led.h"
#include "drv_button.h"
#include "drv_sensor.h" // 假设已实现传感器驱动
#include "mid_format.h"
#include "mid_filter.h"
#include <stdio.h>
// 系统状态
static SystemState_t sys_state = {
.mode = SYS_MODE_IDLE,
.is_running = 0,
.sample_count = 0,
.last_temperature = 0.0f,
.last_humidity = 0.0f
};
// 滤波器缓冲区
static float temp_filter_buffer[10];
static float hum_filter_buffer[10];
static MovingAvgFilter_t temp_filter;
static MovingAvgFilter_t hum_filter;
// 定时器计数
static uint32_t sample_timer = 0;
// 前向声明
static void ButtonEventHandler(BUTTON_ID_t button, BUTTON_Event_t event);
static void ProcessMonitoring(void);
static void UpdateLEDStatus(void);
/**
* @brief 应用层初始化
* @retval None
*/
void APP_Init(void) {
// 初始化驱动层
DRV_LED_Init();
DRV_BUTTON_Init();
DRV_SENSOR_Init(); // 假设已实现
// 注册按键回调
DRV_BUTTON_RegisterCallback(ButtonEventHandler);
// 初始化滤波器
MID_Filter_Init(&temp_filter, temp_filter_buffer, 10);
MID_Filter_Init(&hum_filter, hum_filter_buffer, 10);
// 设置初始LED状态
DRV_LED_SetState(LED_STATUS, LED_STATE_BLINK_SLOW);
DRV_LED_SetState(LED_ERROR, LED_STATE_OFF);
printf("System Initialized\r\n");
}
/**
* @brief 应用层主处理函数
* @retval None
*/
void APP_Process(void) {
// 更新LED状态
UpdateLEDStatus();
// 根据模式执行不同的处理
switch (sys_state.mode) {
case SYS_MODE_IDLE:
// 空闲模式,等待用户操作
break;
case SYS_MODE_MONITORING:
// 监测模式,定期采集数据
ProcessMonitoring();
break;
case SYS_MODE_ERROR:
// 错误模式,等待恢复
break;
}
}
/**
* @brief 按键事件处理函数
* @param button: 按键ID
* @param event: 按键事件
* @retval None
*/
static void ButtonEventHandler(BUTTON_ID_t button, BUTTON_Event_t event) {
if (button == BUTTON_USER) {
if (event == BUTTON_EVENT_CLICK) {
// 单击:切换工作模式
if (sys_state.mode == SYS_MODE_IDLE) {
APP_SetMode(SYS_MODE_MONITORING);
printf("Start Monitoring\r\n");
} else if (sys_state.mode == SYS_MODE_MONITORING) {
APP_SetMode(SYS_MODE_IDLE);
printf("Stop Monitoring\r\n");
}
} else if (event == BUTTON_EVENT_LONG_PRESS) {
// 长按:重置系统
MID_Filter_Reset(&temp_filter);
MID_Filter_Reset(&hum_filter);
sys_state.sample_count = 0;
printf("System Reset\r\n");
}
}
}
/**
* @brief 监测模式处理
* @retval None
*/
static void ProcessMonitoring(void) {
sample_timer++;
// 每2秒采集一次数据
if (sample_timer >= 200) { // 2000ms / 10ms
sample_timer = 0;
float temperature, humidity;
// 读取传感器数据
if (DRV_SENSOR_Read(&temperature, &humidity) == 0) {
// 数据滤波
temperature = MID_Filter_Update(&temp_filter, temperature);
humidity = MID_Filter_Update(&hum_filter, humidity);
// 保存数据
sys_state.last_temperature = temperature;
sys_state.last_humidity = humidity;
sys_state.sample_count++;
// 格式化并输出数据
char buffer[128];
MID_FormatSensorData(buffer, sizeof(buffer), temperature, humidity);
printf("%s", buffer);
// 检查异常情况
if (temperature > 40.0f || temperature < -10.0f) {
APP_SetMode(SYS_MODE_ERROR);
printf("Temperature out of range!\r\n");
}
} else {
// 读取失败
APP_SetMode(SYS_MODE_ERROR);
printf("Sensor read error!\r\n");
}
}
}
/**
* @brief 更新LED状态
* @retval None
*/
static void UpdateLEDStatus(void) {
switch (sys_state.mode) {
case SYS_MODE_IDLE:
DRV_LED_SetState(LED_STATUS, LED_STATE_BLINK_SLOW);
DRV_LED_SetState(LED_ERROR, LED_STATE_OFF);
break;
case SYS_MODE_MONITORING:
DRV_LED_SetState(LED_STATUS, LED_STATE_ON);
DRV_LED_SetState(LED_ERROR, LED_STATE_OFF);
break;
case SYS_MODE_ERROR:
DRV_LED_SetState(LED_STATUS, LED_STATE_OFF);
DRV_LED_SetState(LED_ERROR, LED_STATE_BLINK_FAST);
break;
}
}
/**
* @brief 获取系统模式
* @retval 系统模式
*/
SystemMode_t APP_GetMode(void) {
return sys_state.mode;
}
/**
* @brief 设置系统模式
* @param mode: 系统模式
* @retval None
*/
void APP_SetMode(SystemMode_t mode) {
sys_state.mode = mode;
sample_timer = 0;
}
5.4 主函数实现¶
// main.c - 主函数
#include "app_main.h"
#include "drv_led.h"
#include "drv_button.h"
#include "hal_systick.h" // 假设已实现系统滴答定时器
int main(void) {
// 系统初始化
SystemInit();
HAL_SysTick_Init(); // 初始化系统滴答定时器(1ms)
// 应用层初始化
APP_Init();
// 主循环
while (1) {
// 应用层处理
APP_Process();
// 驱动层处理(每10ms调用一次)
static uint32_t last_tick = 0;
uint32_t current_tick = HAL_GetTick();
if (current_tick - last_tick >= 10) {
last_tick = current_tick;
DRV_LED_Process();
DRV_BUTTON_Process();
}
// 可以添加低功耗处理
// __WFI(); // 等待中断
}
}
应用层设计要点: - 实现业务逻辑和状态管理 - 调用下层接口,不直接操作硬件 - 使用回调函数处理异步事件 - 代码清晰,易于理解和维护
步骤6: 项目目录结构组织¶
6.1 推荐的目录结构¶
project/
├── HAL/ # 硬件抽象层
│ ├── hal_gpio.h
│ ├── hal_gpio.c
│ ├── hal_uart.h
│ ├── hal_uart.c
│ ├── hal_systick.h
│ └── hal_systick.c
├── Driver/ # 驱动层
│ ├── drv_led.h
│ ├── drv_led.c
│ ├── drv_button.h
│ ├── drv_button.c
│ ├── drv_sensor.h
│ └── drv_sensor.c
├── Middleware/ # 中间件层
│ ├── mid_format.h
│ ├── mid_format.c
│ ├── mid_filter.h
│ └── mid_filter.c
├── Application/ # 应用层
│ ├── app_main.h
│ └── app_main.c
├── Config/ # 配置文件
│ └── system_config.h
├── Docs/ # 文档
│ ├── architecture.md
│ └── api_reference.md
└── main.c # 主函数
6.2 头文件包含规则¶
规则1: 只包含必要的头文件
// 不好的做法
#include "hal_gpio.h"
#include "hal_uart.h"
#include "hal_i2c.h" // 未使用
// 好的做法
#include "hal_gpio.h"
#include "hal_uart.h"
规则2: 使用包含保护
规则3: 避免循环包含 - 使用前向声明 - 在.c文件中包含头文件 - 重新设计模块依赖关系
6.3 编译配置¶
在Makefile或IDE中配置包含路径:
# Makefile示例
INC_DIRS = -I./HAL \
-I./Driver \
-I./Middleware \
-I./Application \
-I./Config
CFLAGS = $(INC_DIRS) -Wall -O2
步骤7: 验证和测试¶
7.1 编译验证¶
步骤:
1. 清理项目: make clean
2. 编译项目: make all
3. 检查编译警告和错误
4. 确保所有层次的代码都能正确编译
预期结果: - 无编译错误 - 无编译警告(或只有可接受的警告) - 生成可执行文件
7.2 功能测试¶
测试用例1: LED控制测试
测试用例2: 按键功能测试
测试用例3: 数据采集测试
测试用例4: 异常处理测试
7.3 性能测试¶
测试项1: 响应时间 - 按键响应时间应小于50ms - LED状态更新应实时
测试项2: CPU占用率 - 空闲模式下CPU占用率应小于5% - 监测模式下CPU占用率应小于20%
测试项3: 内存使用 - 检查栈使用情况 - 检查堆使用情况 - 确保无内存泄漏
故障排除¶
问题1: 编译错误 - 找不到头文件¶
现象:
原因: - 头文件路径配置不正确 - 头文件名称拼写错误
解决方案: 1. 检查Makefile或IDE的包含路径配置 2. 确认头文件确实存在于指定目录 3. 检查头文件名称大小写
问题2: 链接错误 - 未定义的引用¶
现象:
原因: - 对应的.c文件未添加到编译列表 - 函数声明和定义不匹配
解决方案: 1. 确认.c文件已添加到Makefile或IDE项目中 2. 检查函数声明和定义是否一致 3. 检查是否有拼写错误
问题3: LED不闪烁¶
现象: - LED始终亮或始终灭 - LED不按预期闪烁
排查步骤: 1. 检查GPIO初始化是否正确 2. 确认DRV_LED_Process()是否被定期调用 3. 使用调试器查看LED状态变量 4. 检查硬件连接是否正确
问题4: 按键无响应¶
现象: - 按下按键没有任何反应
排查步骤: 1. 检查GPIO配置(输入模式、上下拉) 2. 确认DRV_BUTTON_Process()是否被定期调用 3. 检查回调函数是否正确注册 4. 使用万用表测试按键硬件
问题5: 数据采集异常¶
现象: - 传感器数据始终为0或异常值
排查步骤: 1. 检查传感器驱动初始化 2. 确认I2C或SPI通信是否正常 3. 使用逻辑分析仪查看通信波形 4. 检查传感器供电是否正常
总结¶
通过本教程,我们完成了一个完整的分层架构嵌入式系统设计:
关键要点:
- HAL层: 封装硬件操作,提供统一接口,便于移植
- 驱动层: 实现设备驱动,管理设备状态,提供高级功能
- 中间件层: 提供通用算法和工具,与硬件无关
- 应用层: 实现业务逻辑,调用下层接口
设计原则:
- 单向依赖:上层调用下层,下层通过回调通知上层
- 接口稳定:每层提供清晰稳定的接口
- 职责单一:每层只负责特定功能
- 最小知识:每层只了解相邻层的接口
实践收获:
- 掌握了分层架构的设计方法
- 学会了如何设计清晰的接口
- 理解了各层的职责和交互方式
- 能够独立设计分层架构系统
下一步建议:
- 尝试添加新的功能模块(如显示屏驱动)
- 实践移植到不同的MCU平台
- 学习更多的设计模式
- 深入学习RTOS架构
延伸阅读¶
参考资料¶
- "Embedded Software Development: The Open-Source Approach" - Ivan Cibrario Bertolotti
- "Design Patterns for Embedded Systems in C" - Bruce Powel Douglass
- "Making Embedded Systems" - Elecia White
- ARM CMSIS标准文档
- STM32 HAL库设计文档
练习题¶
练习1: 添加新的LED¶
任务: 在系统中添加第三个LED,用于指示数据采集状态
要求: - 在驱动层添加LED_DATA定义 - 数据采集时LED闪烁 - 空闲时LED熄灭
提示: - 修改drv_led.h和drv_led.c - 在应用层更新LED控制逻辑
练习2: 实现数据存储功能¶
任务: 添加数据存储功能,将采集的数据保存到Flash
要求: - 设计Flash HAL层接口 - 实现数据存储驱动 - 在应用层添加存储逻辑
提示: - 参考GPIO HAL层的设计方法 - 考虑数据格式和存储策略 - 注意Flash写入次数限制
练习3: 移植到不同平台¶
任务: 将项目移植到Arduino或ESP32平台
要求: - 只修改HAL层代码 - 其他层代码保持不变 - 功能完全一致
提示: - 分析目标平台的GPIO和UART接口 - 重新实现HAL层函数 - 测试验证所有功能
练习4: 添加命令行接口¶
任务: 通过串口实现简单的命令行接口
要求: - 支持查询当前状态 - 支持设置采集间隔 - 支持启动/停止监测
提示: - 在中间件层实现命令解析 - 在应用层处理命令 - 使用状态机管理命令处理流程
恭喜你完成了分层架构设计实践教程!
现在你已经掌握了嵌入式系统分层架构的核心设计方法。继续实践和探索,你将能够设计出更加优雅和强大的嵌入式系统。
下一步: 建议学习模块化设计原则,进一步提升你的架构设计能力。