C / 嵌入式软件开发 / 哦!这就是嵌入式软件开发
写在前面的话
2013年6月份,两年,所谓的嵌入式软件工程师,其实是9527型程序员。老程序员可能会告诉我们,“你可以在这个行业有梦想,但它不应该是生活的全部”。我同意,不过那是30岁后的事了,如果20多岁的年轻人梦想不是你的全部,请问你拿什么跟别人拼,干爹?这两年,像很多diors一样,工作上显得碌碌无为,执行一些繁杂而又缺乏技术含量的任务。OK,打住,再多抱怨无用,多编程多总结才是可行的,这两天一直在想一个问题:嵌入式软件开发和其它像服务器编程、移动开发、Web编程相比,包括开发环境在内都有哪些不同呢?话说这个问题本该是初学时就要先了解的,随便了,总结而已。
在网上看到一份pdf档《C语言嵌入式系统编程修炼》,很好的总结了在嵌入式系统开发的知识,有兴趣的可在网上搜一下。
以下是我写的一点读书笔记,与大家分享,里面的东西不知道大家是不是真的掌握了,反正我好多都没掌握。
硬件架构
软件架构
内存操作
屏幕、键盘操作
性能优化
软件架构:
#开发环境
就像没有人在手机或者平板上写代码一样,嵌入式软件开发大都也是在PC在编程,编程环境的话,各不相同,基本上都是在PC上用各类编辑软件写好代码后,用本地交叉编译环境Cygwin或者服务器上的编译环境去自动编译整包代码,完后把可执行档案想办法读到平台RAM里面去,最后用PC串口与平台通信,用打印信息或其它上位机软件来调试。整个开发或者Debug过程大多都是这样,当然其它还有什么的话,我就不知道了... ...和服务器编程、移动开发、Web编程不同的是,这些都有强大的技术支持,而嵌入式软件开发一般不会有模拟器让普通开发者使用,不会有Firebug或开发者工具之类的东西使用,因为大多是定制的原因,走的路线很多时候往往和标准不太一样,这一切都让嵌入式软件开发比其它开发环境艰难。
#按照功能进行模块划分(p.s.:在OOP中这种设计是错误的)
1. 模块即是一个(堆).c档和一个.h档的结合,.h档是开给别人用的编程接口;
2. .c档里不想被别人使用的东西定义为static保护起来;
3. 如果模块的.c档里既没有用static来保护函数或数据,又没有在.h档里面开出相应的接口,别人就可以用extern来使用你的函数或数据;
4. 不要在.h档中出现这样的东西:
int a = 10;
因为这个.h档如果被很多人include 的话,那内存中就会有很多份的a变量;如果你想把你.c档里的变量作为全局变量开给外面的人来用,你可以在.h档中用
extern int a;
的方式,这样,你完全可以把.c编译成一个.dll档或.a或.so等等提供出去。
#单任务,多任务
单任务,典型的是以前没有操作系统的功能手机,一直while(1)或for(;;)在那里,然后不断的polling各个模块。主流程像这样:
0. CPU到指定地址执行去跑汇编
1. 跳到main函数所在的地址,开始跑C代码,做init hw, init sw
2.
for(;;) { A(); B(); C(); }
多任务,现在的嵌入式系统几乎都带操作系统,按不同的方案需求选用不同的系统,如果没有非常实时的要求,嵌入式Linux是个不错的选择,现在很多的嵌入式设备都是基于嵌入式Linux系统来开发。既然有支持多任务的操作系统存在,那上面的死循环就不是简单的轮询了,任务调度,消息处理也让嵌入式系统的可开发的功能变得更多,更强大,现在,和开发PC上的程序就几乎无异了。
#ISR
硬件中断
软件中断,ANSI C不支持中断,需要编译器添加对C的中断的支持。中断的编写要求:
1. 不能传参,不能有返回值
2. 功能mini一点
3. 不能有printf
#让C也面向对象
OOP的三大特征:封装、继承、多态;
1. 封装:
C没有C++中的类,但可以在structure里面数据集合的基础上写一些函数指针来模拟;
2. 继承:
从GObject的思想我们可以知道,在structure定义的开始位置如果是另一个structure的变量,那么我们就模拟继承了一个“类”。
3. 多态:
在成功继承之后,我们便可以通过强制类型转换的手法(GObject中把它封装成了宏)来模拟多态的特性。
可参考GObject对象系统:http://www.ibm.com/developerworks/cn/linux/l-gobject/ 来实现C的面向对象。
内存操作:
#之所以在嵌入式系统中使用C语言进行程序开发,主要因为它具备强大的内存操作能力。
#数据指针
CPU以Byte为单位来编址,而C语言指针以指向的数据类型长度作++/--
#函数指针
1. C语言的函数名直接对应于函数生成的指令代码在内存中的地址;
2. 调用函数实际上等同于“跳转指令 + 参数传递处理 + 回归位置入栈”,本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器;
#数组vs动态申请(谁申请谁释放)
#const
const int a;
int const a;
const int* a;
int* const a;
int const* a const;
const int SIZE = 10; /* 不能改变的"变量" */
char a[SIZE]; /* 非法;因为编译阶段不能用到变量 */
#volatile
1. 防止编译器的误优化;
2. 主要用在处在共享状态的变量身上。
性能优化:
#使用宏
减少函数调用和函数返回的开销
#define MIN(A,B) (A<=B?A:B) /* Wrong */
#define MIN(A,B) ((A)<=(B)?(A):(B)) /* OK,宏定义需要括上所有参数*/
#使用寄存器变量
register i=0; /* 只有局部自动变量和形参才可以定义为寄存器变量,因为寄存器变量属于动态存储方式,且register只是一个“建议型”关键字 */
# 使用内嵌汇编
int result; void add(long a, long b) { _asm { MOV AX, a MOV BX, b ADD AX, [BX] MOV result, AX } }
#利用硬件特性:CPU对各种存储器的访问速度
1. CPU内部RAM > 外部同步RAM > 外部异步RAM > FLASH/ROM, 对于程序代码,最好在系统启动后将FLASH/ROM中的目标代码拷入RAM中后再执行以提高取指令速度
2. 使用DMA读取方式在读取存储信息较大时效率较高,以块来传输
#使用位操作
<<
>>
&
|
~
以上,只是C语言在嵌入式软件编程当中的一些常识性的知识,而在自己实际的工作当中却很少注意到这些知识点,这些最基本的东西让你知道,嵌入式软件编程是这样。