5695|9

6366

帖子

4929

TA的资源

版主

楼主
 

C语言嵌入式系统编程修炼之道 [复制链接]

C语言嵌入式系统编程修炼之道

  不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力。无疑,汇编语言具备这样的特质。但是,由于汇编语言开发的复杂性,它并不是嵌入式系统开发的一般选择。而与之相比,C语言--一种"高级的低级"语言,则成为嵌入式系统开发的最佳选择。
此帖出自单片机论坛

最新回复

好东西,  详情 回复 发表于 2010-8-19 01:51
点赞 关注
 

回复
举报

6366

帖子

4929

TA的资源

版主

沙发
 

C语言嵌入式系统编程修炼之背景篇

不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力。无疑,汇编语言具备这样的特质。但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发的一般选择。而与之相比,C语言--一种"高级的低级"语言,则成为嵌入式系统开发的最佳选择。笔者在嵌入式系统项目的开发过程中,一次又一次感受到C语言的精妙,沉醉于C语言给嵌入式开发带来的便利。

  图1给出了本文的讨论所基于的硬件平台,实际上,这也是大多数嵌入式系统的硬件平台。它包括两部分:

  (1) 以通用处理器为中心的协议处理模块,用于网络控制协议的处理;

  (2) 以数字信号处理器(DSP)为中心的信号处理模块,用于调制、解调和数/模信号转换。

  本文的讨论主要围绕以通用处理器为中心的协议处理模块进行,因为它更多地牵涉到具体的C语言编程技巧。而DSP编程则重点关注具体的数字信号处理算法,主要涉及通信领域的知识,不是本文的讨论重点。

  着眼于讨论普遍的嵌入式系统C编程技巧,系统的协议处理模块没有选择特别的CPU,而是选择了众所周知的CPU芯片--80186,每一位学习过《微机原理》的读者都应该对此芯片有一个基本的认识,且对其指令集比较熟悉。80186的字长是16位,可以寻址到的内存空间为1MB,只有实地址模式。C语言编译生成的指针为32位(双字),高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个关口,这些关口主分为两类,一类是技巧型,有很强的适用性;一类则是常识型,在理论上有些意义。

  So, let’s go.
此帖出自单片机论坛
 
 

回复

6366

帖子

4929

TA的资源

版主

板凳
 

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内核,作者正准备进行此项工作,希望能将心得贡献给大家。

  究竟选择多任务还是单任务方式,依赖于软件的体系是否庞大。例如,绝大多数手机程序都是多任务的,但也有一些小灵通的协议栈是单任务的,没有操作系统,它们的主程序轮流调用各个软件模块的处理程序,模拟多任务环境。
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

4
 

C语言嵌入式系统编程修炼之内存操作

数据指针

  在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它编程语言基本没有直接访问绝对地址的能力。在嵌入式系统的实际调试中,多借助C语言指针所具有的对绝对地址单元内容的读写能力。以指针直接操作内存多发生在如下几种情况:

  (1) 某I/O芯片被定位在CPU的存储空间而非I/O空间,而且寄存器对应于某特定地址;

  (2) 两个CPU之间以双端口RAM通信,CPU需要在双端口RAM的特定单元(称为mail box)书写内容以在对方CPU产生中断;

  (3) 读取在ROM或FLASH的特定单元所烧录的汉字和英文字模。

  譬如:

unsigned char *p = (unsigned char *)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)。

  同理,若执行:

long int *p = (long int *)0xF000FF00;

  则p++(或++p)的结果等同于:p = p+sizeof(long int) ,而p-(或-p)的结果是p = p-sizeof(long int)。

  记住:CPU以字节为单位编址,而C语言指针以指向的数据类型长度作自增和自减。理解这一点对于以指针直接操作内存是相当重要的。

  函数指针

  首先要理解以下三个问题:

  (1)C语言中函数名直接对应于函数生成的指令代码在内存中的地址,因此函数名可以直接赋给指向函数的指针;

  (2)调用函数实际上等同于"调转指令+参数传递处理+回归位置入栈",本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的PC寄存器;

  (3)因为函数调用的本质是跳转到某一个地址单元的code去执行,所以可以"调用"一个根本就不存在的函数实体,晕?请往下看:

  请拿出你可以获得的任何一本大学《微型计算机原理》教材,书中讲到,186 CPU启动后跳转至绝对地址0xFFFF0(对应C语言指针是0xF000FFF0,0xF000为段地址,0xFFF0为段内偏移)执行,请看下面的代码:

typedef void (*lpFunction) ( ); /* 定义一个无参数、无返回类型的 */
/* 函数指针类型 */
lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定义一个函数指针,指向*/
/* CPU启动后所执行第一条指令的位置 */
lpReset(); /* 调用函数 */

  在以上的程序中,我们根本没有看到任何一个函数实体,但是我们却执行了这样的函数调用:lpReset(),它实际上起到了"软重启"的作用,跳转到CPU启动后第一条要执行的指令的位置。

  记住:函数无它,唯指令集合耳;你可以调用一个没有函数体的函数,本质上只是换一个地址开始执行指令!

  数组vs.动态申请

  在嵌入式系统中动态内存申请存在比一般系统编程时更严格的要求,这是因为嵌入式系统的内存空间往往是十分有限的,不经意的内存泄露会很快导致系统的崩溃。

  所以一定要保证你的malloc和free成对出现,如果你写出这样的一段程序:

char * function(void)
{
 char *p;
 p = (char *)malloc(…);
 if(p==NULL)
  …;
  … /* 一系列针对p的操作 */
 return p;
}

  在某处调用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,如下:

void function(char *p)
{
 … /* 一系列针对p的操作 */
}

  基本上,动态申请内存方式可以用较大的数组替换。对于编程新手,笔者推荐你尽量采用数组!嵌入式系统可以以博大的胸襟接收瑕疵,而无法"海纳"错误。毕竟,以最笨的方式苦练神功的郭靖胜过机智聪明却范政治错误走反革命道路的杨康。

  给出原则:

  (1)尽可能的选用数组,数组不能越界访问(真理越过一步就是谬误,数组越过界限就光荣地成全了一个混乱的嵌入式系统);

  (2)如果使用动态申请,则申请后一定要判断是否申请成功了,并且malloc和free应成对出现!
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

5
 

C语言嵌入式系统编程修炼之屏幕操作

汉字处理

  现在要解决的问题是,嵌入式系统中经常要使用的并非是完整的汉字库,往往只是需要提供数量有限的汉字供必要的显示功能。例如,一个微波炉的LCD上没有必要提供显示"电子邮件"的功能;一个提供汉字显示功能的空调的LCD上不需要显示一条"短消息",诸如此类。但是一部手机、小灵通则通常需要包括较完整的汉字库。

  如果包括的汉字库较完整,那么,由内码计算出汉字字模在库中的偏移是十分简单的:汉字库是按照区位的顺序排列的,前一个字节为该汉字的区号,后一个字节为该字的位号。每一个区记录94个汉字,位号则为该字在该区中的位置。因此,汉字在汉字库中的具体位置计算公式为:94*(区号-1)+位号-1。减1是因为数组是以0为开始而区号位号是以1为开始的。只需乘上一个汉字字模占用的字节数即可,即:(94*(区号-1)+位号-1)*一个汉字字模占用字节数,以16*16点阵字库为例,计算公式则为:(94*(区号-1)+(位号-1))*32。汉字库中从该位置起的32字节信息记录了该字的字模信息。

  对于包含较完整汉字库的系统而言,我们可以以上述规则计算字模的位置。但是如果仅仅是提供少量汉字呢?譬如几十至几百个?最好的做法是:

  定义宏:

# define EX_FONT_CHAR(value)
# define EX_FONT_UNICODE_VAL(value) (value),
# define EX_FONT_ANSI_VAL(value) (value),

  定义结构体:

typedef struct _wide_unicode_font16x16
{
 WORD value; /* 内码 */
 BYTE data[32]; /* 字模点阵 */
}Unicode;
#define CHINESE_CHAR_NUM … /* 汉字数量 */

  字模的存储用数组:

Unicode chinese[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分钟才有一次小时变化,如果我们每次都将读取的时间在屏幕上完全重新刷新一次,则浪费了大量的系统时间。

  一个较好的办法是我们在时间显示函数中以静态变量分别存储小时、分钟、秒,只有在其内容发生变化的时候才更新其显示。

extern void DisplayTime(…)
{
 static BYTE byHour,byMinute,bySecond;
 BYTE byNewHour, byNewMinute, byNewSecond;
 byNewHour = GetSysHour();
 byNewMinute = GetSysMinute();
 byNewSecond = GetSysSecond();
 
 if(byNewHour!= byHour)
 {
  … /* 显示小时 */
  byHour = byNewHour;
 }
 if(byNewMinute!= byMinute)
 {
  … /* 显示分钟 */
  byMinute = byNewMinute;
 }
 if(byNewSecond!= bySecond)
 {
  … /* 显示秒钟 */
  bySecond = byNewSecond;
 }
}

  这个例子也可以顺便作为C语言中static关键字强大威力的证明。当然,在C++语言里,static具有了更加强大的威力,它使得某些数据和函数脱离"对象"而成为"类"的一部分,正是它的这一特点,成就了软件的无数优秀设计。
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

6
 

C语言嵌入式系统编程修炼之键盘操作

处理功能键

  功能键的问题在于,用户界面并非固定的,用户功能键的选择将使屏幕画面处于不同的显示状态下。例如,主画面如图1:


图1 主画面

  当用户在设置XX上按下Enter键之后,画面就切换到了设置XX的界面,如图2:


图2 切换到设置XX画面

  程序如何判断用户处于哪一画面,并在该画面的程序状态下调用对应的功能键处理函数,而且保证良好的结构,是一个值得思考的问题。

  让我们来看看WIN32编程中用到的"窗口"概念,当消息(message)被发送给不同窗口的时候,该窗口的消息处理函数(是一个callback函数)最终被调用,而在该窗口的消息处理函数中,又根据消息的类型调用了该窗口中的对应处理函数。通过这种方式,WIN32有效的组织了不同的窗口,并处理不同窗口情况下的消息。

  我们从中学习到的就是:

  (1)将不同的画面类比为WIN32中不同的窗口,将窗口中的各种元素(菜单、按钮等)包含在窗口之中;

  (2)给各个画面提供一个功能键"消息"处理函数,该函数接收按键信息为参数;

  (3)在各画面的功能键"消息"处理函数中,判断按键类型和当前焦点元素,并调用对应元素的按键处理函数。

/* 将窗口元素、消息处理函数封装在窗口中 */
struct windows
{
 BYTE currentFocus;
 ELEMENT element[ELEMENT_NUM];
 void (*messageFun) (BYTE keyValue);
 …
};
/* 消息处理函数 */
void messageFunction(BYTE keyValue)
{
 BYTE i = 0;
 /* 获得焦点元素 */
 while ( (element .ID!= currentFocus)&& (i < ELEMENT_NUM) )
 {
  i++;
 }
 /* "消息映射" */
 if(i < ELEMENT_NUM)
 {
  switch(keyValue)
  {
   case OK:
    element.OnOk();
    break;
   …
  }
 }
}

  在窗口的消息处理函数中调用相应元素按键函数的过程类似于"消息映射",这是我们从WIN32编程中学习到的。编程到了一个境界,很多东西都是相通的了。其它地方的思想可以拿过来为我所用,是为编程中的"拿来主义"。

  在这个例子中,如果我们还想玩得更大一点,我们可以借鉴MFC中处理MESSAGE_MAP的方法,我们也可以学习MFC定义几个精妙的宏来实现"消息映射"。
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4929

TA的资源

版主

7
 
C语言嵌入式系统编程修炼之性能优化

使用宏定义

  在C语言中,宏是产生内嵌代码的唯一方法。对于嵌入式系统而言,为了能达到性能要求,宏是一种很好的代替函数的方法。

  写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个:

  错误做法:

#define MIN(A,B)  ( A <= B ? A : B )

  正确做法:

#define MIN(A,B) ((A)<= (B) ? (A) : (B) )

  对于宏,我们需要知道三点:

  (1)宏定义"像"函数;

  (2)宏定义不是函数,因而需要括上所有"参数";

  (3)宏定义可能产生副作用。

  下面的代码:

least = MIN(*p++, b);

  将被替换为:

( (*p++) <= (b) ?(*p++):(b) )

  发生的事情无法预料。

  因而不要给宏定义传入有副作用的"参数"。

  使用寄存器变量

  当对一个变量频繁被读写时,需要反复访问内存,从而花费大量的存取时间。为此,C语言提供了一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,从而提高效率。寄存器变量的说明符是register。对于循环次数较多的循环控制变量及循环体内反复使用的变量均可定义为寄存器变量,而循环计数是应用寄存器变量的最好候选者。

  (1) 只有局部自动变量和形参才可以定义为寄存器变量。因为寄存器变量属于动态存储方式,凡需要采用静态存储方式的量都不能定义为寄存器变量,包括:模块间全局变量、模块内全局变量、局部static变量;

  (2) register是一个"建议"型关键字,意指程序建议该变量放在寄存器中,但最终该变量可能因为条件不满足并未成为寄存器变量,而是被放在了存储器中,但编译器中并不报错(在C++语言中有另一个"建议"型关键字:inline)。

  下面是一个采用寄存器变量的例子:

/* 求1+2+3+….+n的值 */
WORD Addition(BYTE n)
{
 register i,s=0;
 for(i=1;i<=n;i++)
 {
  s=s+i;
 }
 return s;
}

  本程序循环n次,i和s都被频繁使用,因此可定义为寄存器变量。

  内嵌汇编

  程序中对时间要求苛刻的部分可以用内嵌汇编来重写,以带来速度上的显著提高。但是,开发和测试汇编代码是一件辛苦的工作,它将花费更长的时间,因而要慎重选择要用汇编的部分。

  在程序中,存在一个80-20原则,即20%的程序消耗了80%的运行时间,因而我们要改进效率,最主要是考虑改进那20%的代码。

  嵌入式C程序中主要使用在线汇编,即在C程序中直接插入_asm{ }内嵌汇编语句:

/* 把两个输入参数的值相加,结果存放到另外一个全局变量中 */
int result;
void Add(long a, long *b)
{
 _asm
 {
  MOV AX, a
  MOV BX, b
  ADD AX, [BX]
  MOV result, AX
 }
}

  利用硬件特性

  首先要明白CPU对各种存储器的访问速度,基本上是:

CPU内部RAM > 外部同步RAM > 外部异步RAM > FLASH/ROM

  对于程序代码,已经被烧录在FLASH或ROM中,我们可以让CPU直接从其中读取代码执行,但通常这不是一个好办法,我们最好在系统启动后将FLASH或ROM中的目标代码拷贝入RAM中后再执行以提高取指令速度;

  对于UART等设备,其内部有一定容量的接收BUFFER,我们应尽量在BUFFER被占满后再向CPU提出中断。例如计算机终端在向目标机通过RS-232传递数据时,不宜设置UART只接收到一个BYTE就向CPU提中断,从而无谓浪费中断处理时间;

  如果对某设备能采取DMA方式读取,就采用DMA读取,DMA读取方式在读取目标中包含的存储信息较大时效率较高,其数据传输的基本单位是块,而所传输的数据是从设备直接送入内存的(或者相反)。DMA方式较之中断驱动方式,减少了CPU 对外设的干预,进一步提高了CPU与外设的并行操作程度。

  活用位操作

  使用C语言的位操作可以减少除法和取模的运算。在计算机程序中数据的位是可以操作的最小数据单位,理论上可以用"位运算"来完成所有的运算和操作,因而,灵活的位操作可以有效地提高程序运行的效率。举例如下:

/* 方法1 */
int i,j;
i = 879 / 16;
j = 562 % 32;
/* 方法2 */
int i,j;
i = 879 >> 4;
j = 562 - (562 >> 5 << 5);

  对于以2的指数次方为"*"、"/"或"%"因子的数学运算,转化为移位运算"<< >>"通常可以提高算法效率。因为乘除运算指令周期通常比移位运算大。

  C语言位运算除了可以提高运算效率外,在嵌入式系统的编程中,它的另一个最典型的应用,而且十分广泛地正在被使用着的是位间的与(&)、或(|)、非(~)操作,这跟嵌入式系统的编程特点有很大关系。我们通常要对硬件寄存器进行位设置,譬如,我们通过将AM186ER型80186处理器的中断屏蔽控制寄存器的第低6位设置为0(开中断2),最通用的做法是:

#define INT_I2_MASK 0x0040
wTemp = inword(INT_MASK);
outword(INT_MASK, wTemp &~INT_I2_MASK);

  而将该位设置为1的做法是:

#define INT_I2_MASK 0x0040
wTemp = inword(INT_MASK);
outword(INT_MASK, wTemp | INT_I2_MASK);

  判断该位是否为1的做法是:

#define INT_I2_MASK 0x0040
wTemp = inword(INT_MASK);
if(wTemp & INT_I2_MASK)
{
… /* 该位为1 */
}

  上述方法在嵌入式系统的编程中是非常常见的,我们需要牢固掌握。

  总结

  在性能优化方面永远注意80-20准备,不要优化程序中开销不大的那80%,这是劳而无功的。

  宏定义是C语言中实现类似函数功能而又不具函数调用和返回开销的较好方法,但宏在本质上不是函数,因而要防止宏展开后出现不可预料的结果,对宏的定义和使用要慎而处之。很遗憾,标准C至今没有包括C++中inline函数的功能,inline函数兼具无调用开销和安全的优点。

  使用寄存器变量、内嵌汇编和活用位操作也是提高程序效率的有效方法。

  除了编程上的技巧外,为提高系统的运行效率,我们通常也需要最大可能地利用各种硬件设备自身的特点来减小其运转开销,例如减小中断次数、利用DMA传输方式等。
此帖出自单片机论坛

赞赏

1

查看全部赞赏

 
 
 

回复

144

帖子

0

TA的资源

一粒金砂(初级)

8
 

眼都晕了

看标题,直接回复。
此帖出自单片机论坛
 
个人签名小小的我,大大的梦想...
 
 

回复

20

帖子

0

TA的资源

一粒金砂(初级)

9
 
好东西,不过不能下载了看有点遗憾!
此帖出自单片机论坛
 
个人签名思想有多远,你就能走多远!
 
 

回复

112

帖子

0

TA的资源

一粒金砂(初级)

10
 

好东西,

好东西,
此帖出自单片机论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表