C
语言嵌入式系统编程修炼之一:背景篇
不同于一般½式的½件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求
其编程语言具备较强的硬件直接操½½力。无疑,汇编语言具备这样的特质。½是,½
因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发的一般选择。而与之相比,
C
语言--一种"高级的½级"语言,则成为嵌入式系统开发的最½选择。笔者在嵌入式系
统项目的开发过程中,
一次又一次感受到
C
语言的精妙,
沉醉于
C
语言给嵌入式开发带
来的便利。
图
1
给出了本文的讨论所基于的硬件平台,实际上,这也是大多数嵌入式系统的硬
件平台。它包括两部分:
(1) 以通用处理器为中心的协议处理模块,用于½络控制协议的处理;
(2) 以数字信号处理器(DSP)为中心的信号处理模块,用于调制、解调和数/
模信号½换。
本文的讨论主要围绕以通用处理器为中心的协议处理模块进行,
因为它更多地牵涉
到具½的
C
语言编程技巧。而
DSP
编程则重点关注具½的数字信号处理算法,主要涉
及通信领域的知识,不是本文的讨论重点。
着眼于讨论普遍的嵌入式系统
C
编程技巧,系统的协议处理模块没有选择特别的
CPU,而是选择了众所周知的 CPU
芯片--80186,每一½学习过《微机原理》的读者½
应该对此芯片有一个基本的认识,且对其指令集比较熟悉。80186 的字长是
16
½,可
以寻址到的内存空间为
1MB,
只有实地址模式。 语言编译生成的指针为
32
½
C
(双字)
,
高
16
½为段地址,½
16
½为段内编译,一段最多
64KB。
图
1
系统硬件架构
协议处理模块中的
FLASH
和
RAM
几乎是每个嵌入式系统的必备设备,前者用于存
储程序,后者则是程序运行时指令及数据的存放½½。系统所选择的
FLASH
和
RAM
的
½½½为
16
½,与
CPU
一致。
实时钟芯片可以为系统定时,给出½前的年、月、日及具½时间(小时、分、秒及
毫秒) 可以设定其经过一段时间即向
CPU
提出中断或设定报警时间到来时向
CPU
提出
,
中断(类似闹钟功½)
。
NVRAM(非易失去性 RAM)具有掉电不丢失数据的特性,可以用于保存系统的设
½信息,譬如½络协议参数等。在系统掉电或重新启动后,仍然可以读取先前的设½信
息。其½½为
8
½,比
CPU
字长小。文章特意选择一个与
CPU
字长不一致的存储芯片,
为后文中一节的讨论创造条件。
UART
则完成
CPU
并行数据传输与
RS-232
串行数据传输的½换,它可以在接收到
[1~MAX_BUFFER]字节后向 CPU
提出中断,MAX_BUFFER 为
UART
芯片存储接收到字
节的最大缓冲区。
键盘控制器和显示控制器则完成系统人机界面的控制。
以上提供的是一个较完备的嵌入式系统硬件架构,实际的系统可½包含更少的外
设。之所以选择一个完备的系统,是为了后文更全面的讨论嵌入式系统
C
语言编程技巧
的方方面面,所有设备½会成为后文的分析目标。
嵌入式系统需要良½的½件开发环境的支持,由于嵌入式系统的目标机资源受限,
不可½在其上建立庞大、复杂的开发环境,因而其开发环境和目标运行环境相互分离。
因此,嵌入式应用½件的开发方式一般是,在宿主机(Host)上建立开发环境,进行应用
程序编码和交叉编译,然后宿主机同目标机(Target)建立连接,将应用程序下½½到目标
机上进行交叉调试,经过调试和优化,最后将应用程序固化到目标机中实际运行。
CAD-UL
是适用于
x86
处理器的嵌入式应用½件开发环境,
它运行在
Windows
操½
系统之上,可生成
x86
处理器的目标代码并通过
PC
机的
COM
口(RS-232 串口)或以
太½口下½½到目标机上运行,如图
2。其驻留于目标机 FLASH
存储器中的
monitor
程序
可以监控宿主机
Windows
调试平台上的用户调试指令,获取
CPU
寄存器的值及目标机
存储空间、I/O 空间的内容。
图
2
交叉开发环境
后续章节将从½件架构、内存操½、屏幕操½、键盘操½、性½优化等多方面阐述
C
语言嵌入式系统的编程技巧。½件架构是一个宏观概念,与具½硬件的联系不大;内
存操½主要涉及系统中的
FLASH、RAM
和
NVRAM
芯片;屏幕操½则涉及显示控制器和
实时钟;键盘操½主要涉及键盘控制器;性½优化则给出一些具½的减小程序时间、空
间消耗的技巧。
在我们的修炼旅途中将经过
25
个关口,这些关口主分为两类,一类是技巧型,有
很强的适用性;一类则是常识型,在理论上有些意义。
C
语言嵌入式系统编程修炼之二:½件架构篇
模块划分
模块划分的"划"是规划的意思,意指怎样合理的将一个很大的½件划分为一系列功
½独立的部分合½完成系统的需求。C 语言½为一种结构化的程序设计语言,在模块的
划分上主要依据功½(依功½进行划分在面向对象设计中成为一个错误,牛顿定律遇到
了>相对论) 语言模块化程序设计需理解如下概念:
,C
(1) 模块即是一个.c 文件和一个.h 文件的结合,头文件(.h)中是对于该模块接口
的声明;
(2) 某模块提供给其它模块调用的外部½数及数据需在.h 中文件中冠以
extern
关键字声明;
(3) 模块内的½数和全局变量需在.c 文件开头冠以
static
关键字声明;
(4) 永远不要在.h 文件中定义变量!定义变量和声明变量的区别在于定义会产生
内存分配的操½,是汇编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶段
从其它模块寻找外部½数和变量。如:
/*module1.h*/
int a = 5; /*
在模块
1
的.h 文件中定义
int a */
/*module1 .c*/
#include "module1.h" /*
在模块
1
中包含模块
1
的.h 文件
*/
/*module2 .c*/
#include "module1.h" /*
在模块
2
中包含模块
1
的.h 文件
*/
/*module3 .c*/
#include "module1.h" /*
在模块
3
中包含模块
1
的.h 文件
*/
以上程序的结果是在模块
1、2、3
中½定义了整型变量
a,a
在不同的模块中对应
不同的地址单元,这个世界上从来不需要这样的程序。正确的做法是:
/*module1.h*/
extern int a; /*
在模块
1
的.h 文件中声明
int a */
/*module1 .c*/
#include "module1.h" /*
在模块
1
中包含模块
1
的.h 文件
*/
int a = 5; /*
在模块
1
的.c 文件中定义
int a */
/*module2 .c*/
#include "module1.h" /*
在模块
2
中包含模块
1
的.h 文件
*/
/*module3 .c*/
#include "module1.h" /*
在模块
3
中包含模块
1
的.h 文件
*/
这样如果模块
1、2、3
操½
a
的话,对应的是同一片内存单元。
一个嵌入式系统通常包括两类模块:
(1)硬件驱动模块,一种特定硬件对应一个模块;
(2)½件功½模块,其模块的划分应满足½偶合、高内聚的要求。
多任务还是单任务
所谓"单任务系统"是指该系统不½支持多任务并发操½,宏观串行地执行一个任
务。而多任务系统则可以宏观并行(微观上可½串行)地"同时"执行多个任务。
多任务的并发执行通常依赖于一个多任务操½系统(OS)
,多任务
OS
的核心是系
统调度器,它½用任务控制块(TCB)来管理任务调度功½。TCB 包括任务的½前状态、
优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针等信息。调度器
在任务被激活时,
要用到这些信息。
此外,
TCB
还被用来存放任务的"上下文"
(context)。
任务的上下文就是½一个执行中的任务被停止时,所要保存的所有信息。通常,上下文
就是计算机½前的状态,也即各个寄存器的内容。½发生任务切换时,½前运行的任务
的上下文被存入
TCB,并将要被执行的任务的上下文从它的 TCB
中取出,放入各个寄
存器中。
嵌入式多任务
OS
的典型例子有
Vxworks、ucLinux
等。嵌入式
OS
并非遥不可及的
神坛之物,我们可以用不到
1000
行代码实现一个针对
80186
处理器的功½最简单的
OS
内核,½者正准备进行此项工½,希望½将心得贡献给大家。
究竟选择多任务还是单任务方式,依赖于½件的½系是否庞大。例如,绝大多数手
机程序½是多任务的,½也有一些小灵通的协议栈是单任务的,没有操½系统,它们的
主程序½流调用各个½件模块的处理程序,模拟多任务环境。
单任务程序典型架构
(1)从
CPU
复½时的指定地址开始执行;
(2)跳½至汇编代码
startup
处执行;
(3)跳½至用户主程序
main
执行,在
main
中完成:
a.初试化各硬件设备;
b.初始化各½件模块;
c.进入死循环(无限循环)
,调用各模块的处理½数
用户主程序和各模块的处理½数½以
C
语言完成。
用户主程序最后½进入了一个死
循环,其首选方案是:
while(1)
{
}
有的程序员这样写:
for(;;)
{
}
这个语法没有确切表达代码的含义,我们从
for(;;)看不出什么,只有弄明½ for(;;)
在
C
语言中意味着无条件循环才明½其意。
下面是几个"著名"的死循环:
(1)操½系统是死循环;
(2)WIN32 程序是死循环;
(3)嵌入式系统½件是死循环;
(4)多线程程序的线程处理½数是死循环。
½可½会辩驳,大声说:"凡事½不是绝对的,2、3、4 ½可以不是死循环"。Yes,
you are right,½是½得不到鲜花和掌声。实际上,这是一个没有太大意义的牛角尖,
因为这个世界从来不需要一个处理完几个消息就喊着要
OS
杀死它的
WIN32
程序,不
需要一个刚开始
RUN
就自行了断的嵌入式系统,不需要莫名其妙启动一个做一点事就
干掉自己的线程。有时候,过于严谨制造的不是便利而是麻烦。君不见,五层的
TCP/IP
协议栈超越严谨的
ISO/OSI
七层协议栈大行其道成为事实上的标准?
评论