嵌入式系统C语言经典指南.docx
- 文档编号:2913298
- 上传时间:2022-11-16
- 格式:DOCX
- 页数:10
- 大小:48.34KB
嵌入式系统C语言经典指南.docx
《嵌入式系统C语言经典指南.docx》由会员分享,可在线阅读,更多相关《嵌入式系统C语言经典指南.docx(10页珍藏版)》请在冰豆网上搜索。
嵌入式系统C语言经典指南
C语言嵌入式系统编程修炼之内存操作篇
数据指针
在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它编程语言基本没有直接访问绝对地址的能力。
在嵌入式系统的实际调试中,多借助C语言指针所具有的对绝对地址单元内容的读写能力。
以指针直接操作内存多发生在如下几种情况:
(1)某I/O芯片被定位在CPU的存储空间而非I/O空间,而且寄存器对应于某特定地址;
(2)两个CPU之间以双端口RAM通信,CPU需要在双端口RAM的特定单元(称为mailbox)书写内容以在对方CPU产生中断;
(3)读取在ROM或FLASH的特定单元所烧录的汉字和英文字模。
譬如:
unsignedchar*p=(unsignedchar*)0xF000FF00;
*p="11";
以上程序的意义为在绝对地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)写入11。
在使用绝对地址指针时,要注意指针自增自减操作的结果取决于指针指向的数据类别。
上例中p++后的结果是p=0xF000FF01,若p指向int,即:
int*p=(int*)0xF000FF00;
p++(或++p)的结果等同于:
p=p+sizeof(int),而p-(或-p)的结果是p=p-sizeof(int)。
记住:
CPU以字节为单位编址,而C语言指针以指向的数据类型长度作自增和自减。
理解这一点对于以指针直接操作内存是相当重要的。
函数指针
首先要理解以下三个问题:
(1)C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;
(2)调用函数实际上等同于"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器;
(3)因为函数调用的本质是跳转到某一个地址单元的code去执行,所以可以"调用"一个根本就不存在的函数实体,晕?
请往下看:
请拿出你可以获得的任何一本大学《微型计算机原理》教材,书中讲到,186CPU启动后跳转至绝对地址0xFFFF0(对应C语言指针是0xF000FFF0,0xF000为段地址,0xFFF0为段内偏移)执行,请看下面的代码:
typedefvoid(*lpFunction)();/*定义一个无参数、无返回类型的*/
/*函数指针类型*/
lpFunctionlpReset=(lpFunction)0xF000FFF0;/*定义一个函数指针,指向*/
/*CPU启动后所执行第一条指令的位置*/
lpReset();/*调用函数*/
在以上的程序中,我们根本没有看到任何一个函数实体,但是我们却执行了这样的函数调用:
lpReset(),它实际上起到了"软重启"的作用,跳转到CPU启动后第一条要执行的指令的位置。
记住:
函数无它,唯指令集合耳;你可以调用一个没有函数体的函数,本质上只是换一个地址开始执行指令!
数组vs.动态申请
在嵌入式系统中动态内存申请存在比一般系统编程时更严格的要求,这是因为嵌入式系统的内存空间往往是十分有限的,不经意的内存泄露会很快导致系统的崩溃。
所以一定要保证你的malloc和free成对出现,如果你写出这样的一段程序:
char*function(void)
{
char*p;
p=(char*)malloc(…);
if(p==NULL)
…;
…/*一系列针对p的操作*/
returnp;
}
在某处调用function(),用完function中动态申请的内存后将其free,如下:
char*q=function();
…
free(q);
上述代码明显是不合理的,因为违反了malloc和free成对出现的原则,即"谁申请,就由谁释放"原则。
不满足这个原则,会导致代码的耦合度增大,因为用户在调用function函数时需要知道其内部细节!
正确的做法是在调用处申请内存,并传入function函数,如下:
char*p="malloc"(…);
if(p==NULL)
…;
function(p);
…
free(p);
p="NULL";
而函数function则接收参数p,如下:
voidfunction(char*p)
{
…/*一系列针对p的操作*/
}
基本上,动态申请内存方式可以用较大的数组替换。
对于编程新手,笔者推荐你尽量采用数组!
嵌入式系统可以以博大的胸襟接收瑕疵,而无法"海纳"错误。
毕竟,以最笨的方式苦练神功的郭靖胜过机智聪明却范政治错误走反革命道路的杨康。
给出原则:
(1)尽可能的选用数组,数组不能越界访问(真理越过一步就是谬误,数组越过界限就光荣地成全了一个混乱的嵌入式系统);
(2)如果使用动态申请,则申请后一定要判断是否申请成功了,并且malloc和free应成对出现!
const在C++语言中则包含了更丰富的含义,而在C语言中仅意味着:
"只能读的普通变量",可以称其为"不能改变的变量"(这个说法似乎很拗口,但却最准确的表达了C语言中const的本质),在编译阶段需要的常数仍然只能以#define宏定义!
故在C语言中如下程序是非法的:
关键字const
const意味着"只读"。
区别如下代码的功能非常重要,也是老生长叹,如果你还不知道它们的区别,而且已经在程序界摸爬滚打多年,那只能说这是一个悲哀:
constinta;
intconsta;
constint*a;
int*consta;
intconst*aconst;
constintSIZE=10;
chara[SIZE];/*非法:
编译阶段不能用到变量*/
关键字volatile
volatile变量可能用于如下几种情况:
(1)并行设备的硬件寄存器(如:
状态寄存器,例中的代码属于此类);
(2)一个中断服务子程序中会访问到的非自动变量(也就是全局变量);
(3)多线程应用中被几个任务共享的变量。
C语言嵌入式系统编程修炼之屏幕操作篇
汉字处理
现在要解决的问题是,嵌入式系统中经常要使用的并非是完整的汉字库,往往只是需要提供数量有限的汉字供必要的显示功能。
例如,一个微波炉的LCD上没有必要提供显示"电子邮件"的功能;一个提供汉字显示功能的空调的LCD上不需要显示一条"短消息",诸如此类。
但是一部手机、小灵通则通常需要包括较完整的汉字库。
如果包括的汉字库较完整,那么,由内码计算出汉字字模在库中的偏移是十分简单的:
汉字库是按照区位的顺序排列的,前一个字节为该汉字的区号,后一个字节为该字的位号。
每一个区记录94个汉字,位号则为该字在该区中的位置。
因此,汉字在汉字库中的具体位置计算公式为:
94*(区号-1)+位号-1。
减1是因为数组是以0为开始而区号位号是以1为开始的。
只需乘上一个汉字字模占用的字节数即可,即:
(94*(区号-1)+位号-1)*一个汉字字模占用字节数,以16*16点阵字库为例,计算公式则为:
(94*(区号-1)+(位号-1))*32。
汉字库中从该位置起的32字节信息记录了该字的字模信息。
对于包含较完整汉字库的系统而言,我们可以以上述规则计算字模的位置。
但是如果仅仅是提供少量汉字呢?
譬如几十至几百个?
最好的做法是:
定义宏:
#defineEX_FONT_CHAR(value)
#defineEX_FONT_UNICODE_VAL(value)(value),
#defineEX_FONT_ANSI_VAL(value)(value),
定义结构体:
typedefstruct_wide_unicode_font16x16
{
WORDvalue;/*内码*/
BYTEdata[32];/*字模点阵*/
}Unicode;
#defineCHINESE_CHAR_NUM…/*汉字数量*/
字模的存储用数组:
Unicodechinese[CHINESE_CHAR_NUM]=
{
{
EX_FONT_CHAR("业")
EX_FONT_UNICODE_VAL(0x4e1a)
{0x04,0x40,0x04,0x40,0x04,0x40,0x04,0x44,0x44,0x46,0x24,0x4c,0x24,0x48,0x14,0x50,0x1c,0x50,0x14,0x60,0x04,0x40,0x04,0x40,0x04,0x44,0xff,0xfe,0x00,0x00,0x00,0x00}
},
{
EX_FONT_CHAR("中")
EX_FONT_UNICODE_VAL(0x4e2d)
{0x01,0x00,0x01,0x00,0x21,0x08,0x3f,0xfc,0x21,0x08,0x21,0x08,0x21,0x08,0x21,0x08,0x21,0x08,
0x3f,0xf8,0x21,0x08,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00}
},
{
EX_FONT_CHAR("云")
EX_FONT_UNICODE_VAL(0x4e91)
{0x00,0x00,0x00,0x30,0x3f,0xf8,0x00,0x00,0x00,0x00,0x00,0x0c,0xff,0xfe,0x03,0x00,0x07,0x00,
0x06,0x40,0x0c,0x20,0x18,0x10,0x31,0xf8,0x7f,0x0c,0x20,0x08,0x00,0x00}
},
{
EX_FONT_CHAR("件")
EX_FONT_UNICODE_VAL(0x4ef6)
{0x10,0x40,0x1a,0x40,0x13,0x40,0x32,0x40,0x23,0xfc,0x64,0x40,0xa4,0x40,0x28,0x40,0x2f,0xfe,
0x20,0x40,0x20,0x40,0x20,0x40,0x20,0x40,0x20,0x40,0x20,0x40,0x20,0x40}
}
}
要显示特定汉字的时候,只需要从数组中查找内码与要求汉字内码相同的即可获得字模。
如果前面的汉字在数组中以内码大小顺序排列,那么可以以二分查找法更高效的查找到汉字的字模。
这是一种很有效的组织小汉字库的方法,它可以保证程序有很好的结构。
系统时间显示
从NVRAM中可以读取系统的时间,系统一般借助NVRAM产生的秒中断每秒读取一次当前时间并在LCD上显示。
关于时间的显示,有一个效率问题。
因为时间有其特殊性,那就是60秒才有一次分钟的变化,60分钟才有一次小时变化,如果我们每次都将读取的时间在屏幕上完全重新刷新一次,则浪费了大量的系统时间。
一个较好的办法是我们在时间显示函数中以静态变量分别存储小时、分钟、秒,只有在其内容发生变化的时候才更新其显示。
externvoid
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 嵌入式 系统 语言 经典 指南