嵌入式软件的几种程序架构

嵌入式软件的程序架构是其设计的核心,它决定了代码的组织方式、可维护性、可扩展性以及资源利用效率。下面详细介绍几种主流的嵌入式软件程序架构,从简单到复杂,并分析其优缺点和适用场景。

一、前后台系统(超级循环架构)

这是最简单、最常见的嵌入式程序架构,尤其适用于资源极度受限的单片机系统。

实现方式:

复制代码
int main(void) {
	// 初始化    
	init_all();
	while(1) {
	// 前台任务       
	task1();
	task2();
	task3();
	// 后台中断处理        
    // 由硬件中断自动完成    
   }
}

优点:

  • 简单直观,易于理解和实现

  • 资源占用极少,无额外开销

  • 适合逻辑简单的控制任务

缺点:

  • 任务之间无优先级,响应性差

  • 长时间任务会阻塞整个系统

  • 可维护性差,任务耦合度高

适用场景:

  • 8位、16位单片机

  • 任务量少、逻辑简单的系统

  • 对成本极度敏感的场合

二、时间片轮询架构

在超级循环的基础上引入了简单的时间片调度,提高了任务执行的合理性。

实现方式:

复制代码
typedef struct {
	void (*task)(void); // 任务函数   
    uint32_t interval;  // 执行间隔   
    uint32_t last_run;  // 上次执行时间
    task_t;
	task_t tasks[] = { {
			task1, 10, 0
		}
		, {
			task2, 20, 0
		}
		, {
			task3, 50, 0
		}
	}
	;
	void scheduler(void) {
		uint32_t current_time = get_tick_count();
		for (int i = 0; i < TASK_COUNT; i++) {
			if(current_time - tasks[i].last_run >= tasks[i].interval) {
				tasks[i].task();
				tasks[i].last_run = current_time;
			}
		}
	}

优点:

  • 相对简单,资源开销小

  • 任务调度更加合理

  • 可以控制任务执行频率

缺点:

  • 仍然无法实现真正的优先级

  • 实时性有限

  • 长时间任务仍会影响其他任务

适用场景:

  • 任务执行周期相对固定的系统

  • 对实时性要求不高的场合

  • 资源有限的32位单片机

三、实时操作系统(RTOS)架构

使用实时操作系统进行任务调度,是嵌入式开发的重要发展方向。

基于FreeRTOS的实现:

复制代码
void task1(void *params) {
    while(1) {
        // 任务1处理
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

void task2(void *params) {
    while(1) {
        // 任务2处理
        vTaskDelay(200 / portTICK_PERIOD_MS);
    }
}

int main(void) {
    // 初始化
    init_all();

    // 创建任务
    xTaskCreate(task1, "Task1", 128, NULL, 2, NULL);
    xTaskCreate(task2, "Task2", 128, NULL, 1, NULL);

    // 启动调度器
    vTaskStartScheduler();

    return 0;
}

优点:

  • 真正的多任务,支持优先级

  • 优秀的实时响应特性

  • 提高代码的可维护性和可扩展性

缺点:

  • 需要额外的资源开销(ROM、RAM)

  • 学习曲线相对陡峭

  • 增加了系统复杂度

适用场景:

  • 复杂的多任务系统

  • 对实时性要求高的场合

  • 资源相对丰富的32位单片机

四、事件驱动架构

基于状态机或事件队列的架构,适合处理大量的异步事件。

状态机实现示例:

复制代码
typedef enum {
    STATE_IDLE,
    STATE_RUNNING,
    STATE_PAUSED,
    STATE_ERROR
} system_state_t;

typedef enum {
    EVT_START,
    EVT_STOP,
    EVT_PAUSE,
    EVT_RESUME,
    EVT_ERROR
} event_t;

void state_machine(event_t event) {
    static system_state_t current_state = STATE_IDLE;

    switch(current_state) {
        case STATE_IDLE:
            if(event == EVT_START) {
                start_operation();
                current_state = STATE_RUNNING;
            }
            break;

        case STATE_RUNNING:
            if(event == EVT_STOP) {
                stop_operation();
                current_state = STATE_IDLE;
            } else if(event == EVT_PAUSE) {
                pause_operation();
                current_state = STATE_PAUSED;
            }
            break;

        // 其他状态处理...
    }
}

优点:

  • 适合处理复杂的异步事件

  • 状态清晰,易于调试

  • 资源利用率高

缺点:

  • 状态过多时复杂度高

  • 需要仔细设计状态转换

适用场景:

  • 通信协议处理

  • 用户界面交互

  • 复杂的业务流程控制

五、面向对象/组件化架构

这是一种设计思想,可以与上述任何一种架构结合使用,尤其是在中大型嵌入式项目中。

核心思想:使用 C 语言的结构体和函数指针来模拟 C++ 的类和对象,将硬件和外设抽象成独立的、可复用的"驱动"或"组件"。

实现方式

定义一个结构体,包含该"对象"的数据(属性)和操作这些数据的函数指针(方法)。通过创建该结构体的实例(对象)来使用它。

复制代码
// led.h - LED "类"的定义
typedef struct {
    GPIO_TypeDef *port;
    uint16_t pin;
    void (*on)(void);
    void (*off)(void);
    void (*toggle)(void);
} Led_TypeDef;

// 构造函数
void Led_Init(Led_TypeDef *led, GPIO_TypeDef *port, uint16_t pin);

// led.c
static void _led_on(Led_TypeDef *this) {
    HAL_GPIO_WritePin(this->port, this->pin, GPIO_PIN_SET);
}
// ... 其他静态函数

void Led_Init(Led_TypeDef *led, GPIO_TypeDef *port, uint16_t pin) {
    led->port = port;
    led->pin = pin;
    led->on = _led_on;
    led->off = _led_off;
    led->toggle = _led_toggle;
}

// main.c
Led_TypeDef led1, led2;
int main(void) {
    // ... 初始化
    Led_Init(&led1, GPIOA, GPIO_PIN_5);
    Led_Init(&led2, GPIOC, GPIO_PIN_13);

    while (1) {
        led1.toggle(); // 使用对象的方法
        led2.on();
        HAL_Delay(500);
    }
}

优点

  • 高内聚、低耦合:代码模块化程度高,易于复用和维护。

  • 隐藏实现细节:使用者只需关心接口,无需了解底层硬件操作。

  • 易于测试:可以方便地进行单元测试和模拟。

缺点

  • 代码结构稍复杂:引入了间接的函数调用。

  • 需要良好的设计:设计不当会导致过度工程化。

适用场景

中大型项目、产品线需要硬件平台迁移或驱动复用的场合。

六、如何选择合适的程序架构

考虑因素一:硬件资源

资源极度受限(Flash < 16KB,RAM < 2KB):

推荐使用前后台系统或简单的时间片轮询,避免RTOS带来的额外开销。

资源中等(Flash 16-64KB,RAM 2-8KB):

可以根据复杂度选择时间片轮询或轻量级RTOS,如FreeRTOS、μC/OS等。

资源丰富(Flash > 64KB,RAM > 8KB):

优先考虑RTOS架构,提高系统的可维护性和扩展性。

考虑因素二:实时性要求

实时性要求不高:

前后台系统或时间片轮询可以满足需求。

有硬实时要求:

必须使用RTOS,并合理设置任务优先级。

考虑因素三:系统复杂度

简单控制系统:

选择前后台系统,快速开发。

中等复杂度:

时间片轮询或简单RTOS。

复杂系统:

RTOS配合状态机或事件驱动架构。

考虑因素四:团队能力和开发周期

团队经验不足、周期紧张:

选择熟悉的简单架构,降低风险。

团队技术能力强、周期充足:

可以选择更先进的架构,为后续维护和扩展打好基础。

七、实际项目中的混合架构

在实际项目中,经常采用混合架构来平衡各种需求:

复制代码
// RTOS + 状态机的混合架构示例
void high_priority_task(void *params) {
    // 高优先级实时任务
    while(1) {
        // 处理紧急事件
    }
}

void state_machine_task(void *params) {
    // 状态机任务
    while(1) {
        process_events();
        vTaskDelay(10 / portTICK_PERIOD_MS);
    }
}

void background_tasks(void *params) {
    // 低优先级后台任务
    while(1) {
        // 数据处理、日志记录等
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

八、总结

架构 复杂度 实时性 资源占用 适用场景
前后台系统 极低 简单控制、8/16位MCU、成本敏感
时间片轮询 中低 任务周期固定、执行时间短
RTOS 中高 复杂多任务、高实时性、32位MCU
面向对象/组件化 设计思想 取决于底层架构 略有增加 中大型项目、强调可维护性和复用性
  1. 先评估项目需求:任务数量、实时性要求、硬件资源(CPU主频、RAM、Flash)、团队技术栈、项目工期。

  2. "杀鸡勿用牛刀":对于简单应用,前后台或时间片轮询是最经济高效的选择。

  3. 拥抱复杂性:当系统需要处理多个不相关且时序要求严格的事件时,RTOS 是必然选择。

  4. 考虑长期维护:对于需要长期开发和维护的产品,采用组件化设计思想,即使是在 RTOS 或时间片轮询之上,也会带来巨大好处。

在实际项目中,这些架构也常常是混合使用的。例如,在 RTOS 中,每个任务内部可能使用一个小型的状态机;整个软件的驱动层采用组件化设计,而应用层则使用 RTOS 的任务进行管理。

相关推荐
华清远见IT开放实验室7 天前
STM32学习路线!软硬件兼修:裸机+RTOS+LVGL+硬件设计+项目实战 (STM32多核心开发板)
stm32·嵌入式·开发板·rtos·课程·软硬件·裸机开发
poemyang14 天前
职责分离的艺术:剖析主从Reactor模型如何实现极致的并发性能
rpc·reactor·事件驱动
poemyang15 天前
从C10K到Reactor:事件驱动,如何重塑高并发服务器的网络架构
rpc·reactor·事件驱动
linweidong19 天前
跨平台驱动开发:打造兼容多款MCU的硬核方案
驱动开发·单片机·嵌入式硬件·bsp·rtos·spi驱动·hal设计
义薄云天us1 个月前
Apache NuttX 入门指南
apache·rtos·nuttx
要做朋鱼燕1 个月前
解析UART空闲中断与DMA接收机制
开发语言·笔记·单片机·嵌入式硬件·rtos·嵌入式软件
Nerd Nirvana2 个月前
C++编程——异步处理、事件驱动编程和策略模式
开发语言·c++·策略模式·嵌入式开发·事件驱动·异步处理
jz-炸芯片的zero2 个月前
【Zephyr炸裂知识系列】11_手撸内存泄露监测算法
驱动开发·算法·iot·rtos·内存泄露·zephyr
乖乖是干饭王3 个月前
FreeRTOS源码分析二:task启动(RISCV架构)
架构·c·riscv·rtos