跳转至

嵌入式系统概述

嵌入式系统概述(中国大学Mooc)的课堂笔记


嵌入式系统的定义,组成及一般开发流程

目前国内普遍接受的概念[1]:嵌入式系统是以应用为中心,以计算机技术为基础,软硬件可裁剪,适于应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。

嵌入式系统的一般设计过程[1]: 1. 硬件原理图的设计 2. PCB设计 3. 硬件制板 4. 硬件元器件的安装 5. 嵌入式操作系统的移植 6. 嵌入式软件设计

嵌入式系统的设计,通常要考虑到实时性、可靠性、稳定性。具体的问题包括: + 硬件资源、运行环境和设计指标 + 升级方式 + 开发和测试策略

嵌入式的概念,是相对于通用计算机的,当然,这种通用也是相对的,嵌入式强调的是一种专用用途,应用场景常常是资源受限的,优先考虑的也不是用户友好性,是安全性和可靠性。嵌入式系统的用途非常广,包括但不限于无人驾驶、通信设备、军事武器等等。


嵌入式C语言编程写法及优化技巧

嵌入式开发过程中,最为常用的编程语言是C语言,原因很多,很重要的一个原因,内核通常是用C语言写的。

一般的单任务嵌入式程序典型架构是: 1. 从cpu复位时的指定地址开始执行 2. 跳转至汇编代码startup处执行,完成部分初始化动作 3. 跳转至用户主程序main执行,在main中完成: + 初始化部分硬件设备 + 初始化各软件模块 + 进入死循环(无线循环),调用各模块的处理函数

尽管C语言并不是一门面向对象的语言,但是,通过sturct和函数指针,仍然可以很好的模拟"类"的写法,使得写出的代码,具备很好的维护性。

以下,是这种写法的一种实例:

#define C_Class struct

C_Class A{
    C_Class A *A_this;

    void (*foo)(C_Class A*A_this);
    int (*parea)(int length, int width);

    int a;
    int b;
};

嵌入式平台中,最常用的芯片是arm系列,因此,如果需要处理一些性能敏感的场景,需要结合生成的arm汇编代码一起查看。

尽管现在的编译器已经非常智能,能够帮助程序员处理很多情况。但是,在一些存在歧义的情况下,编译器一般会采取保守的判断,以防止程序行为超出预期。因此需要对编译器有一定的了解,了解编译器在哪些方面是保守的,了解编译器对应的处理器体系结构。

以指针别名为例,来展示编译器的保守方面以及对应的优化策略:

考虑以下代码

void timers_v1(int *timer1, int *timer2, int *step)
{
    *timer1 += *step;
    *timer2 += *step;
}

对应的arm汇编为

LDR r3, [r0, #0]     --> r3 = *timer1
LDR r12,[r2, #0]     --> r12 = *step
ADD r3, r3, r12      --> r3 += r12
STR r3, [r0, #0]     --> *timer1 = r3
LDR r0, [r1, #0]     --> r0 = *timer2
LDR r2, [r2, #0]     --> r2 = *step
ADD r0, r0, r2       --> r0 += r2
STR r0, [r1, #0]     --> *timer2 = r0
MOV pc, r14          --> return

可以看到, step指针指向的值被取出来两次,有一次是明显多余的。出现这种情况的原因,是编译器必须考虑所有可能的情况,假设step跟timer1指向同一个地址,那么第一次运算后,timer和step的值都被更新了,所以第2次重取step的值,才能保证在这种情况下,行为也符合预期。

但是如果程序员确定step和timer指向不同的地址,那么可以采取下面的写法,来减少一次step取值:

void timers_v2(int *timer1, int *timer2, int *step)
{
    int tmp = *step;
    *timer1 += tmp;
    *timer2 += tmp;
}

除此之外,数据结构在内存中的布局也能极大影响程序的效率,如果结构的成员变量不全是内存对齐的,那么在取值时,就需要多余的操作。

总之,嵌入式程序的优化,是在基于对所写程序面向的场景,编译器和程序语言的了解上,使得生成的arm汇编最为精简。需要说明的是,效率最优并不总是程序首先考虑的事情,代码的可维护性以及优化所要付出的努力和优化带来的效果对比,这些都是优化前所要考虑的事情。


Linux C的编程工具链以及构建系统

嵌入式开发过程中,涉及到的编译器,调试器,构建工具等等一系列的工具,被称之为工具链。

嵌入式系统开发中,开发环境和运行环境常常是不同架构的,因此就需要交叉编译,即在开发环境中编译出与之不同的运行环境的可执行文件。

当前使用最为广泛的为GNU工具链,GNU支持本地程序开发,也交叉编译,即GNU工具链包括 : + GNU Tools (GNU Development Toolchains) + GNU Tools交叉开发环境 (GNU Cross-Platform Development Toolchains)

嵌入式C语言最早流行的构建系统是make,后来进一步发展出了CMake,CMake足够处理一些常见的项目场景,但在更复杂的构建场景中,比如系统的生成需要从多个仓库获取源码,进行编译链接时。对于这种复杂的场景,当前比较主流的构建系统是yocto。

一个嵌入式系统通常由3部分构成: + Bootloader + 嵌入式操作系统内核 + 文件系统

Bootloader可以分为单阶段和多阶段。多阶段的Bootloader能提供更加复杂的功能,以及更好的移植性。大多数Bootloader都有两种工作模式,分别是Bootloader自动加载操作系统运行,全程不需要用户介入,即正常工作模式;以及通过串口或者网络等方式首先下载所需文件,再进行后续引导,即下载模式,通常首次启动时使用。


嵌入式系统书籍推荐

  1. 嵌入式软件设计(清华大学出版社)
  2. ARM嵌入式系统开发 - 软件设计与优化(北京航空航天大学出版社)
  3. Linux程序设计(人民邮电出版社)
  4. Linux设备驱动程序(中国电力出版社)
  5. Linux设备驱动开发详解(人民邮电出版社)
  6. Proteus电子电路设计及仿真(电子工业出版社)


参考

  1. 嵌入式系统概述(中国大学MOOC)