目录
[一、Demo 坏代码示例(](#一、Demo 坏代码示例()
[二、四层分层模型(裸机、RTOS 通用)](#二、四层分层模型(裸机、RTOS 通用))
[1. 驱动层 driver](#1. 驱动层 driver)
[2. 组件层 component](#2. 组件层 component)
[3. 业务层 app](#3. 业务层 app)
[4. 调度层 scheduler](#4. 调度层 scheduler)
[四、RTOS 下简单改造要点](#四、RTOS 下简单改造要点)
前言
本篇聚焦工程重构实战,区分 demo 代码和产品代码,讲解分层原则、模块解耦、裸机与 RTOS 工程改造,附带极简示例代码,为后面进阶专题做铺垫。
一、Demo 坏代码示例(
特征:业务、驱动混在一起,大量全局变量,直接操作寄存器,到处魔数
#include "stm32f10x.h"
uint16_t adc_val;
uint8_t flag;
int main(void)
{
RCC->APB2ENR |= (1<<2)|(1<<9);
GPIOA->CRL &= ~0x0000000F;
ADC1->CR2 |= 1;
while(1)
{
ADC1->SWSTART |= 1;
while(!(ADC1->SR & (1<<1)));
adc_val = ADC1->DR;
if(adc_val > 3000)
{
GPIOA->BSRR = 1<<5;
flag = 1;
}
else
{
GPIOA->BRR = 1<<5;
flag = 0;
}
}
}
问题:
- 寄存器直接散落在 main,换芯片全部重写
- ADC 采集、判断逻辑、IO 控制全部耦合
- 魔数
3000、引脚 5 硬编码,修改要多处改动- 没有封装,无法复用,不能单元测试
二、四层分层模型(裸机、RTOS 通用)
1. 驱动层 driver
只做硬件操作,不含业务逻辑。对外只提供函数接口。
driver_adc.h
#ifndef __DRIVER_ADC_H
#define __DRIVER_ADC_H
#include <stdint.h>
void drv_adc_init(void);
uint16_t drv_adc_get_sample(void);
#endif
driver_adc.c
#include "driver_adc.h"
#include "stm32f10x.h"
void drv_adc_init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_ADC1EN;
GPIOA->CRL &= ~GPIO_CRL_CNF0;
ADC1->CR2 |= ADC_CR2_ADON;
}
uint16_t drv_adc_get_sample(void)
{
ADC1->SWSTART |= ADC_CR2_SWSTART;
while(!(ADC1->SR & ADC_SR_EOC));
return ADC1->DR;
}
driver_led.h
#ifndef __DRIVER_LED_H
#define __DRIVER_LED_H
void drv_led_init(void);
void drv_led_set(uint8_t on);
#endif
driver_led.c
#include "driver_led.h"
#include "stm32f10x.h"
#define LED_PIN GPIO_Pin_5
#define LED_PORT GPIOA
void drv_led_init(void)
{
GPIO_InitTypeDef gpio_cfg;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
gpio_cfg.GPIO_Pin = LED_PIN;
gpio_cfg.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_cfg.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(LED_PORT, &gpio_cfg);
}
void drv_led_set(uint8_t on)
{
if(on)
GPIO_SetBits(LED_PORT, LED_PIN);
else
GPIO_ResetBits(LED_PORT, LED_PIN);
}
2. 组件层 component
数据处理、工具组件,不操作硬件。 component_filter.h 简单滑动滤波示例
#ifndef __COMPONENT_FILTER_H
#define __COMPONENT_FILTER_H
#include <stdint.h>
#define FILTER_BUF_LEN 8
typedef struct
{
uint16_t buf[FILTER_BUF_LEN];
uint8_t index;
}filter_slide_t;
void filter_slide_init(filter_slide_t *f);
uint16_t filter_slide_calc(filter_slide_t *f, uint16_t new_val);
#endif
component_filter.c
#include "component_filter.h"
void filter_slide_init(filter_slide_t *f)
{
for(int i=0;i<FILTER_BUF_LEN;i++)
f->buf[i] = 0;
f->index = 0;
}
uint16_t filter_slide_calc(filter_slide_t *f, uint16_t new_val)
{
f->buf[f->index++] = new_val;
if(f->index >= FILTER_BUF_LEN) f->index = 0;
uint32_t sum = 0;
for(int i=0;i<FILTER_BUF_LEN;i++)
sum += f->buf[i];
return sum / FILTER_BUF_LEN;
}
3. 业务层 app
只处理业务逻辑,调用驱动与组件,不碰寄存器。 app_detect.h
#ifndef __APP_DETECT_H
#define __APP_DETECT_H
void app_detect_init(void);
void app_detect_task(void);
#endif
app_detect.c
#include "app_detect.h"
#include "driver_adc.h"
#include "driver_led.h"
#include "component_filter.h"
#define ADC_THRESHOLD 3000
static filter_slide_t adc_filter;
void app_detect_init(void)
{
drv_adc_init();
drv_led_init();
filter_slide_init(&adc_filter);
}
void app_detect_task(void)
{
uint16_t raw = drv_adc_get_sample();
uint16_t val = filter_slide_calc(&adc_filter, raw);
if(val > ADC_THRESHOLD)
{
drv_led_set(1);
}
else
{
drv_led_set(0);
}
}
4. 调度层 scheduler
裸机时间戳轮询调度,分离各个业务的执行节拍,消除阻塞 delay。 scheduler.h
#ifndef __SCHEDULER_H
#define __SCHEDULER_H
void scheduler_init(void);
void scheduler_run(void);
#endif
scheduler.c
#include "scheduler.h"
#include "app_detect.h"
#include "stm32f10x.h"
static uint32_t tick_ms = 0;
void SysTick_Handler(void)
{
tick_ms++;
}
void scheduler_init(void)
{
SysTick_Config(SystemCoreClock / 1000U);
app_detect_init();
}
void scheduler_run(void)
{
static uint32_t t1 = 0;
if(tick_ms - t1 >= 20)
{
t1 = tick_ms;
app_detect_task();
}
}
main.c 最终入口,极度简洁
#include "scheduler.h"
int main(void)
{
scheduler_init();
while(1)
{
scheduler_run();
}
}
核心原则:上层可以调用下层,下层绝对不调用上层。禁止跨层直接访问变量。
三、重构实操核心规则
- 单一职责:一个函数只完成一件事,拒绝几百行大函数。
- 模块拆分 :一类功能放到同一组
.c/.h,内部变量尽量用static私有化,头文件只对外暴露必要接口。- 禁止跨层访问:app 不能直接调用寄存器;driver 层不能包含业务判断。
- 魔数全部宏定义,阈值、引脚、周期集中管理。
- 大数组禁止放局部栈内,使用 static 或者全局。
- 中断只做标记,业务处理交给调度层任务。
四、RTOS 下简单改造要点
以 FreeRTOS 举例,业务层几乎不用改,调度层换成任务:
void app_detect_task_thread(void *arg)
{
(void)arg;
app_detect_init();
for(;;)
{
app_detect_task();
vTaskDelay(pdMS_TO_TICKS(20));
}
}
//main中创建任务
xTaskCreate(app_detect_task_thread, "detect", 512, NULL, 2, NULL);
注意:多任务访问共享数据,需要加互斥锁保护。
五、头文件书写规范(工程必守)
.h只放:函数声明、宏、typedef,不能放变量定义 ;变量定义放在.c,h 用extern引出。- 头文件保护宏
#ifndef / #define / #endif必须写。- 尽量少 include 其他头文件在
.h,减少编译依赖。
