嵌入编程论文范文

2024-08-15

嵌入编程论文范文(精选9篇)

嵌入编程论文 第1篇

关键词:梯形图,编程,嵌入式PLC

1 关于软PLC的主要构成模块介绍

编程系统以及运行系统是嵌入式软PLC的主要部分, 其中编程系统的功能是对梯形图以及解析相关指令的工作。软PLC编程系统相对传统来讲, 更具国际化, 公认的模块有编辑编译模块等, 他们各司其职, 具有不同的但又相互联系的功能。运行系统则主要负责执行相应的代码指令, 从而达到控制的目的。其具体框架如下图1所示。

2 关于软PLC的主要界面功能介绍

软PLC的界面主要有信息输出模块、指令表模块以及梯形图编辑模块。有5种国际PLC编程语言的定义, 包括顺序功能图, 指令, 梯形图等。梯形图是其中最易操作使用的语言, 因此本编程所选择的用户程序开发语言就是梯形图。在软PLC编程系统界面中, 梯形图编辑模块主要的功能是对于梯形图的驻留以及设置参数, 指令表模块的功能主要在于对梯形图对应的指令进行逻辑显示。信息输出模块主要是对梯形图模块中所输入的语法问题进行显示, 包括输出提示语法错误以及提示语法得到正确执行的信息。

3 梯形图的编辑详情

梯形图的编辑, 首先是选择所需加载的梯形图进行元件类型。其次是点击所需编辑的区域, 通过程序扫面获得相应的笛卡儿坐标值。第三是添加梯形图的元件类型, 让其进行存储链接。第四是在小窗口中调出并显示梯形图。第五是设置相应的其他需要设置的元件参数。

3.1 对于梯形图的绘制

梯形图的绘制主要是按矢量图进行, 矢量图所占内存较小且较易操作。可以借助VC++中装置的矢量图功能进行梯形图的绘制工作, 主要是通过扁历链表以及CDC中的指针函数来完成梯形图的绘制, 具体步骤如下:

绘制结束后的梯形图, 系统将会对该梯形图的行号、列号进行自动设置, 用户无法随意更改, 而对于元件编号等参数需要用户进行相应的设置。

3.2 实现梯形图序列化

梯形图的序列化有助于梯形图多次编辑的实现, 所谓序列化是指将数据存在CDocument中, 并且其中的变量将变为文件, 这样就形成了序列化。序列化可以将相关变量保存为文件, 从而可以方便以后加载使用。而将变量导入到内存中, 则是反序列化。另外通过调用“》”、“《”就能建立缓冲区以及读写数据, 因为梯形图导入了CObject的功能, 因此具有动态创建缓冲区以及类型识别等功能。

4 梯形图的编译详情

这是真个编程系统的重要部分, 它主要的功能是将用户所编辑的内容转化为可识别运行的目标程序, 期间需要经过逻辑、语法的分析反复扫描, 才能将梯形图程序转化为运行系统能够识别的程序, 假若发现梯形图编辑错误, 则会将错误信息反馈到界面中。以梯级为扫描基础, 对每个元素进行逐个扫描, 利用深度扫描算法进行相应的扫描工作, 其扫描顺序是从上至下, 从左至右。其扫描的逻辑分区以竖线元素来划分。其具体扫描过程见下图2。

另外为了提高梯形图的移植性, 本程序软件还加入了配置文件技术, 主要用来对于梯形图数据的记录, 以便仿真模块能够读取到相关的信息。

5 梯形图的仿真设计

仿真模块是软PLC的特色模块, 它能够实现对程序的模拟运行, 从而在编译程序的过程中就能检测代码是否正确。逻辑运算是仿真模块的最主要的工作部分, 逻辑运算模块的功能主要是负责识别梯形图提供的数据信息。并且进行仿真模拟时, 用户所编辑的梯形图程序将被转为C语言程序, 这样逻辑运算模块才能进行控制工作。

6 结语

本文所介绍的嵌入式软PLC编程系统采用的梯形图具有简便快捷等优势, 其编辑、编译、仿真等功能模块的设置亦显示出了人性化特点, 方便用户操作使用。并且还通过导入VC++的功能进行辅助实现面向对象的交互, 突破了以往PLC的缺陷。另外, 仿真模块还实现了VC++软件系统的开发环境与软PLC系统的耦合度区分, 使得软PLC更具应用价值。

参考文献

[1]葛永翠.工业控制中以太网技术的发展与研究[J].科技广场, 2009 (01) .

[2]张琪, 李菲.一种教育/娱乐机器人控制器的设计与实现[J].中国科技信息, 2009 (16) .

[3]邱霞.基于嵌入式智能家居远程监控系统设计[J].科技信息, 2009 (28) .

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

C语言嵌入式系统编程修炼之道——背景篇...1 C语言嵌入式系统编程修炼之道——软件架构篇...4 1.模块划分...4 2.多任务还是单任务...5 3.单任务程序典型架构...6 4.中断服务程序...7 5.硬件驱动模块...9 6.C的面向对象化...10 总结...10 C语言嵌入式系统编程修炼之道——内存操作篇...12 1.数据指针...12 2.函数指针...13 3.数组vs.动态申请...14 4.关键字const 15 5.关键字volatile.16 6.CPU字长与存储器位宽不一致处理...17 总结...18 C语言嵌入式系统编程修炼之道——屏幕操作篇...19 1.汉字处理...19 2.系统时间显示...20 3.动画显示...21 4.菜单操作...22 5.模拟MessageBox函数...24 总结...26 C语言嵌入式系统编程修炼之道——键盘操作篇...27 1.处理功能键...27 2.处理数字键...28 3.整理用户输入...29 总结...30 C语言嵌入式系统编程修炼之道——性能优化篇...31 1.使用宏定义...31 2.使用寄存器变量...31 3.内嵌汇编...32 4.利用硬件特性...32 5.活用位操作...33 总结

C语言嵌入式系统编程修炼之道——背景篇 不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力。无疑,汇编语言具备这样的特质。但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发的一般选择。而与之相比,C语言——一种“高级的低级”语言,则成为嵌入式系统开发的最佳选择。笔者在嵌入式系统项目的开发过程中,一次又一次感受到C语言的精妙,沉醉于C语言给嵌入式开发带来的便利。本文的目的在于进行“C语言嵌入式系统开发的内功心法”秀,一共包括25招。

图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.C语言嵌入式系统编程修炼之道——软件架构篇 1.模块划分

模块划分的“划”是规划的意思,意指怎样合理的将一个很大的软件划分为一系列功能独立的部分合作完成系统的需求。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)软件功能模块,其模块的划分应满足低偶合、高内聚的要求。2.多任务还是单任务

所谓“单任务系统”是指该系统不能支持多任务并发操作,宏观串行地执行一个任务。而多任务系统则可以宏观并行(微观上可能串行)地“同时”执行多个任务。

多任务的并发执行通常依赖于一个多任务操作系统(OS),多任务OS的核心是系统调度器,它使用任务控制块(TCB)来管理任务调度功能。TCB包括任务的当前状态、优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针等信息。调度器在任务被激活时,要用到这些信息。此外,TCB还被用来存放任务的“上下文”(context)。任务的上下文就是当一个执行中的任务被停止时,所要保存的所有信息。通常,上下文就是计算机当前的状态,也即各个寄存器的内容。当发生任务切换时,当前运行的任务的上下文被存入TCB,并将要被执行的任务的上下文从它的TCB中取出,放入各个寄存器中。嵌入式多任务OS的典型例子有Vxworks、ucLinux等。嵌入式OS并非遥不可及的神坛之物,我们可以用不到1000行代码实现一个针对80186处理器的功能最简单的OS内核,作者正准备进行此项工作,希望能将心得贡献给大家。

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

(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七层协议栈大行其道成为事实上的标准? 经常有网友讨论:

printf(“%d,%d”,++i,i++);

/* 输出是什么?*/ c = a+++b;

/* c=? */ 等类似问题。面对这些问题,我们只能发出由衷的感慨:世界上还有很多有意义的事情等着我们去消化摄入的食物。实际上,嵌入式系统要运行到世界末日。4.中断服务程序

中断是嵌入式系统中重要的组成部分,但是在标准C中不包含中断。许多编译开发商在标准C上增加了对中断的支持,提供新的关键字用于标示中断服务程序(ISR),类似于__interrupt、#program interrupt等。当一个函数被定义为ISR的时候,编译器会自动为该函数增加中断服务程序所需要的中断现场入栈和出栈代码。

中断服务程序需要满足如下要求:(1)不能返回值;

(2)不能向ISR传递参数;

(3)ISR应该尽可能的短小精悍;

(4)printf(char * lpFormatString,„)函数会带来重入和性能问题,不能在ISR中采用。

在某项目的开发中,我们设计了一个队列,在中断服务程序中,只是将中断类型添加入该队列中,在主程序的死循环中不断扫描中断队列是否有中断,有则取出队列中的第一个中断类型,进行相应处理。/* 存放中断的队列 */ typedef struct tagIntQueue { int intType;

/* 中断类型 */ struct tagIntQueue *next;}IntQueue;

IntQueue lpIntQueueHead;

__interrupt ISRexample(){

int intType;

intType = GetSystemType();QueueAddTail(lpIntQueueHead, intType);/* 在队列尾加入新的中断 */ } 在主程序循环中判断是否有中断: While(1){ If(!IsIntQueueEmpty())

{

intType = GetFirstInt();

switch(intType)

/* 是不是很象WIN32程序的消息解析函数? */

{

/* 对,我们的中断类型解析很类似于消息驱动 */

case xxx:

/* 我们称其为“中断驱动”吧? */

break;

case xxx:

break;

} }

} 按上述方法设计的中断服务程序很小,实际的工作都交由主程序执行了。5.硬件驱动模块

一个硬件驱动模块通常应包括如下函数:(1)中断服务程序ISR(2)硬件初始化

a.修改寄存器,设置硬件参数(如UART应设置其波特率,AD/DA设备应设置其采样速率等);

b.将中断服务程序入口地址写入中断向量表: /* 设置中断向量表 */

m_myPtr = make_far_pointer(0l);/* 返回void far型指针void far * */

m_myPtr += ITYPE_UART;/* ITYPE_UART: uart中断服务程序 */ /* 相对于中断向量表首地址的偏移 */

*m_myPtr = &UART _Isr;

/* UART _Isr:UART的中断服务程序 */(3)设置CPU针对该硬件的控制线

a.如果控制线可作PIO(可编程I/O)和控制信号用,则设置CPU内部对应寄存器使其作为控制信号;

b.设置CPU内部的针对该设备的中断屏蔽位,设置中断方式(电平触发还是边缘触发)。

(4)提供一系列针对该设备的操作接口函数。例如,对于LCD,其驱动模块应提供绘制像素、画线、绘制矩阵、显示字符点阵等函数;而对于实时钟,其驱动模块则需提供获取时间、设置时间等函数。6.C的面向对象化

在面向对象的语言里面,出现了类的概念。类是对特定数据的特定操作的集合体。类包含了两个范畴:数据和操作。而C语言中的struct仅仅是数据的集合,我们可以利用函数指针将struct模拟为一个包含数据和操作的“类”。下面的C程序模拟了一个最简单的“类”: #ifndef C_Class

#define C_Class struct #endif C_Class A {

C_Class A *A_this;

/* this指针 */

void(*Foo)(C_Class A *A_this);/* 行为:函数指针 */

int a;

/* 数据 */

int b;};我们可以利用C语言模拟出面向对象的三个特性:封装、继承和多态,但是更多的时候,我们只是需要将数据与行为封装以解决软件结构混乱的问题。C模拟面向对象思想的目的不在于模拟行为本身,而在于解决某些情况下使用C语言编程时程序整体框架结构分散、数据和函数脱节的问题。我们在后续章节会看到这样的例子。总结

本篇介绍了嵌入式系统编程软件架构方面的知识,主要包括模块划分、多任务还是单任务选取、单任务程序典型架构、中断服务程序、硬件驱动模块设计等,从宏观上给出了一个嵌入式系统软件所包含的主要元素。

请记住:软件结构是软件的灵魂!结构混乱的程序面目可憎,调试、测试、维护、升级都极度困难。

一个高尚的程序员应该是写出如艺术作品般程序的程序员。

C语言嵌入式系统编程修炼之道——内存操作篇 1.数据指针

在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的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语言指针以指向的数据类型长度作自增和自减。理解这一点对于以指针直接操作内存是相当重要的。2.函数指针

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

(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启动后第一条要执行的指令的位置。

记住:函数无它,唯指令集合耳;你可以调用一个没有函数体的函数,本质上只是换一个地址开始执行指令!3.数组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应成对出现!4.关键字const const意味着“只读”。区别如下代码的功能非常重要,也是老生长叹,如果你还不知道它们的区别,而且已经在程序界摸爬滚打多年,那只能说这是一个悲哀: const int a;int const a;const int *a;int * const a;int const * a const;(1)关键字const的作用是为给读你代码的人传达非常有用的信息。例如,在函数的形参前添加const关键字意味着这个参数在函数体内不会被修改,属于“输入参数”。在有多个形参的时候,函数的调用者可以凭借参数前是否有const关键字,清晰的辨别哪些是输入参数,哪些是可能的输出参数。

(2)合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改,这样可以减少bug的出现。const在C++语言中则包含了更丰富的含义,而在C语言中仅意味着:“只能读的普通变量”,可以称其为“不能改变的变量”(这个说法似乎很拗口,但却最准确的表达了C语言中const的本质),在编译阶段需要的常数仍然只能以#define宏定义!故在C语言中如下程序是非法的: const int SIZE = 10;char a[SIZE];/* 非法:编译阶段不能用到变量 */ 5.关键字volatile C语言编译器会对用户书写的代码进行优化,譬如如下代码: int a,b,c;a = inWord(0x100);/*读取I/O空间0x100端口的内容存入a变量*/ b = a;a = inWord(0x100);/*再次读取I/O空间0x100端口的内容存入a变量*/ c = a;很可能被编译器优化为: int a,b,c;a = inWord(0x100);/*读取I/O空间0x100端口的内容存入a变量*/ b = a;c = a;但是这样的优化结果可能导致错误,如果I/O空间0x100端口的内容在执行第一次读操作后被其它程序写入新值,则其实第2次读操作读出的内容与第一次不同,b和c的值应该不同。在变量a的定义前加上volatile关键字可以防止编译器的类似优化,正确的做法是: volatile int a;

volatile变量可能用于如下几种情况:

(1)并行设备的硬件寄存器(如:状态寄存器,例中的代码属于此类);(2)一个中断服务子程序中会访问到的非自动变量(也就是全局变量);(3)多线程应用中被几个任务共享的变量。6.CPU字长与存储器位宽不一致处理

在背景篇中提到,本文特意选择了一个与CPU字长不一致的存储芯片,就是为了进行本节的讨论,解决CPU字长与存储器位宽不一致的情况。80186的字长为16,而NVRAM的位宽为8,在这种情况下,我们需要为NVRAM提供读写字节、字的接口,如下: typedef unsigned char BYTE;typedef unsigned int WORD;

/* 函数功能:读NVRAM中字节

* 参数:wOffset,读取位置相对NVRAM基地址的偏移

* 返回:读取到的字节值 */ extern BYTE ReadByteNVRAM(WORD wOffset){

LPBYTE lpAddr =(BYTE*)(NVRAM + wOffset * 2);/* 为什么偏移要×2? */

return *lpAddr;}

/* 函数功能:读NVRAM中字

* 参数:wOffset,读取位置相对NVRAM基地址的偏移

* 返回:读取到的字 */ extern WORD ReadWordNVRAM(WORD wOffset){

WORD wTmp = 0;

LPBYTE lpAddr;

/* 读取高位字节 */

lpAddr =(BYTE*)(NVRAM + wOffset * 2);

/* 为什么偏移要×2? */

wTmp +=(*lpAddr)*256;

/* 读取低位字节 */

lpAddr =(BYTE*)(NVRAM +(wOffset +1)* 2);

/* 为什么偏移要×2? */

wTmp += *lpAddr;

return wTmp;}

/* 函数功能:向NVRAM中写一个字节

*参数:wOffset,写入位置相对NVRAM基地址的偏移 *

byData,欲写入的字节 */ extern void WriteByteNVRAM(WORD wOffset, BYTE byData){

… }

/* 函数功能:向NVRAM中写一个字 */ *参数:wOffset,写入位置相对NVRAM基地址的偏移 *

wData,欲写入的字 */ extern void WriteWordNVRAM(WORD wOffset, WORD wData){

… } 子贡问曰:Why偏移要乘以2? 子曰:请看图1,16位80186与8位NVRAM之间互连只能以地址线A1对其A0,CPU本身的A0与NVRAM不连接。因此,NVRAM的地址只能是偶数地址,故每次以2为单位前进!

图1 CPU与NVRAM地址线连接

子贡再问:So why 80186的地址线A0不与NVRAM的A0连接? 子曰:请看《IT论语》之《微机原理篇》,那里面讲述了关于计算机组成的圣人之道。总结

本篇主要讲述了嵌入式系统C编程中内存操作的相关技巧。掌握并深入理解关于数据指针、函数指针、动态申请内存、const及volatile关键字等的相关知识,是一个优秀的C语言程序设计师的基本要求。当我们已经牢固掌握了上述技巧后,我们就已经学会了C语言的99%,因为C语言最精华的内涵皆在内存操作中体现。

我们之所以在嵌入式系统中使用C语言进行程序设计,99%是因为其强大的内存操作能力!

如果你爱编程,请你爱C语言; 如果你爱C语言,请你爱指针; 如果你爱指针,请你爱指针的指针!

C语言嵌入式系统编程修炼之道——屏幕操作篇 1.汉字处理

现在要解决的问题是,嵌入式系统中经常要使用的并非是完整的汉字库,往往只是需要提供数量有限的汉字供必要的显示功能。例如,一个微波炉的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}

} } 要显示特定汉字的时候,只需要从数组中查找内码与要求汉字内码相同的即可获得字模。如果前面的汉字在数组中以内码大小顺序排列,那么可以以二分查找法更高效的查找到汉字的字模。

这是一种很有效的组织小汉字库的方法,它可以保证程序有很好的结构。2.系统时间显示

从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具有了更加强大的威力,它使得某些数据和函数脱离“对象”而成为“类”的一部分,正是它的这一特点,成就了软件的无数优秀设计。3.动画显示

动画是无所谓有,无所谓无的,静止的画面走的路多了,也就成了动画。随着时间的变更,在屏幕上显示不同的静止画面,即是动画之本质。所以,在一个嵌入式系统的LCD上欲显示动画,必须借助定时器。没有硬件或软件定时器的世界是无法想像的:

(1)

没有定时器,一个操作系统将无法进行时间片的轮转,于是无法进行多任务的调度,于是便不再成其为一个多任务操作系统;

(2)

没有定时器,一个多媒体播放软件将无法运作,因为它不知道何时应该切换到下一帧画面;

(3)

没有定时器,一个网络协议将无法运转,因为其无法获知何时包传输超时并重传之,无法在特定的时间完成特定的任务。

因此,没有定时器将意味着没有操作系统、没有网络、没有多媒体,这将是怎样的黑暗?所以,合理并灵活地使用各种定时器,是对一个软件人的最基本需求!在80186为主芯片的嵌入式系统中,我们需要借助硬件定时器的中断来作为软件定时器,在中断发生后变更画面的显示内容。在时间显示“xx:xx”中让冒号交替有无,每次秒中断发生后,需调用ShowDot: void ShowDot(){ static BOOL bShowDot = TRUE;

/* 再一次领略static关键字的威力 */ if(bShowDot)

{ showChar(‘:’,xPos,yPos);} else

{ showChar(‘ ’,xPos,yPos);

} bShowDot =!bShowDot;} 4.菜单操作

无数人为之绞尽脑汁的问题终于出现了,在这一节里,我们将看到,在C语言中哪怕用到一丁点的面向对象思想,软件结构将会有何等的改观!笔者曾经是个笨蛋,被菜单搞晕了,给出这样的一个系统: 图1 菜单范例

要求以键盘上的“←→”键切换菜单焦点,当用户在焦点处于某菜单时,若敲击键盘上的OK、CANCEL键则调用该焦点菜单对应之处理函数。我曾经傻傻地这样做着:

/* 按下OK键 */ void onOkKey(){ /* 判断在什么焦点菜单上按下Ok键,调用相应处理函数 */ Switch(currentFocus){ case MENU1:

menu1OnOk();

break;case MENU2:

menu2OnOk();

break;„ } } /* 按下Cancel键 */ void onCancelKey(){ /* 判断在什么焦点菜单上按下Cancel键,调用相应处理函数 */ Switch(currentFocus){ case MENU1:

menu1OnCancel();

break;case MENU2:

menu2OnCancel();

break;„ } } 终于有一天,我这样做了:

/* 将菜单的属性和操作“封装”在一起 */ typedef struct tagSysMenu

{

char *text;

/* 菜单的文本 */

BYTE xPos;/* 菜单在LCD上的x坐标 */

BYTE yPos;/* 菜单在LCD上的y坐标 */

void(*onOkFun)();

/* 在该菜单上按下ok键的处理函数指针 */

void(*onCancelFun)();/* 在该菜单上按下cancel键的处理函数指针 */ }SysMenu, *LPSysMenu;当我定义菜单时,只需要这样: static SysMenu menu[MENU_NUM] = {

{

“menu1”, 0, 48, menu1OnOk, menu1OnCancel

} ,{

“ menu2”, 7, 48, menu2OnOk, menu2OnCancel

} ,{

“ menu3”, 7, 48, menu3OnOk, menu3OnCancel

} ,{

“ menu4”, 7, 48, menu4OnOk, menu4OnCancel

}

… };OK键和CANCEL键的处理变成: /* 按下OK键 */ void onOkKey(){

menu[currentFocusMenu].onOkFun();

} /* 按下Cancel键 */ void onCancelKey(){ menu[currentFocusMenu].onCancelFun();

} 程序被大大简化了,也开始具有很好的可扩展性!我们仅仅利用了面向对象中的封装思想,就让程序结构清晰,其结果是几乎可以在无需修改程序的情况下在系统中添加更多的菜单,而系统的按键处理函数保持不变。面向对象,真神了!5.模拟MessageBox函数

MessageBox函数,这个Windows编程中的超级猛料,不知道是多少入门者第一次用到的函数。还记得我们第一次在Windows中利用MessageBox输出“Hello,World!”对话框时新奇的感觉吗?无法统计,这个世界上究竟有多少程序员学习Windows编程是从MessageBox(“Hello,World!”,„)开始的。在我本科的学校,广泛流传着一个词汇,叫做“‘Hello,World’级程序员”,意指入门级程序员,但似乎“‘Hello,World’级”这个说法更搞笑而形象。

图2 经典的Hello,World!图2给出了两种永恒经典的Hello,World对话框,一种只具有“确定”,一种则包含“确定”、“取消”。是的,MessageBox的确有,而且也应该有两类!这完全是由特定的应用需求决定的。

嵌入式系统中没有给我们提供MessageBox,但是鉴于其功能强大,我们需要模拟之,一个模拟的MessageBox函数为:

/****************************************** /*

函数名称:

MessageBox /*

功能说明:

弹出式对话框,显示提醒用户的信息 /*

参数说明:

lpStr---提醒用户的字符串输出信息

/*

TYPE---输出格式(ID_OK = 0, ID_OKCANCEL = 1)/*

返回值:

返回对话框接收的键值,只有两种 KEY_OK, KEY_CANCEL /****************************************** typedef enum TYPE

{ ID_OK,ID_OKCANCEL

}MSG_TYPE;extern

BYTE MessageBox(LPBYTE lpStr, BYTE TYPE){

BYTE keyValue =-1;

ClearScreen();

/* 清除屏幕 */

DisplayString(xPos,yPos,lpStr,TRUE);/* 显示字符串 */

/* 根据对话框类型决定是否显示确定、取消 */

switch(TYPE)

{

case

ID_OK:

DisplayString(13,yPos+High+1, “ 确定 ”, 0);

break;

case

ID_OKCANCEL:

DisplayString(8, yPos+High+1, “ 确定 ”, 0);

DisplayString(17,yPos+High+1, “ 取消 ”, 0);

break;

default:

break;

}

DrawRect(0, 0, 239, yPos+High+16+4);/* 绘制外框 */

/* MessageBox是模式对话框,阻塞运行,等待按键 */

while((keyValue!= KEY_OK)||(keyValue!= KEY_CANCEL))

{ keyValue = getSysKey();} /* 返回按键类型 */ if(keyValue== KEY_OK){ return ID_OK;} else { return ID_CANCEL;} } 上述函数与我们平素在VC++等中使用的MessageBox是何等的神似啊?实现这个函数,你会看到它在嵌入式系统中的妙用是无穷的。总结

本篇是本系列文章中技巧性最深的一篇,它提供了嵌入式系统屏幕显示方面一些很巧妙的处理方法,灵活使用它们,我们将不再被LCD上凌乱不堪的显示内容所困扰。

屏幕乃嵌入式系统生存之重要辅助,面目可憎之显示将另用户逃之夭夭。屏幕编程若处理不好,将是软件中最不系统、最混乱的部分,笔者曾深受其害。

C语言嵌入式系统编程修炼之道——键盘操作篇 1.处理功能键

功能键的问题在于,用户界面并非固定的,用户功能键的选择将使屏幕画面处于不同的显示状态下。例如,主画面如图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 [i].ID!= currentFocus)&&(i < ELEMENT_NUM))

{

i++;

}

/* “消息映射” */

if(i < ELEMENT_NUM)

{

switch(keyValue)

{

case OK:

element[i].OnOk();

break;

}

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

在这个例子中,如果我们还想玩得更大一点,我们可以借鉴MFC中处理MESSAGE_MAP的方法,我们也可以学习MFC定义几个精妙的宏来实现“消息映射”。2.处理数字键

用户输入数字时是一位一位输入的,每一位的输入都对应着屏幕上的一个显示位置(x坐标,y坐标)。此外,程序还需要记录该位置输入的值,所以有效组织用户数字输入的最佳方式是定义一个结构体,将坐标和数值捆绑在一起: /* 用户数字输入结构体 */ typedef struct tagInputNum

{

BYTE byNum;/* 接收用户输入赋值 */

BYTE xPos;

/* 数字输入在屏幕上的显示位置x坐标 */

BYTE yPos;

/* 数字输入在屏幕上的显示位置y坐标 */

}InputNum, *LPInputNum;那么接收用户输入就可以定义一个结构体数组,用数组中的各位组成一个完整的数字:

InputNum inputElement[NUM_LENGTH];/* 接收用户数字输入的数组 */ /* 数字按键处理函数 */ extern void onNumKey(BYTE num){

if(num==0|| num==1)/* 只接收二进制输入 */

{ /* 在屏幕上显示用户输入 */ DrawText(inputElement[currentElementInputPlace].xPos, inputElement[currentElementInputPlace].yPos, “%1d”, num);

/* 将输入赋值给数组元素 */

inputElement[currentElementInputPlace].byNum = num;

/* 焦点及光标右移 */

moveToRight();

} } 将数字每一位输入的坐标和输入值捆绑后,在数字键处理函数中就可以较有结构的组织程序,使程序显得很紧凑。3.整理用户输入

继续第2节的例子,在第2节的onNumKey函数中,只是获取了数字的每一位,因而我们需要将其转化为有效数据,譬如要转化为有效的XXX数据,其方法是:

/* 从2进制数据位转化为有效数据:XXX */ void convertToXXX(){

BYTE i;

XXX = 0;

for(i = 0;i < NUM_LENGTH;i++)

{

XXX += inputElement[i].byNum*power(2, NUM_LENGTH1);

}

} 反之,我们也可能需要在屏幕上显示那些有效的数据位,因为我们也需要能够反向转化:

/* 从有效数据转化为2进制数据位:XXX */ void convertFromXXX(){

BYTE i;

XXX = 0;

for(i = 0;i < NUM_LENGTH;i++)

{

inputElement[i].byNum = XXX / power(2, NUM_LENGTH1)% 2;

}

} 当然在上面的例子中,因为数据是2进制的,用power函数不是很好的选择,直接用“<< >>”移位操作效率更高,我们仅是为了说明问题的方便。试想,如果用户输入是十进制的,power函数或许是唯一的选择了。总结

本篇给出了键盘操作所涉及的各个方面:功能键处理、数字键处理及用户输入整理,基本上提供了一个全套的按键处理方案。对于功能键处理方法,将LCD屏幕与Windows窗口进行类比,提出了较新颖地解决屏幕、键盘繁杂交互问题的方案。

计算机学的许多知识都具有相通性,因而,不断追赶时髦技术而忽略基本功的做法是徒劳无意的。我们最多需要“精通”三种语言(精通,一个在如今的求职简历里泛滥成灾的词语),最佳拍档是汇编、C、C++(或JAVA),很显然,如果你“精通”了这三种语言,其它语言你应该是可以很快“熟悉”的,否则你就没有“精通”它们。

C语言嵌入式系统编程修炼之道——性能优化篇 1.使用宏定义

在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))发生的事情无法预料。

因而不要给宏定义传入有副作用的“参数”。2.使用寄存器变量

当对一个变量频繁被读写时,需要反复访问内存,从而花费大量的存取时间。为此,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都被频繁使用,因此可定义为寄存器变量。3.内嵌汇编

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

在程序中,存在一个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

}

}

4.利用硬件特性

首先要明白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与外设的并行操作程度。5.活用位操作

使用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函数兼具无调用开销和安全的优点。

嵌入编程论文 第3篇

毋庸置疑,SoC是未来嵌入式系统的承载主体,器件可编程化则是嵌入式系统的发展主题,当发展主题和承载主体两大理念合二为一,一个新的奇迹就此诞生。

PSoC成长奇迹

赛普拉斯半导体(Cypress)公司的PSoC(可编程片上系统),从诞生之日起就经历了惊人的成长历程。PSoC大规模的商业化交付始于2002年,2003/2004年在8位嵌入式MCU(微控制器)市场排名41位,2005/2006年跃居第15位,2007/2008年跃至第11位。2009年3月11日,Cypress宣布PSoC在全球出货量已达5亿片!

如此市场奇迹缘于各方对PSoC的广泛认可。一些企业把CapSense作为了追逐的目标,CapSense触摸感应芯片目前占PSoC总出货量的60%。一些专家也关注到PSoC开发工具的独特之处。例如,图形化嵌入式设计软件——PSoC Express 3.0曾荣获本刊“2007影响中国的嵌入式系统新技术奖”,专家认为该工具是最先把图形化设计方法引入嵌入式设计业的产品之一。学生是接触新事物最快的群体,在嵌入式系统高校竞赛中PSoC颇受青睐,例如去年在某公司举办的多核竞赛作品展示时,参赛学生在控制部分采用了PSoC。网友们对PSoC的评价是上手快、应用方便,其模拟性能很强,很适合模拟高手采用。

可见,PSoC以独特的模拟可编程定位成为8位MCU大千世界里的一朵奇葩。但Cypress坚定地认为:PSoC不是MCU,是SoC!更确切地说是可编程嵌入式片上系统。Cypress过去主要定位通信市场的存储、时钟、数字逻辑等芯片,2001年网络泡沫破灭后,公司转向了消费类电子、工业等其他新兴PSoC事业部副总裁Gahan Richardson:PSoC 3和PSoC 5将原有市场规模扩大10倍市场,在此背景下诞生的PSoC何以能在短短几年内获得巨大成功?MCU与可编程嵌入式片上系统的区别在哪里?PSoC的下一步发展方向将如何?

2009年9月14日,PSoC翻开了重要的篇章:在原有PSoC(现在被该公司命名为PSoC 1)的基础之上,推出两系列全新架构产品:PSoC 3和PSoC5,分别采用8051架构和ARM Cortex-M3核,并使模拟和数字可编程性能大幅提升。借此机会,我们深入理解了PSoC的特点及走向。

天下难事必做于易

我国古人老子说:“天下难事必做于易,天下大事必做于细。”从用户角度来说,PSoC为设计者提供了便捷性和灵活性,适合各种电子设计,可谓化繁为简。

新推出的PSoC 3和PSoC 5器件都基于市场上通用、较为流行的架构或内核,为的是方便用户了解、获得第三方开发支持。例如PSoC 3基于8位8051处理器,PSoC 5器件则包含了ARM公司两三年前在ARM7基础上优化的Cortex-M3处理器。这两款新产品为设计者提供了一个无缝的可编程设计平台,可以轻松地从Cypress的8位过渡到

16位或32位。除了提供标准的MCU内核之外,新的PSoC 3和PSoC5架构还包含高精度可编程模拟能力(最高20位分辨率的ADC)以及可扩展的可编程数字资源,并配有足够的存储器和通讯外设。

“PSoC 3和PSoC 5架构扩充了PSoC原有的可编程嵌入式系统设计平台,将PSoC的应用市场规模扩大10倍并增至150亿美元,涵盖8、16、32位应用和精确模拟市场。”CypressPSoC事业部副总裁Gahan Richardson说,“PSoC 3和PSoC 5能够在诸如马达控制、智能供电和电源管理、人机界面(如CapSense触摸感应)、LCD节段显示、图形控制,以及音频/语音处理、通讯协议等应用中大显身手。这些新的性能大大地拓展了PSoc的应用市场范围,包括工业、医疗、汽车、通讯和消费电子设备等等。可以说‘PSoC is everywhere,即PSoC可以用于任何电子产品。”

早在几年前,8位市场已经被认为量大以至有些过滥,营业额已经难以上升,很多公司纷纷把注意力转移到32位市场。弊和利并不是绝对对立的,Cypress在大力发掘8位MCU市场中获益匪浅。

天下大事必做于细

与一般MCU厂家比较注重MCU部分的策略不同,Cypress注重在可编程模拟部分,增加其产品的价值,在灵活性方面给予客户更多的选择。

丰富的模拟资源

pSoC 3和PSoC 5架构包含了高精度可编程模拟资源,可以配置为ADC、DAC、TIA、混合器、PGA、运放以及其他模拟器件。除此之外,还包括增强型的基于可编程逻辑的数字资源,可配置为8、16、24和32位计时器、计数器、PWM以及更多高级数字外设,例如循环冗余校验(CRC)、伪随机顺序(PRS)发生器,以及正交调幅解码器。PSoC 3和PSoC 5所拥有的基于PLD的全功能通用逻辑使设计者们拥有了独特的能力,可以对这一数字系统进行客户化设计。这一新架构还支持多种通讯接口,包括全速USB、I2C、SPI、UART、CAN、LIN和I2S(如表1)。

处理器架构的提升

基于8051处理器的PSoC 3架构运算速度最高可达33 MIPS(如图1);而PSoc 5架构则囊括了一个32位ARMCortex-M3处理器,运算速度最高可达100 DMIPS。因提供了宽泛的0.5~5.5V的电压范围和低至200nA的休眠电流,这两种架构均可满足极低功耗应用的要求。PSoC 3和PSoC 5提供了从8位到32位架构、具有引脚和API兼容性的无缝可编程设计平台,而且拥有可编程通路,允许任何模拟或数字信号分配到任何通用I/O,从而简化了电路板布局。这一功能可以将LCD节段显示和CapSense信号引至任何GPIO引脚。

PSoC 3可编程精确模拟子系统

·Delta-Sigma ADC精度可达20位;

·12位SAR ADC的采样率高达1Msps;

·在工业温度和电压范围内,参考电压精确度可达±0.1%;

·最多4个8位精度、8Msps的DAC:1~50倍PGA;具有25mA驱动能力的通用运放;最多4个响应时间为30ns的比较器;

·类似DSP的数字滤波,可用于仪器仪表和医学信号处理;

·PSoC Creator软件中预先配置好的大型模拟外设库;

·所有器件均支持CapSense功能。

低功耗的秘诀

PSoC 3和PSoC 5工作电压可以低至0.5V,适合很多低功耗便携式的应用,特别是满足太阳能电池板、绿色能源应用的需要,主要通过以下几个步骤实现;

·针对太阳能电池板的0.5V需求,芯片内部集成了一个高效的调节器;

·PSoC使模拟部分的电压降低,一般MCU在数字部分可以实现低电压,但外部模拟功耗难以控制;

·在PSoC内部低功耗部分有三个工作模式:工作状态:睡眠状态功耗1μA(PSoC3)和2μA(PSoC 5);休眠状态功耗200nA(PSoC 3)和300nA(PSoC 5),为PSoC提供不同的耗电状态和工作效率;

·在软件方面,ADC可以提供低耗电的模式,所有Block(部分)都可以进入低功耗模式;

·模拟器件部分的DMA(直接存储器存取)可以把耗电降低。一般32位处理器才有DMA、PSoC的8位MCU已具有DMA,这对降低耗电量是个很大的贡献。

可编程高性能数字子系统

·“通用数字模块”阵列(UDB),每个均包含未定义逻辑(PLD)、结构逻辑(数据通道)以及通往其他UDB、I/O和外设的灵活通路:

·PSoC Creator软件中预先设置好的大型数字外设库,如8、16、24和32位计时器、计数器和PWM;

·通过基于PLD的全功能通用逻辑可将数字系统进行客户定制化;

·高速连接:全速USB、I2C、SPI、UART、CAN、LIN、I2S。

图形化的开发工具PSoC Creator

PSoC Creator集成开发环境(IDE)支持PSoC 3和PSoC s,开创性地将基于电路图的设计与全部测试过的、预先打包好的模拟和数字外设库结合起来,通过直观的向导和API(应用程序接口)即可进行客户化设计,实现特殊的设计要求。PSoC Creator使得工程师们能按照自己的思考方式进行设计,从而可大大缩短产品上市时间。

PSoC的未来

展望未来,PSoC将实现更多的模拟集成,例如现有闪存可做到64kB/8位和256kB/32位,将来会有更高密度产品推出。封装方面,公司会提供更丰富的封装选择,如CSP(芯片尺寸封装)、客户定制封装等。在某一个领域,例如工业应用,将考虑把以太网等接口集成进去。此外,Cypress已经从ARM获得ARM9笔IP授权、可以预见下一代可编程平台运算速度更高。

推出PSoC 3和PSoC s后,Cypress并未停止PSoC 1的开发,计划今年第四季度推出新产品。Cypress的策略是实现PSoC的8、16、32位处理器内核一揽子解决方案,可为客户提供从低到高的系列可编程嵌入式方案。

柏树精神

嵌入编程论文 第4篇

传统而言,并发多任务的实现采用的是在操作系统级运行多个进程,由操作系统按照一定的策略(优先级、循环等),调度各个进程的执行,以最大限度的利用计算机的各种资源。在这种实现方法中最基本的调度单位是操作系统级上的进程。由于各个进程拥有自己独立的运行环境(寄存器和地址空间等)。多线程程序设计,就是使单个程序中包含并发执行的多个线程。当多线程程序执行时,在该程序对应的进程中就有多个控制流在同时运行,即具有并发执行的多个线程。在一个进程中包含并发执行的多个控制流,而不是把多个控制流一一分散在多个进程中,这是多线程程序设计与并发多进程程序设计截然不同之处。本文在采纳后者思想的基础上实现了嵌入式系统的多线程编程

2 硬件环境

配置相对高端的PC一台,结合本文试验用的Magic ARM2410实验开发平台一套

3 软件环境

3.1 Windows ce 5.0

Windows CE5.0为微软针对个人电脑以外的电脑产品所研发的嵌入式操作系统,是微软于2004年7月推出的Windows CE的最新版本。操作系统功能进一步增强。把使用Platform Builder构建操作系统与使用命令行构建操作系统进行了统一。在Windows CE 5.0中,Platform Builder5.0集成开发环境只是命令行界面的简单封装,使用Platform Builder与使用命令行构建操作系统没有任何功能上的区别。BSP的质量得到了提高,更加模块化,结构更加清晰。Platform Builde是用于定制基于Windows CE OS为内核的嵌入式操作系统的集成开发环境。PB提供了所有基于Windows CE内核的design,create,build,test and debug的工具。安装W i n d o w s c e 5.0之前必须安装Framework 1.1.

3.2 e Mbedded Visual c++4.0(必须包含sp4)

e Mbedded Visual C++4.0,这一编译器主要是面象于Windows CE.NET操作系统的开发,是嵌入式系统Windows CE5.0应用程序的集成开发环境,具有编译应用程序、远程调试等功能。

3.3 Microsoft Acitve Sync 4.1

Microsoft Active Sync是Microsoft WindowsCE系统设备的电脑同步软件,可以在Win 98/Win ME/Win NT/Win2000/Win XP系统上运行;实现设备端与电脑的连接与通讯。

3.4 ZY2410 SDK

ZY2410是用Platform Builde5.0定制的一个基于Magic ARM2410运行的Windows ce 5.0,工程名为ZY2410,SDK是基于ZY2410平台开发应用程序必须的一个安装包。

4 多线程编程

Windows CE多线程编程包括线程的启动、线程的运行状态控制、线程同步及数据通信和线程的正常/非正常退出。本项目的软件及架构在多线程设计上,要求通过多线程实现异步的数据采集及绘制,以提高系统运行效率。

4.1 线程的启动

Win32API提供支持多线程的启动,调用API函数Create Thread()分配资源启动线程,并返回线程句柄(Handle),以控制线程状态。

4.2 线程的状态

一般有就绪态、运行台、阻塞态、终止态等。为了实现各线程的功能同步,有必要合理的是各个线程进行状态的切换。在MFC的线程框架下,通过:Afx Create Thread()启动线程、自身CWin Therad:Suspend()阻塞线程、其他线程调用CWin Thread:Resume()重新就绪线程、线程return正常退出。考虑到本嵌入式系统对稳定性、效率的高要求,去掉不安全的调用,如CWin Thread:Terminate()外部调用终止线程等方法。

4.3 线程的同步

Windows CE提供若干线程通信机制,本系统选用Event(事件)进行线程间的同步。通过Create Event()申请Event资源,返回HANFLE实例,以进行行为控制。在申请时即可指定生成时Event状态以及复位机制。本系统中信号生成时为复位状态,有且仅有手动调用Reset Event()才能复位--即手动方式,则在生成时参数为:C r e a t e E v e n t(N U L L,T R U E,F A L S E,N U L L)。

4.4 线程的优先级

本系统为及时响应驱动的数据传递信号,将与之相关的线程Sub Thread提高优先级,通过CWind Thread:Set Thread Priority(THREAD_PRIORITY_ABOVE_NORMAL)实现。但Windows CE严格按优先级进行调度,因此Sub Thread一般在Wait For Multiple Objects()阻塞状态,才能使其他线程得以运行。

5 多线程编程实现过程

5.1 主要控件属性

控件属性设置如表5.1所示

5.2 定义线程函数及消息处理代码

在类CThtead Dlg中声明线程函数,访问权限私有且为静态。Function T y p e为DWORD,Function Decleration为Thread Proc(PVOID p Arg).其代码如下:

运行按钮中所添加的消息处理程序代码如下

6 结语

通过以上内容的完成,进一步的熟悉了e Mbedded Visual C++4.0开发环境,并在此基础上实现了以线程为示例的P C与实验箱的通信,为更深层次的研究基于WINCE 5.0应用程序开发奠定良好的基础。

参考文献

[1]周立功等.ARM&Win CE实验与实践[M].北京:北京航空航天大学出版社,2007.7.

[2]Samsung公司.S3C2410A User’s Manual Rebision1.0.2004.

[3]张冬泉等.Windows CE实用开发技术[M].北京:电子工业出版社,2006.

[4]汪兵等.EVC高级编程及用应开发[M].北京:中国水利水电出版社,2005.

嵌入式系统C语言编程方法研究 第5篇

关键词:嵌入式系统,C语言,编程思维

1 嵌入式系统C语言编程思维

一般形式的软件编程不同于嵌入式系统C语言编程, 嵌入式系统编程建立在特定的51单片机、ARM芯片、DSP数字处理芯片等硬件平台上, 所以要求其编程语言具有较强的硬件操控能力。汇编语言是最接近于机器语言的一种编程语言。但是, 由于汇编语言结构的复杂性, 且移植到不同的硬件平台, 所以它并不是嵌入式系统开发的一般选择。而与之相比, C语言是一种最接近机器语言的高级语言, 称之为嵌入式底层硬件开发的最佳编程语言。

在了解嵌入式系统构成的基础上, 嵌入式C语言的编程要注意以下几个方面:

1.1 程序模块化

是指怎样合理的将一个很大的工程文件划分为一系列功能独立的各个模块进行编程、编译、调试。C语言是一种结构化的程序设计语言, 在模块的划分上主要依据功能 (依功能进行划分在面向对象设计中成为一个错误, 牛顿定律遇到了相对论) 来划分, C语言程序模块化设计需要涉及的概念: (1) 一个程序文件 (.c文件) 和一个库文件 (.h) 文件的合成一个模块, 头文件 (.h) 中是对于该模块软硬件接口的声明; (2) 某模块提供给其它模块调用的外部函数及数据需在.h中文件中冠以extern关键字声明; (3) 以static关键字声明的变量, 是模块内的函数和全局变量, 需在.c文件开头; (4) 只有在汇编中, 才可以认为定义变量和声明变量的区别在于定义会产生内存分配的操作, 所以, 禁止.h文件中定义变量。

1.2 任务模式选择

所谓任务模式选择, 就是单任务还是多任务的选择。“单任务系统”是指该系统模式下, 不能支持在同一时刻下的多任务并发操作。在计算机编译原理中, 也介绍过, 计算任务的执行, 可以认为是宏观串行地执行一个任务。而多任务系统则可以宏观并行 (微观上是串行) 地“同时”执行多个任务。多任务的并发执行通常依赖于一个多任务操作系统 (OS) , 多任务OS的核心是系统调度器, 它使用任务控制块 (TCB) 来管理任务调度功能。

嵌入式多任务OS的典型例子有Vxworks、ucLinux等。嵌入式OS并非遥不可及的神坛之物, 我们可以用不到1000行代码实现一个针对80186处理器的功能最简单的OS内核, 作者正准备进行此项工作, 希望能将心得贡献给大家。

1.3 单任务模式程序

(1) 从MCU复位 (reset) 时的指定地址开始执行;

(2) 跳转至汇编程序startup处执行;

(3) 跳转至用户主程序main函数执行, 在main从上而下顺序执行: (1) 初始化 (驱动程序) 各硬件设备; (2) 初始化 (各接口程序) 各软件模块; (3) 进入死循环 (无限循环) , 调用各模块的处理函数

用户main函数和各模块的处理函数都以C语言完成。用户main函数最后都进入了一个死循环, 这也是一个可选方案。

1.4 中断服务程序

中断是嵌入式系统中重要的组成部分, 它规定了一个硬件在规定的条件下, 跳转到其他子函数执行其他优先级高的任务。但是在标准C中不包含中断。因此许多编译开发商在标准C上增加了对中断的支持。当一个函数被定义为ISR的时候, 编译器会自动配置程序所需要的函数的堆栈。

在下面的一个队列, 将中断类型添加到队列中时, 在main函数的执行过程中, 主程序死循环中不断扫描该程序是否有中断, 有则取出中断函数, 进行相应的处理。

在下面的一个队列, 在中断服务程序中, 只是将中断类型添加入该队列中, 在主程序的死循环中不断扫描中断队列是否有中断, 有则取出队列中的第一个中断类型, 进行相应处理。

按上述方法设计的中断服务程序所占用的内存很小, 实际的工作都交由主程序由上至下执行。

1.5 硬件模块驱动驱动程序

一个硬件驱动模块通常应包括如下函数:

(1) 中断服务程序ISR。

(2) 硬件驱动: (1) 修改寄存器, 设置硬件参数 (如芯片工作模式、串口波特率、效验位) ; (2) 将中断程序入口地址写入中断向量表格:

(3) 设置MCU针对该硬件的模式: (1) 如果控制线可作可编程I/O和传输数据用途, 则设置MCU内部对应寄存器使其作为控制信号; (2) 设置MCU内部的针对该设备的中断模式, 设置中断触发方式。

(4) 设置对该设备的操作接口函数。例如, 对于实时时钟, 其驱动模块需提供时间、设置时间转换等函数。对于液晶屏, 驱动程序包括像素描绘、字库以及一些字符点显示函数。

2 结束语

嵌入编程论文 第6篇

在进行数据库访问时,往往需要将SQL强大的查询能力与通用编程语言的表达能力结合起来,即在应用程序中嵌入SQL查询来访问数据库中的数据。SQL查询所嵌入的编程语言称为宿主语言,宿主语言中使用的SQL结构被称为嵌入式SQLㄢ

一个使用嵌入式SQL的程序在编译前要通过一个特殊的预处理器 (SQL Server的预处理器是nsqlprep.exe) 进行预处理,将程序中的嵌入式SQL请求转换成宿主语言的过程调用,然后用宿主语言编译器对预处理以后的程序进行编译、连接生成可执行程序。

2、嵌入式SQL编程介绍

2.1 SQL通信区

SQL通信区 (SQL Communication Area,简称SQLCA) 用于包含程序中刚结束的那条嵌入式SQL语句的执行状态信息。C语言通过语句"EXEC SQL INCLUDE sqlca;"定义SQLCAㄢ

SQLCA中最重要的数据项是sqlcode (long变量) ,作用是存放每次SQL语句执行后的状态,其值为0表示成功执行;其值为1表示SQL语句执行,但遇到异常;其值为负表示由于系统错误导致SQL语句没有执行。应用程序每执行完一条SQL语句,都应检测sqlcode变量的值,以得到该SQL语句的执行情况,从而决定程序的执行流程。

2.2 嵌入式SQL语句格式

为使预处理器识别应用程序中的嵌入式SQL请求,需使用EXEC SQL语句,格式如下:EXEC SQL<SQL语句>;。在执行任何嵌入式SQL语句前,程序必须先连接到数据库,用下面语句实现:EXEC SQL CONNECT TO ServeNamer.DatabaseName US-ER UserName.Password;

2.3 宿主变量

嵌入式SQL语句中使用的宿主语言变量称为宿主变量 (简称主变量) 。主变量分为输入主变量和输出主变量。输入主变量由应用程序赋值,并由嵌入式SQL语句引用;输出主变量由嵌入式SQL语句对其赋值或设置状态信息,并供应用程序进一步处理。一个主变量的定义后可紧跟一个指示变量 (整数类型) ,若其值小于0,表示对应的主变量为空值。嵌入式SQL语句引用主变量时,需在变量名前加冒号。

2.4 游标

SQL采用面向集合的数据处理方式,而用一组宿主变量一次只能存放一个元组的若干属性值,可通过游标机制解决这个矛盾。游标是数据库系统开设的一个数据缓冲区,用于存放SQL查询的结果。游标赋予了应用程序逐行处理查询结果集的能力。

3、嵌入式SQL编程在SQL Server中的实现

3.1 编程前准备

SQL Server为嵌入式SQL提供了一些特殊接口,若采用默认安装方式,则在安装目录下没有包含文件"devtools.rar"、预编译程序"nsqlprep.exe"和动态链接库文件"sqlaiw32.dll"和"sqlakw32.dll"。用户可将安装光盘中的devtools文件夹复制到系统安装目录 (默认安装目录是"C:Program FilesMicrosoft SQL Server") ,并将安装光盘的x86binn目录中的nsqlprep.exe和sqlaiw32.dll、sqlakw32.dll复制到系统安装目录的MSSQLBinn子目录。

3.2 初始化环境

3.2.1 初始化Visual C++6.0编译器环境

在命令提示符环境 (cmd.exe) 下,通过cd命令进入vcvars32bat文件所在目录 (VC++6.0安装目录的bin子目录下,VC++6.0的默认安装目录是C:Program FilesMicrosoft Visual StudioVC98) ,再输入文件名"vcvars32.bat"执行程序,完成初始化。

3.2.2 初始化SQL Server预编译环境

在命令提示符环境 (cmd.exe) 下,通过cd命令进入setenv bat文件所在目录 (在devtools文件夹的esqlc子目录下,再输入文件名"setenv.bat"执行程序,完成初始化。

3.3 嵌入式SQL编程示例

下面示例程序分析了如何在C程序中嵌入SQL代码,以及演示了嵌套游标技术:

将上述程序保存在文件"sample.sqc"中,并将该文件存放在SQL Server安装目录的MSSQLBinn子目录,打开命令提示符环境,通过cd命令进入Binn目录,输入命令"nsqlprep sample sqc",即使用预编译程序nsqlprep对sample.sqc进行预处理,若预编译成功,则生成sample.c源程序文件。

3.4 用VC++6.0编译源程序文件

编译前要将SQL Server安装目录下的MSSQLBinn目录的文件路径添加到系统环境变量path中,方法是:右击"我的电脑"→"属性"→"高级"选项卡→"环境变量"→双击变量"Path",添加路径。

启动VC++6.0,建立一个"Win32 Console Application"工程"EmbededSqlTest",并在工程中添加源程序文件sample.cㄢ

在VC++6.0窗口选择"工具"菜单→选项→目录选项卡→为Include files添加路径"C:Program FilesMicrosoft SQL ServerDEVTOOLSINCLUDE",并为Library files添加路径"C:Program FilesMicrosoft SQL ServerDEVTOOLSX86LIB"。

在VC++6.0窗口选择"工程"菜单→设置→连接→在"对象库模块"文本框中输入文件名"sqlakw32.lib"和"caw32.lib"。

最后对sample.c进行编译、连接,运行结果如图1.1所示:

4、结束语

虽然一些教材对嵌入式SQL编程原理作了介绍,但没有给出实现嵌入式SQL编程的详细步骤,本文不仅给出基于SQL Server和VC++6.0进行嵌入式SQL编程的详细步骤,且通过嵌套游标增强程序访问数据库的功能,为高校从事数据库教学的老师指导学生进行嵌入式SQL编程实验提供参考。

摘要:本文介绍嵌入式SQL编程的主要原理, 并给出基于SQL Server和VC++6.0进行嵌入式SQL编程的详细步骤和一个使用嵌套游标技术的编程示例。

关键词:嵌入式SQL,游标,SQL Server,VC++6.0

参考文献

[1].Abraham Silberschatz, Henry F.Korth, S.Sudarshan.数据库系统概念[M].杨冬清, 马秀莉, 唐世渭, 译.北京:机械工业出版社, 2006:87-90

嵌入编程论文 第7篇

后PC时代, 嵌入式系统将拥有最大的市场。产业的发展给高职的教育带来了新的课题, 要提高学生的核心竞争力, 重点在于使他们掌握迎合产业发展的最新技术。而嵌入式系统专业课程的知识点多, 涉及面广, 课程新颖, 难度大。面对教学对象为高职学生, 就要做好量体裁衣, 因材施教。

一、《嵌入式C编程》课程是嵌入式技术及应用专业的专业基础课

嵌入式系统是软硬件结合的产物, 通常嵌入式硬件设计完成后, 各种功能就全靠软件来实现。与通用软件相比, 嵌入式软件具有自身的一些特点, 如规模较小、实时性和可靠性要求高、与硬件结合紧密等。与此相对应, C语言是一种融合了嵌入式软件开发特点的现代语言, 其语言简洁、紧凑, 使用时灵活、方便。由于C编译生成的目标代码小、效率高、速率快, 充分体现了在嵌入式编程中经济使用资源的原则, C语言已成为嵌入式软件开发的主流编程语言。

二、《嵌入式C编程》课程教学内容及方法

1、嵌入式C编程教学内容

嵌入式开发中C语言与普通C语言的在内容选取上有所侧重。在嵌入式系统的教学中, C语言这门课程的教学内容必须进行选择和取舍。以下是本课程侧重的内容要点:

(1) 数据类型:数据类型是编程语言中最基本的构成元素, 在嵌入式软件开发中, 标准C的变量和数据类型具有新的特征。最大的改变发生在默认整数类型是8位或者16位。C编译器识别int8, int16, int24, int32数据类型, 它们是含有相应数位的整数, 这些整型数据类型避免了整数长度变化而带来的歧义性。

(2) 关键字const、volatile、static的使用:一个变量是以它的类型和存储类型表征的, 关键字const、volatile可表示变量的两种属性:不变性和易变性, 如果变量声明中带有关键字const, 则不能通过赋值、增加或减少运算来修改该变量的值。[用const关键字修饰的变量为只读变量, 通常放在Flash只读空间中, 可节省RAM空间;const修饰变量的内容一般不希望被修改, const可防止一些无意的代码修改其内容。通常在编译时, 代码要被优化的, 如:int a=3;a=3×4;在程序中, a=3没使用则将被编译器优化掉, 但在嵌入式编程中, 硬件是不能被优化掉的 (状态寄存器) , 为防止被优化掉需加关键字volatile。当全局变量前加static时, 表示该变量已被封装, 其他函数在使用与被static修饰的变量或同名函数时不发生冲突。

(3) 位操作:C语言的位操作可能对变量中的个别位进行操作, 例如:通常向硬件设备发送一两个字节来控制该设备。为了节省内存空间, 在系统软件中常将多个标志状态简单地组合在一起, 存储到一个字节 (或字) 中。有时, 存储1个信息不必占用1个字节, 只需二进制的1个 (或多个) 位就够用。如果仍然使用结构类型, 则造成内存空间的浪费。为此, C语言引入了位段类型。

(4) 中断:中断是嵌入式系统中重要的组成部分。使用时应注意:ISR不能返回一个值;ISR不能传递参数;在许多的处理器/编译器中, 浮点一般都是不可重入的。此外, ISR应该是短而有效率的, 在ISR中做浮点运算是不明智的。

(5) 自定义类型typedef:在C语言中用typedef为已存在的数据类型的起别名, 如:typedef struct s*pt;pt pt1, pt2;通常用typedef显式标识出各数据类型的长度和符号特性, 避免直接使用标准数据类型。

(6) 数据类型转换;正确地使用数据类型的转换有助于提高程序的安全性, 建议使用显式强制转换。隐式转换容易造成致命的程序缺陷。

(6) 预处理:预处理是编译环境处理C程序的第一个环节, 但往往最先被程序员忽略。条件编译:按条件对C程序的一部分进行编译, 其它部分不编译。条件编译的目的:是使源代码能更迅速、更容易地进行修改, 并使目标代码缩短, 条件编译可有效地提高程序的可移植性。宏定义是C语言中实现类似函数功能而又不具函数调用和返回开销的较好方法, 但宏在本质上不是函数, 因而要防止宏展开后出现不可预料的结果, 对宏的定义和使用要小心。使用这些预处理命令可以编写出更易移植、易调试、模块化的程序, 从而提高程序的开发效率。

2、嵌入式C编程实践教学方法

(1) 课堂上理论案例教学法

具体教学时, 先引出基本概念和基本知识点, 然后将这些知识加以整合以案例的形式出现, 让学生在案例中掌握如何运用这些知识解决实际问题。

(2) 实验课任务 (项目) 驱动法

嵌入式C编程是一门实践性很强的课程, 只有让学生亲自动手多做实验, 才能消化掌握并灵活应用课堂所学知识。“项目驱动式”教学的关键在于培养学生掌握“做什么”和“如何做”的思维能力。一个项目就是一个工程, 在“项目驱动式”教学中, 首先应该让学生简单了解什么是软件工程思想, 其次在c语言理论教学过程中, 让学生懂得程序设计的风格, 最后引导他们来设计项目。我院的具体做法是, 把全班分为若几个小组, 从一开始就为每个小组提供几个综合性的项目进行自由选择, 各个小组一旦选择好项目, 就会带着问题去学习, 老师引导学生学习, 同时学生自己也要主动查阅资料、书籍或相互讨论, 这样随着新知识的学习项目也在动态的实现着, 最后在项目完成的同时也达到了知识的整合, 学生既掌握了知识, 又了解了项目的整个流程。为增强学生的实践动手能力奠定了基础。

结束语

信息时代、数字时代使得嵌入式产品获得了巨大的发展机遇, 也为嵌入式市场展现了美好的前景, 同时也对嵌入式系统的开发者提出了新的挑战。

摘要:随着计算机技术和微电子技术的迅速发展, 嵌入式系统应用领域越来越广泛。嵌入式人才供不应求, 如何合理、高效地培养嵌入式人才已迫在眉睫。《嵌入式C编程》课程作为嵌入式技术及应用专业的专业基础课, 作者经过多年的教学探索与实践, 就高职高专学生特点, 对该课程教学内容的选取、教学实践等方面提出了自己的一些见解。

关键词:嵌入式,C编程,教学内容及实践

参考文献

[1]Stephen Prat, C Primer Plus.人民邮电出版社, 2005年。

ARM嵌入式系统的C语言编程探讨 第8篇

1ARM和嵌入式系基本概念

嵌入式系统也就是说嵌入式计算机系统, 可以使用在大型厂房或者机器装置进行监控中, 相比较普通计算机系统来说, 存在以下几方面优势: 第一, 计算机系统主要面对的就是大部分行业和大众, 但是嵌入式计算机系统主要面向的是一些特定应用; 第二, 一般来说计算机软件大部分都是储存在硬盘或者磁盘中, 但是嵌入式计算机软件大部分存储在单片机或者存储器芯片上[1]; 第三 , 一般情况下 , 嵌入式计算机系统需要一定比较专业的编程环境和开发工具, 与计算机系统相类似, 嵌入式计算机系统包括应用软件和操作软件, 由此可发现, 其中基本结构主要包括了嵌入式外围设备、 嵌入式处理器、 嵌入式应用软件、 嵌入式操作系统, 嵌入式处理器实际上与计算机CPU类似, 属于系统核心部分。 侵入式处理器中最重要的就是ARM系类处理器, 具备功耗 低 、 速度快、 价格低等特点, 使其拥有广阔的应用前景, 现阶段已经在通信系统、 电子产品和军事系统中得到广泛运用。

2main函数和系统引导

一般来说, C语言都是从main函数开始进行的, main函数的基本原型就是: Int main (int argc, char***argv), argv表示参数个数, argv表示各项参数指针数组。 利用操作系统的内核来启动main函数, 系统内核主要就是能否为初始化变量提供函数, 并且能够在完成调用以后, 检查函数的返回值, 如果返回值是0, 说明程序可以正常运行, 否则说明程序存在错误[2]。 在嵌入式系统中 , 如果没有一定的操作系统内核 , 智能通过系统引导来完成初始化main函数的工作。 在系统引导部分合理完成初始化以后, 通过汇编语言来达到目的。 主要工作包括初始化硬件、 设置寄存器、 初始化全局变量、 加载mai运行模块、 初始化堆参数。 完成上述工作以后, 需要在C语言的main函数中运用控制权, 对于main函数来说, argv和arge参数和返回值没有实际意义。 此外, 为了避免出现混淆问题, 需要给main函数重新取名, 否则, 会使得编译器形成很多初始化代码, 导致接错系统引导模块和C程序入口。

3函数局数变量个数

为了能够提高执行程序的速度, 在编译函数的时候, 在寄存器中合理分配局部变量。 如果局部变量多余寄存器的时候, 编译器会适当把多出来的变量存入到存储器中, 所以, 需要合理控制局部变量的个数[3]。 AMR处理器主 要使用的 就是RISC结构 , 并且具有 很多的内 部寄存器 。 编译器需 要合理运 用APCS开关选项 , 也就是能够符合ATPCS标准 , 从理论上来说, 具备14个寄存器可以适当存储局部变量。 在实际使用的过程中, 由于自身具备一定的特殊性, R9在读取与位置相关语言的时候, 可以当做静态基址寄存器, R12可以被当做子程序来调用内部临时寄存器, 以此, 需要合理限制局部变量数目。

4LCD终端和存储器管

4.1LCD终端

系统I/O重要内容 实际上就 是LCD终端如阿 健 , 包括LCD绘图 、 LCD字符显示 、 320*240像素LCD显示器 , 可以显示5行 *20列的汉字字符或者15行 *40列的英文字符, 具有比较良好的分辨率图像现实功能[4]。

4.2存储管理

从广义上来说, 存储管理包括内存、 磁盘文件系统、 片内高速Cacge等, 在嵌入式系统中, 最有意义的实际上就是适当和分配内存动态和管理Flash存储器。 C语言系统中, 释放和分配动态内存的时候, 主要利用free和malloc标准库函数实现 。 Malloc可以从系 统内存空 闲空间中 合理分配 内存块 , free可以完成回回收内存块的作用 , 上述两种函数需要相应的系统内核, 在ARM系统平台上, 不可以直接进 行调用 , 因此, 需要适当编写m-free和m-alloc函数, 以便于达到动态存储管理的目的。

5设计应用程序和驱动程序

5.1驱动程序

驱动程序主要包括两大部分, 最底层中断处理器以及处理器上的驱动程序, 实现设计驱动程序与外围设备息息相关, 具有一定复杂性。 在设计C语言驱动程序的时候, 需要注意以下方面: 一般来说, 利用中断来连接外围设备和CPU。 中断属于外部异步事件, 在设计过程中, 处理和中断有关程序的时候, 需要谨慎小心。 设计C语言编程的时候还需要考虑效率, 如, 操作对数组元素的时候, Array [idx/4] =&~1, 可以适当变为Array [idx>>2] =&~1,其中>>属于移位运算, 具有比较复杂的算法, 现在存在的先进编辑器能够合理优化上述语句, 但是并不是所有设备都具有以上功能[5]。

5.2应用程序

在设计嵌入式应用程序的时候, 与计算机应用程序存在很多不同的地方, 需要密切注意。 第一, 不能使用操作系统支持的标准函数, 可以自己进行编写, 例如, m-free。 第二, 具有有限内存资源的时候, 存在有限栈容量, 还不能进行自动扩展, 使用的时候要小心。 第三, 不可以使用递 归函数 。第四, 不能忽略编译器的警告信息, 如果编译人员具有良好的素质, 是不会忽略任何警告的, 一般来说, 出警告信息往往存在比较严重的逻辑错误[6]。

6结语

主要分析了嵌入式系统中比较具有代表性的C语言编程和嵌入式系统, 此外还介绍了嵌入式系统应用程序设计, 上述过程中, 会出现不能直接借用内部资源的问题, 导致限制容量, 使得在设计扩展的时候出现困难, 在系统中应用C语言编程方式可以很好地解决上述问题, 随着不断发展和日渐成熟的C语言编程方式, 为其ARM嵌入式系统发展提供了依据和保障。

参考文献

[1]高永平.嵌入式DSP系统C语言硬件编程技术[J].电子制作,2014,(13):65-66.

[2]过怡.基于ARM的高效C语言编程[J].单片机与嵌入式系统应用,2010,(8):74-75.

[3]甄华.基于ARM嵌入式系统的C语言编程分析[J].哈尔滨职业技术学院学报,2015,(1):148-149.

[4]孙婧.ARM嵌入式系统C语言编程分析[J].计算机光盘软件与应用,2011,(18):215-215.

[5]李丽萍.基于ARM嵌入式系统的C语言编程初探[J].电子测试,2014,(13):3-4.

嵌入编程论文 第9篇

标准SQL是非过程化的查询语言, 难以实现业务中的逻辑控制。嵌入式的SQL编程, 可以很好的克服这一缺点, 提高应用系统和RDBMS间的互操作性。

嵌入式SQL就是一种将SQL语句和高级编程语言结合起来的一种方式, 通过此方式的合作, 将大大提高编程的效率和处理数据的能力。通过将SQL语句嵌入到编程语言中可以使SQL语句专心的用来处理数据事件, 将需要调取数据库的事件用SQL语句来代替, 需要逻辑处理的问题通过编程来解决, 使得对数据处理要求较高的程序问题, 也很容易的能够被解决。

本文从嵌入式SQL的处理过程、嵌入式SQL语句的与主语言之间的通信、不用游标的SQL语句、使用游标的SQL语句和动态SQL等五个方面来重点讲解嵌入式SQL。

1 嵌入式SQL语句的主要组成部分

通信区:嵌入式SQL在执行程序以后, 会有若干的结果信息, 系统会要求将信息传递给应用程序。这种信息通常有当前的工作状态和结果数据。系统应该从SQL中取出这些数据信息, 通过对这些数据信息的分析来决定下面应该执行的逻辑操作。

主变量:高级编程语言的程序变量可以最为SQL语句的输入或者输出的数据, SQL语句将高级编程语言中的变量当作自己的主变量, 根据主变量的不同, 分为输入和输出主变量。输入的意思就是对高级编程语言进行赋值的操作。输出的意思就是收到的状态信息或者数据等信息, 反馈给主系统中。

游标:通常情况下, SQL语句是面向一种集体数据, 它的结果数据可能为单条记录也可能是一个记录数组。但是高级编程语言不能满足SQL语句的这种输入数据的方式, 因此就出现的游标的情况, 通过游标的使用来对这两种情况进行处理。

2 嵌入式SQL的存在形式

不用游标的SQL语句:有的嵌入式SQL语句是不需要使用游标的。这些语句通常有说明性语句、数据定义语句、数据控制语句和查询语句最后结果为单记录的一些Select语句、非current形式的增、删、改、查语句等。查询结果是单记录的语句, 不需要游标的原因是它的结果是唯一的, 它仅仅需要用into语句来存放查询结果, 将其作为主变量。一些增、删、改、查语句不需要使用游标的原因是在UPDATE的SET和where语句有可以被使用为主变量, 其中set字句还可以被使用为指示变量。

使用游标的SQL语句:一般情况下, 必须使用游标的语句有查询的结果是多条记录的语句和current形式的UPDATE和DELETE语句。一般来说, SELECT语句的查询结果在很多情况下是一个数组形式的, 它包含有多条记录。因此使用游标机制非常有必要。我们通过游标机制将多个记录按条形式来送到高级编程语言中来进行处理, 从而将集合性的操作变成是对每一个记录的处理。UPDATE和DELETE语句也属于是数组式的操作, 如果是想针对单条记录进行处理, 就需要游标来帮忙, 现用游标的SELECT语句查出所有的记录, 然后再修改和删除这些记录, 最后通过CURRENT形式的语句来删除和修改。

动态SQL:嵌入式的SQL语句使用的条件、查询目标列和主变量都是固定的, 它们属于静态的SQL语句。静态的SQL语句虽然能够满足一般情况下的要求, 但是如果要执行的时候才提交SQL的语句和查询的条件等等情况的话, 就需要使用动态的SQL来进行处理。动态的SQL在程序运行的时候会临时的组装这些SQL语句。动态的SQL语句能够支持动态参数和SQL语句, 给开发人员提供任何设计SQL语句的能力。

程序的主变量中包含的内容不在是保存数据输入或者是输出的内容, 而是SQL语句的内容。SQL语句在程序执行期间可以设定为不同的SQL语句, 然后来运行。

动态参数是一种可变元素, 通过参数的符号来表示数据的运行设定问题。较之前的主变量也是不同的。动态的参数不是通过编译的时候来完成绑定操作, 而是利用准备变量和绑定数据等时候来完成绑定操作的。

动态参数绑定需要一系列的步骤:先要声明SQL语句主变量, 然后准备SQL语句, 最后执行准备好的SQL语句。

3 嵌入式SQL语句处理过程与源程序的通信

嵌入式SQL是将SQL语句嵌入到程序设计语言中的一种形式。对于嵌入式SQL来说, 使用预处理程序对源程序进行扫描, 识别出嵌入式SQL中的语句, 最后将SQL语句转换成主语言来调用。这样做的目的就是使主语言编译成功的代码可以很好的识别出它们。最终我们要将主语言编译成目标的程序来处理。

在ESQL中, 将所有的SQL语句前面加EXEC作为开始, 同时以 (;) 结束, 来区分SQL语句和主语言语句的区别。

通过将SQL语句使用到高级编程语言中, 这样我们就可以通过SQL语句来确保对数据库部分的操作, 通过高级编程语言来确保在程序中处理整个逻辑流程。通常这时候系统就会出现两种语句, 负责它们之间的通信主要有三个部分:

第一:通过SQL通信来实现。通过向主语言传递SQL的状态信息, 使主语言可以准确的知道程序的逻辑流程应该如何跳转。

第二:通过主变量的方式来实现。此时高级语言向SQL语句提供参数, 保证数据处理部分的正确执行。

第三:通过主变量和游标来实现。此时只需要将SQL语句查询数据库中数据的结果, 传递给高级语言来处理即可实现通信。

4 结论

本文主要讲述了嵌入式SQL。讲述如何将SQL语句嵌入到高级语言中如C语言中。利用高级语言的强大计算能力来实现复杂应用的需求。

在嵌入式SQL中, SQL语句是要和数据库进行互相操作的, SQL语句是操作数据库存取数据的。而高级语言会用来控制程序流转的流程和对数据的进一步的处理和加工。

SQL语句和高级编程语言有非常不同的处理方式, SQL是一种面向集合的编程方式, 而高级编程语言是一种面向记录的编程方式, 所以SQL语句需要引入游标这个东西, 通过对游标的使用, 来帮助协调它们之间的关系。

本文通过嵌入式SQL的处理过程、嵌入式SQL语句的与主语言之间的通信、不用游标的SQL语句、使用游标的SQL语句和动态SQL这五个方面来对数据库编程中嵌入式SQL进行探讨和研究。

参考文献

[1]袁鹏飞.中文版SQL Server2000数据库系统管理.北京:人民邮电出版社, 2001.

[2][美]Microsoft公司.Microsoft SQL Server2000数据库编程.北京:希望电子出版社, 2001

上一篇:自然主义手法下一篇:书法特色