1.3 嵌入式系统软件

硬件是计算机系统的基础设施,软件决定计算机系统具有什么样的功能。图书馆内供读者查询信息的设备就是一台标准的桌面计算机, 银行或政府办事大厅的排队取号、叫号设备也是一台标准的桌面计算机,这些设备的硬件区别几乎仅有可能是外设,决定这些设备功能 的因素只有软件,准确地说软件指的是应用程序,如图书馆信息查询系统软件、排队系统软件等。请注意,本书不刻意区分“程序和软件”。

事实上,所有计算机系统的硬件只能执行几十到几百种机器指令,无论是数百万行的应用程序或是几百行应用程序都是为了实现 某种特定功能而有序地组合起来的机器指令。机器指令从诞生那一刻起就是二进制形式表示,人类很难读懂机器指令,所以现代的所有 计算机体系架构的每一种机器指令都有方便人类识别的指令助记符。指令助记符,也称汇编指令。

或许你从来没有见过汇编指令级别的程序,即使见到这样的程序,你或许也觉得可读性极差!你能想象计算机诞生的早期的那些程序员们 使用机器指令/二进制码编写程序的难度吗?从上世纪70年代开始,计算机科学的一个重要分支就是高级编程语言及其编译器的研究, Python、C/C++、Java等高级编程语言大大地提高程序员们的工作效率,使用很少的语句就能实现很强大的功能。高级编程语言输出 的软件必须经过一系列的处理最终变成特定计算机系统硬件能够认识并执行的机器语言程序。我们通常把高级语言程序处理为机器语言程序 的软件统称为工具链,包括编译器(将高级语言程序编译成特定机器的汇编语言程序)和汇编器(将汇编语言程序、汇编库、二进制库、常量等 映射到特定硬件的程序存储器可用的机器码程序)。以ARMv8机器指令集为例,我们将一段C程序编译为汇编语言程序并通过汇编转换为 二进制机器码的过程 [1]_,如图1.10所示。

../_images/c_asmble_machinecode.jpg

图1.10 将高级语言程序转换为汇编语言程序再转换为机器语言程序的流程

高级编程语言不仅提高程序员的工作效率,还能让程序员使用接近自然语言的方式来思考问题和解决问题。二进制格式的机器语言程序是 为特定机器订制的,但高级编程语言的程序是可移植的,譬如我们使用不同的工具链可以将同一个C/C++程序转换不同机器的机器语言程序。 当然,任何高级语言程序的可移植性还受限于程序本身,或者说受限于程序员的工作。


具有桌面计算机编程经历的绝大多数人都写过将字符串“hello world”显示在屏幕上的最简单应用程序,这个程序最关键的几行 代码就是告知编译器“加载标准输入/输出库”,并使用标准输入/输出库的字符流输出“hello world”字符串。即使需要编写、编译、执行如此 简单的程序之前必须安装文本编辑器软件、桌面计算机CPU和高级编程语言相关的工具链等软件,大多数情况你的确只安装了一个软件, 我们应该称之为“集成开发环境(IDE)”,譬如Visual Studio Code及C/C++工具链安装包。IDE本身是桌面计算机的一种应用程序,专门 用来编辑高级语言程序的文本,并将高级语言程序编译、汇编成为桌面计算机能够执行的机器指令程序。

在桌面计算机上编写高级语言程序、编译、汇编产生的本机可执行的机器语言程序,这个过程所用到的工具已有很多种选择,甚至高级语言 本身也有很多种选择。我们在桌面计算机上编写高级语言程序,能否在嵌入式计算机系统上执行呢?答案是肯定的。基于前一节的知识, 我们很容易想象嵌入式计算机系统的机器语言程序(二进制程序)必须装在到微控制器芯片内的程序存储器中执行,这与桌面计算机的应用程序 的执行完全不同。以C/C++语言为例,我们在桌面计算机上编写高级语言程序,并借助于目标嵌入式系统CPU的工具链将其转换为目标系统CPU 能够识别并执行的机器语言程序,这个过程称为交叉编译。

譬如我们的目标嵌入式系统CPU使用ARMv8指令集,我们需要一个IDE在桌面计算机上编写C/C++程序,并有IDE的工具链将C/C++程序转换成 ARMv8机器的二进制格式程序。高级语言程序和机器语言程序的片段如图1.10所示。另一个问题是,我们如何将嵌入式计算机系统的机器语言 程序从桌面计算机磁盘转移到微控制器的ROM内呢?我们把这个文件转移过程称作程序下载,也有人称之为烧录程序。这个过程必须借助于一些 专用的软硬件工具,譬如JTAG。

本书的第三章将会深入探讨嵌入式计算机系统的交叉编译、程序下载。使用桌面计算机的IDE应用程序和嵌入式计算机系统下载工具等实现 交叉编译和程序下载的流程如图1.11所示 [2]_

../_images/ec_dev_software_1.jpg

图1.11 嵌入式计算机系统软件开发(以C/C++编程语言开发ARM Coretx-M系统为例)


当前,Python编程语言是全球最流行的高级编程语言之一,本书的很多示例都将使用Python语言。Python是一种可读性极高的脚本语言, 相较于C/C++、Fortran等高级语言更接近人类的自然语言,Python语言的程序员们考虑问题的方式更接近日常习惯。使用C/C++和Python 编程有什么区别?现今的程序员们很多都同时掌握多种编程语言,同一个问题可以使用不同种语言来解决,而且任何高级语言程序都需要使用 某些工具软件将其转换成机器语言程序。同一种计算机上执行C/C++程序和Python脚本程序有不同的技术路线,如图1.12所示。

../_images/code2bin.jpg

图1.12 编译型语言程序和解释型语言程序如何被机器执行

C/C++是典型的编译型高级语言,程序源码必须经过工具链处理成特定机器可执行的机器语言程序才能让计算机执行,程序的执行效率很高, 但是源码的任何小改动都必须再用工具链处理一次。Python是典型的解释型高级语言,脚本程序被特定机器的Python解释器逐行地转换 成机器语言程序片段并在当前运行环境立即执行立即输出结果,脚本语言无需预先编译,仅在运行时才逐行解释、执行、输出,编写和修改源码 的效率很高(相较于C/C++语言,Python语言的程序员们的工作效率更高),但Python程序的执行效率较低,Python脚本程序依赖特定的 解释器。嵌入式计算机系统运行Python脚本程序前必须已经启动Python解释器,嵌入式系统的Python解释器本身是一个独立程序, Python解释器能够循环地“读取-估算-输出”Python脚本程序文件的每一行。

同样的问题,Python源码如何下载到嵌入式计算机系统呢?我们将在第3章回答这个问题。随着解释型编程语言的发展,现在很多解释型 语言都能够在极小程序空间和有限内存资源的嵌入式计算机系统上执行,如Python、JavaScript等,你可以使用搜索引擎找到相关的解释器 和可运行的硬件平台。很显然,现今的所有嵌入式计算机系统都支持执行效率更高的编译型语言,如C/C++、Go等,取决于他们的工具链。 嵌入式计算机系统的硬件具有体积小、重量轻、低功耗、低时钟频率和执行速度(即弱计算能力)、有限存储空间(程序空间和数据空间)、 丰富的可编程I/O资源等特点,因此执行脚本语言程序的速度将更慢。


桌面计算机是我们开发嵌入式计算机系统软件的主要工具,安装有文本编辑器、编译器、汇编器等工具链(IDE)软件的桌面计算机被称作宿主 计算机,嵌入式计算机则称作目标系统或目标板/开发板。这种开发模式所使用的工具链一定是交叉工具链,将编译型语言的源码程序和库文件 转换成能够在目标板上执行的机器语言程序/二进制文件,下载工具将二进制文件下载到目标板的FlashROM中。不同的目标板,尤其不同的 微控制器架构体系所使用的交叉工具链是完全不同的。

../_images/ec_software_dev_download.jpg

图1.13 用桌面计算机(宿主)和下载工具开发嵌入式计算机系统软件的流程(以ARM Cortex-M系列为例)

除了交叉开发的工具链软件之外,下载工具是很重要的一种嵌入式系统开发工具。下载工具的核心任务是将宿主机器生成的二进制文件下载到 目标板的程序存储器内,这个下载过程与普通的文件复制/拷贝完全不同。下载过程分为两种:生产过程中下载和在系统/在板程序下载,前者 是微控制器芯片未焊接到目标板时使用专用的工装夹具把机器语言程序下载到芯片内的FlashROM,后者是微控制器芯片已经焊在目标板上借助于 专用的在系统/在板程序下载接口把程序下载到FlashROM中。现今的绝大多数微控制器都支持在系统/在板程序下载。某些下载工具还提供在系统/ 在板仿真和调试程序的功能,譬如JTAG下载工具。

微控制器开发者为了简化在系统/在板下载程序的工具,为微控制器增加一个独立的状态:下载程序状态,以区别于执行用户程序的状态,这个状态 执行一种极小的专用程序与宿主计算机通讯,完成用户程序的下载/更新。微控制器进入下载/更新程序状态时所执行的专用程序被称作Bootloader。 微控制器何时进入Bootloader状态呢?通用的做法是,当系统复位时某几个特定GPIO引脚被置为特定状态,系统复位后将立即进入Bootloader状态, 此时可以下载/更新用户程序。支持Bootloader更新用户程序的微控制器可以通过USB、异步串口(UART)、I2C、SPI等通讯接口实现程序下载, 对于带有USB或异步串口的微控制器来说,使用Bootloader更新用户程序所需成本几乎为零,仅需要一根数据线。


通过本节的内容,我们初步了解嵌入式计算机系统的软件开发和程序下载的基本方法。与桌面计算机的应用程序开发相比,嵌入式计算机系统的软件 开发有什么区别?桌面计算机具有Windows、macOS或Linux等桌面操作系统,这些标准的OS环境都会为程序员们提供应用程序接口(API),不管你使用 C/C++或是Java语言都有相应功能的API帮助开发者实现输入/输出和网络功能。然而,嵌入式计算机系统并没有标准的OS,而且使用编译型语言编程即使 有嵌入式OS,其软件开发流程(如图1.11和图1.13)与桌面计算机应用程序开发完全不同。嵌入式计算机系统使用的OS,如FreeTROS、R-Thread等, 本质上是一种程序库或称中间件,跟我们编写的应用程序源码一起编译和汇编才能成为一个完整的嵌入式计算机系统应用程序。

譬如,ARM Cortex-M体系架构的微控制器开发者可以使用ARM官方的开源嵌入式OS——Mbed OS [2]_,基于这个OS开发基于ARM Cortex-M体系架构 的嵌入式计算机系统与桌面计算机应用程序开发风格相似,都是使用OS提供的API,但编译和汇编用户程序时必须将嵌入式系统OS一起处理成完整的机器语言 程序(二进制文件)。ARM Mbed OS的架构如图1.14所示。

../_images/ec_software_mbed.jpg

图1.14 基于ARM Mbed OS的嵌入式计算机系统软件开发架构

使用不同类型的OS开发嵌入式计算机系统软件的风格、思路和方法或许完全不同,但编译和汇编时都会将所用的OS作为特殊的组件一起处理。如果一个嵌入式 计算机系统软件没有使用OS,但程序编码也不一定全部都从零开始,大多数情况都会使用第三方库(包括源码级库、汇编语言库或二进制库),除非应用程序非常 简单。github等开源社区已有数千万个开源项目,其中大多数属于开源库,我们正准备开发的嵌入式计算机软件项目中或有现成可用的开源代码库,用第三方 代码库能够缩短开发周期。如果使用编译型语言编写嵌入式计算机系统软件,第三方代码库作为用户程序组件的一部分与用户程序源码一起被编译、汇编产生完整 的机器语言程序(二进制文件)。

计算机软件系统的抽象和分层封装是软件架构师和程序员们最常用的利器,抽象能够将较大的工程分割为不同级别和层次的小而简单问题,分层封装的软件设计 方法不仅便于团队的分工协作,也便于软件的调试和维护。如图1.14所示的ARM Mbed OS架构是一种典型的嵌入式计算机系统软件开发架构,嵌入式计算机的硬件 层仅与微控制器的体系架构和外设等相关,硬件层的程序库一般都是由半导体设计公司(如ARM公司)封装;硬件抽象层(HAL)的软件由微控制器的设计和制造商 (如ST、TI、Nordic等)封装,HAL软件也称作微控制器的驱动库,依赖特定的微控制器;嵌入式OS在HAL之上,不依赖特定的硬件;使用嵌入式OS的API编写的 用户应用程序、物联网(IoT)程序与具体电路无关。如果不使用嵌入式OS,基于HAL软件直接编写的用户应用程序也不依赖于特定硬件。我们将在第3~9章中 使用这种抽象和分层封装的软件设计方法编写本书的全部案例。


最后我们需要了解一些编程语言,这将会更好地帮助我们了解本节前面的内容。计算机编程语言与人类其他语言一样,编程语言只用于人-机交互,人类创造了各种各样的语言, 计算机编程语言只是其中的一类。截止目前,人类已经发明了数十种计算机编程语言!2019年编程语言的全球排行榜如图1.15所示。

../_images/codeing_languages.png

图1.15 2019年编程语言的全球排行榜(仅前20种)

计算机科学家为什么发明这么多种编程语言呢?很多语言的诞生是面向特定应用,也有些编程语言是面向特定人群。过去20年里C/C++和Java语言始终处于榜首, 这是因为Java语言支持跨平台主要面向桌面计算机、移动计算机和服务器的应用程序开发,执行效率较高的C/C++语言主要面向工业领域的应用程序开发, 最近两年内Python语言在排行榜上快速上升,归功于Python能极大地提高编程效率(虽然执行效率比较低)和面向Web编程和科学计算的应用。图1.15中的 编程语言都属于代码编程语言,适合专业人员开发应用程序,最近几年发展起来的图形化编程语言,如MIT媒体实验室的Scratch和Google的Blockly, 采用拼接“积木块”(程序块)的形式编写应用程序,主要面向基础教育和非专业人士使用。代码编程的初学者遇到的最大挑战是代码拼写错误, 使用Scratch或Blockly编写的程序不仅没有拼写错误的可能也没有语法结构的困扰。

计算机是如何执行图形化语言程序呢?以Scratch程序为例,我们用浏览器(推荐使用Chrome)打开网页(https://www.ezaoyun.com:6363/) [3]_, 并使用拖放“积木块”的形式编辑图1.16所示的示例程序,点击“小绿旗”即可看到该程序的执行结果:海豹在屏幕左上角的区域(舞台区)绘制一个红色的 正方形。这个Scratch图形化语言程序的执行过程是,首先浏览器(如Chrome)将Scratch程序转换为浏览器内可执行的JavaScript脚本(代码)程序, 然后浏览器就能像执行其他Web程序一样地输出该脚本程序的结果(我们看到海豹绘制的正方形)。

../_images/codeing_language_scratch.jpg

图1.16 Scratch图形化程序示例

JavaScript是一种解释型编程语言,浏览器执行JavaScript脚本程序的过程与Python解释器执行Python脚本程序相似,都是逐行执行并立即输出结果。 现在我们可以想象,Scratch和Blockly图形化语言程序可以转换为其他代码语言,包括解释型语言和编译型语言,然后再由脚本解释器逐行地转换为机器 语言程序片段并执行-输出结果,或使用工具链将其转换为完整的机器语言程序再执行-输出结果。

可编程逻辑控制器(PLC)是广泛使用的一种工业控制器,PLC支持梯形图编程语言,即使没有任何编程知识的电气工程师也能对PLC编程实现特定的工业自动化 控制或过程控制任务。当然,梯形图程序也必须经过特定的PLC编程软件转换为机器语言程序才能被PLC的微控制器执行。

计算机编程语言之间的转换工具链允许我们选择适合自己的某种编程语言解决问题,每一种特定的实际问题都可以采用多种编程语言来解决,他们的区别仅仅 是解决问题的效率。


参考文献:

.. [1] 计算机组成与设计:硬件/软件接口(第5版), David A. Petterson, John L. Hennessy著, 陈微 译, 机械工业出版社, 2018
.. [2] https://os.mbed.com/
.. [3] https://www.ezaoyun.com:6363/