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

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

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

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

实现方式:

复制代码
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 的任务进行管理。

相关推荐
切糕师学AI1 天前
NuttX RTOS是什么?
嵌入式·rtos
aspirestro三水哥2 天前
6.4非POSIXskin进程间通信
rtos·xenomai
aspirestro三水哥3 天前
6.2POSIX线程间通信
rtos·xenomai
鸿蒙小白龙4 天前
OpenHarmony轻量系统(Hi3861)RTOS API开发详解
openharmony·rtos·liteos·轻量系统
无聊到发博客的菜鸟13 天前
STM32 手册寄存器属性
stm32·单片机·嵌入式·rtos·寄存器
aspirestro三水哥13 天前
5.3RTDM用户层驱动
rtos·xenomai
无聊到发博客的菜鸟13 天前
STM32 RTC时钟不准的问题
stm32·嵌入式·rtc·rtos
aspirestro三水哥16 天前
4.7POSIX进程与线程实例
rtos·xenomai
无聊到发博客的菜鸟16 天前
使用STM32对SD卡进行性能测试
stm32·单片机·rtos·sd卡·fatfs
切糕师学AI18 天前
Azure RTOS ThreadX 简介
microsoft·嵌入式·azure·rtos