嵌入式系统的核心是嵌入式处理器。嵌入式处理器一般划分为如下类型:

嵌入式系统分层结构一般可如下:

两层结构:硬件层、应用层(软件层)                                                         诸如:  MCU裸机系统

三层结构:硬件层(原厂芯片用库函数封装驱动)、操作系统层、应用层   诸如:  MCU的RTOS系统

四层结构:硬件层、驱动层、操作系统层、应用层                                   诸如:  嵌入式Linux系统

嵌入式开发与一般的软件开发不同,嵌入式系统通常受到资源(内存、处理器、功耗等)的限制,并且需要处理硬件交互、实时性要求等。因此,嵌入式开发需要程序员具备一些特殊的编程思维。必要了解一些开发接口标准。(设计思路可以是组合一些编程思维以及采用一些接口标准)

嵌入式开发要求程序员具备比通用编程更多的专业思维,包括对资源限制、实时性、功耗、硬件交互等方面的深入理解。此外,嵌入式系统的可靠性要求极高,开发者必须时刻考虑到如何设计出健壮、低功耗、实时性强的系统。POSIX(Portable Operating System Interface for Unix,可移植操作系统接口)是一个由 IEEE(电气和电子工程师协会)制定的标准,旨在确保不同的 Unix 系统能够具有统一的接口,使得程序能够在不同的系统之间移植,而无需进行大量修改。POSIX 标准为 Unix 操作系统及其兼容系统提供了一套应用程序编程接口(API),它定义了系统调用、命令行工具和脚本编程规范等内容。

开发经验:通过合理运用这些设计思维及标准接口,可以更好地应对各种复杂的开发挑战,确保系统稳定高效地运行及良好的可移植性。

POSIX标准的出现就是为了解决跨平台接口的可调整。linux和windows都要实现基本的POSIX标准

完成同一功能,不同内核提供的系统调用(也就是一个函数)是不同的。

在各个平台下,默认C标准库中的函数都是一样的,这样基本可以实现可移植。但是对于C库本身而言,在各种操作系统平台下其内部实现是完全不同的,即C库封装了操作系统API在其内部的实现细节。

因此,C语言提供了在代码级的可移植性,即这种可移植是通过C语言这个中间层来完成的。

/*
   实现代码的可移植
*/
#ifndef _WINDOWS_
       #include <windows.h>
#else
       #include <thread.h>
#endif

POSIX重要组成

系统接口(System Interface)定义了应用程序如何与操作系统交互。主要包括系统调用(如创建进程、文件操作、进程间通信等)以及信号机制、内存管理、文件描述符等。(在设计模块时可以对各类标准接口进行抽象及封装)

  • 文件系统接口:包括打开、关闭、读取、写入、删除文件的操作。POSIX 标准定义了文件的基本操作,确保在不同的系统上行为一致。其中涉及到一些CPU执行权的变换(用户态–>系统调用–>内核态–>返回用户态)

  • 进程管理接口:涉及创建、终止、控制进程的操作,尤其是进程间的同步、信号的传递等。

  • 线程管理接口:POSIX 还规定了多线程编程的标准接口(称为 POSIX 线程或 pthreads),使得多线程程序在不同的系统上能统一运行。

  • 输入输出(I/O)接口:POSIX 定义了对设备的访问和数据传输的标准方式,包括标准输入输出(stdin, stdout, stderr)和文件操作接口。

POSIX 定义了一个标准的命令行接口,确保用户可以通过 shell 脚本和命令行工具操作操作系统。例如,ls(列出目录)、cp(复制文件)、mv(移动文件)等标准工具,都属于 POSIX 规范的一部分。

  • Shell 和脚本:POSIX 定义了一个标准的 shell 环境,标准化了脚本的编写方式,确保脚本可以在不同的系统上执行。POSIX shell 定义了基本的命令、流程控制、变量等标准。

定义了网络编程的接口,包括套接字(socket)API,它是实现网络通信的重要工具。通过 POSIX 网络接口,程序可以建立、接收、发送网络连接,使得跨平台的网络应用变得可能。

提供了进程间通信(IPC)的机制,包括信号、管道、共享内存、消息队列、信号量等,帮助不同进程之间交换数据和同步。(经典的有POSIX消息队列的API)

定义了线程的创建、管理和同步机制,称为 POSIX 线程(pthreads)。它使得程序能够创建多线程应用,处理并发任务,且能够在支持 POSIX 的操作系统上实现。

 作用

跨平台兼容性

  • 通过统一的接口,POSIX 使得程序能够在不同的 Unix 系统上运行,而无需做太多修改。比如,一个遵循 POSIX 标准的程序,可以在 Linux、macOS 或其他 POSIX 兼容的系统中运行。

提高可移植性

  • POSIX 标准的目的是让程序员不必考虑底层操作系统的实现细节,只需要遵循 POSIX 规范,就能确保应用在不同的操作系统上行为一致。

系统一致性

  • POSIX 使得不同操作系统之间的行为趋同,这为开发人员提供了一个一致的开发环境,从而减少了不同操作系统的差异带来的复杂性。

资源限制

嵌入式系统往往运行在资源受限的环境中,如内存(像开发板的片内内存SRAM是非常珍贵的)、处理器速度、电池寿命等。

开发必须始终考虑如何优化资源的使用。每一行代码都可能影响性能、内存占用和功耗。

例如,在嵌入式系统中,内存管理是个重要问题。与通用操作系统不同,嵌入式系统可能没有虚拟内存支持(这里讨论MCU架构,它没有内存管理单元(MMU)),因此开发者需要仔细管理堆栈和堆的使用,防止内存溢出。

// 使用静态分配而不是动态分配来减少内存碎片,避免运行时的堆分配
static char buffer[1024];  

实时性

许多嵌入式系统具有实时性要求,意味着系统必须在特定时间内响应外部事件。

开发需要掌握如何设计和优化系统的响应时间,同时保证系统的确定性,避免过多的阻塞调用和高延迟操作。

例如,在MCU上一个典型的例子是实时操作系统(RTOS)中任务调度的设计,开发者需要确保关键任务按时完成,而低优先级任务则可以在空闲时间运行。

/*
    例如在FreeRTOS中,创建了任务栈后,可以指定入口函数完成某些特殊工作
*/

// 高优先级任务,必须在毫秒级内响应
void critical_task() {    
    process_sensor_data();
}

// 低优先级任务,可以在系统空闲时运行  
void idle_task() {    
    log_data();
}

(在嵌入式Linux这样的分时操作系统则可以使用POSIX标准来完成多任务并发的任务调度设计)

硬件抽象

嵌入式系统与硬件密切相关,比如传感器控制或者协议控制器的使用

开发需要具备硬件抽象的思维,能够有效设计硬件抽象层(HAL),将硬件的细节封装起来,使得上层应用无需直接处理底层硬件细节。这种思维有助于提高代码的可移植性。

例如,开发者可以通过定义通用的硬件接口来屏蔽具体的硬件差异。例如,不同平台的GPIO控制可以通过硬件抽象层来统一处理。(这其中略微涉及设计模式中的抽象工厂模式)

// 硬件抽象层的GPIO接口
typedef struct {    
    void (*init)(void);    
    void (*set_pin)(int pin, int value);    
    int (*read_pin)(int pin);
    void (*remove)(void);
} GPIO_Interface;

// 使用HAL来与不同硬件交互
GPIO_Interface gpio_driver = get_gpio_driver();
gpio_driver.set_pin(13, 1);  // 设置引脚13的电平为高

中断驱动

嵌入式系统通常依赖中断机制来处理外部事件(中断是实现异步通信的一种手段)。

中断驱动思维要求设计系统时考虑中断的优先级、响应时间、中断嵌套问题,并且在中断服务程序(ISR)中尽量减少操作以保证高效的中断处理。在高级的操作系统中还有,中断上下文的划分。

例如,处理按键输入的嵌入式系统通常采用中断驱动模式来避免轮询消耗过多CPU资源。

/*
    GNU C 的一大特色就是__attribute__ 机制。
__attribute__ 可以设置函数属性(Function Attribute)、变量属性(Variable Attribute )和
类型属性(Type Attribute )
*/
void __attribute__((interrupt)) button_isr() {    
    // 快速处理中断事件,延迟处理的任务放到主循环中
    button_pressed_flag = 1;    
}

功耗优化

在电池供电的嵌入式系统中,功耗优化至关重要。

开发需要学习如何通过减少处理器的活动时间、使用低功耗模式、优化代码执行效率等方式来延长设备的工作时间。

例如,在睡眠模式下,处理器可以进入低功耗状态,仅在需要时通过外部中断唤醒。

void enter_sleep_mode() {    
    // 配置系统进入低功耗模式    
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);    
    sleep_enable();    
    sleep_cpu();  // 进入睡眠,等待中断唤醒
}

可靠性和容错控制

嵌入式系统通常运行在对可靠性要求极高的环境中,如医疗设备、汽车控制系统等。

开发必须具备可靠性和容错思维,设计系统时要考虑到如何处理故障、如何检测错误以及如何恢复系统(在MCU开发中可以使用看门狗定时器来监视程序是否跑飞)。

例如,在开发汽车的电子控制单元(ECU)时,需要考虑如何检测传感器故障并提供降级模式以确保系统安全运行。

void check_sensor_data() {    
    if (sensor_reading == INVALID_VALUE) {        
        // 传感器数据无效,启用故障安全模式        
        activate_failsafe_mode();    
    }
}

并发和同步控制

嵌入式系统中常常需要处理多个任务或线程,尤其是在实时操作系统环境下或多任务并发。

开发需要具备并发和同步思维,能够设计出合理的多任务系统,处理好资源共享和数据一致性问题,避免竞态条件和死锁。

例如,假设有多个任务需要访问同一个共享资源,开发者需要确保通过锁或信号量机制来实现互斥访问。

// 使用互斥锁保护共享资源
void access_shared_resource() {   
    osMutexWait(resource_mutex, osWaitForever);    
    // 访问共享资源    
    shared_resource++;    
    osMutexRelease(resource_mutex);
}

故障排除与调试

嵌入式系统的调试通常比传统软件复杂,因为嵌入式设备可能没有完整的操作系统,也缺乏标准的调试工具,异或是在交叉编译开发的环境下。

开发需要具备较强的故障排除与调试思维,包括通过串口日志、JTAG调试器、LED指示灯等方式来进行调试。

 例如,在没有标准显示设备的嵌入式系统中,开发者可以通过串口输出日志或使用LED来指示程序的状态,以便进行调试。

/*
    编写内核驱动时,可以使用printk 来打印输出调试
*/

// 使用串口输出调试信息
printf("System initialized successfully.\n");

状态机

嵌入式系统中的许多应用都可以通过状态机来描述,尤其是在处理复杂的逻辑控制时。

状态机思维可以帮助开发人员清晰地规划系统的状态转移,避免混乱的控制逻辑。

例如,在实现一个简单的洗衣机控制程序时,可以通过状态机来管理各个操作步骤(如加水、加热、搅拌、脱水等)的切换。

typedef enum {    
    IDLE,    
    FILL_WATER,    
    HEATING,    
    WASHING,    
    DRAIN_WATER,    
    SPINNING
} washing_machine_state;

void washing_machine_update() {    
    switch (current_state) {        
        case FILL_WATER:            
            if (water_level_reached()) {
                current_state = HEATING;            
            }            
            break;        
        // 其他状态处理逻辑    
    }
}

总而言之:

POSIX 是为了确保 Unix 系统之间的兼容性和可移植性而设计的一个标准,它通过定义系统调用、文件管理、进程控制、线程管理等接口,帮助开发者编写可以跨平台运行的程序。通过遵循 POSIX,程序可以避免针对不同操作系统进行大量修改,提高软件的移植性和可维护性。而设计思维则引导如何设计出健壮、低功耗、实时性强的系统。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐