c语言嵌入式编程注意事项

上传人:woxinch****an2018 文档编号:38637697 上传时间:2018-05-05 格式:DOC 页数:33 大小:312KB
返回 下载 相关 举报
c语言嵌入式编程注意事项_第1页
第1页 / 共33页
c语言嵌入式编程注意事项_第2页
第2页 / 共33页
c语言嵌入式编程注意事项_第3页
第3页 / 共33页
c语言嵌入式编程注意事项_第4页
第4页 / 共33页
c语言嵌入式编程注意事项_第5页
第5页 / 共33页
点击查看更多>>
资源描述

《c语言嵌入式编程注意事项》由会员分享,可在线阅读,更多相关《c语言嵌入式编程注意事项(33页珍藏版)》请在金锄头文库上搜索。

1、C C 语言嵌入式系统编程注意事项之背景篇语言嵌入式系统编程注意事项之背景篇本文的讨论主要围绕以通用处理器为中心的协议处理模块进行,因为它更多地牵涉到具体的 C 语言编程技巧不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力。无疑,汇编语言具备这样的特质。但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发的一般选择。而与之相比,C 语言-一种“高级的低级“语言,则成为嵌入式系统开发的最佳选择。笔者在嵌入式系统项目的开发过程中,一次又一次感受到C 语言的精妙,沉醉于 C 语言给嵌入式开发带来的便利。图 1 给出了本文的讨论所基于

2、的硬件平台,实际上,这也是大多数嵌入式系统的硬件平台。它包括两部分: (1) 以通用处理器为中心的协议处理模块,用于网络控制协议的处理;(2) 以数字信号处理器(DSP)为中心的信号处理模块,用于调制、解调和数/模信号转换。本文的讨论主要围绕以通用处理器为中心的协议处理模块进行,因为它更多地牵涉到具体的 C 语言编程技巧。而 DSP 编程则重点关注具体的数字信号处理算法,主要涉及通信领域的知识,不是本文的讨论重点。着眼于讨论普遍的嵌入式系统 C 编程技巧,系统的协议处理模块没有选择特别的 CPU,而是选择了众所周知的 CPU 芯片-80186,每一位学习过微机原理的读者都应该对此芯片有一个基本

3、的认识,且对其指令集比较熟悉。80186的字长是 16 位,可以寻址到的内存空间为 1MB,只有实地址模式。C 语言编译生成的指针为 32 位(双字),高 16 位为段地址,低 16 位为段内编译,一段最多 64KB。协议处理模块中的 FLASH 和 RAM 几乎是每个嵌入式系统的必备设备,前者用于存储程序,后者则是程序运行时指令及数据的存放位置。系统所选择的FLASH 和 RAM 的位宽都为 16 位,与 CPU 一致。实时钟芯片可以为系统定时,给出当前的年、月、日及具体时间(小时、分、秒及毫秒),可以设定其经过一段时间即向 CPU 提出中断或设定报警时间到来时向 CPU 提出中断(类似闹钟

4、功能)。NVRAM(非易失去性 RAM)具有掉电不丢失数据的特性,可以用于保存系统的设置信息,譬如网络协议参数等。在系统掉电或重新启动后,仍然可以读取先前的设置信息。其位宽为 8 位,比 CPU 字长小。文章特意选择一个与 CPU 字长不一致的存储芯片,为后文中一节的讨论创造条件。UART 则完成 CPU 并行数据传输与 RS-232 串行数据传输的转换,它可以在接收到1MAX_BUFFER字节后向 CPU 提出中断,MAX_BUFFER 为 UART 芯片存储接收到字节的最大缓冲区。键盘控制器和显示控制器则完成系统人机界面的控制。以上提供的是一个较完备的嵌入式系统硬件架构,实际的系统可能包含

5、更少的外设。之所以选择一个完备的系统,是为了后文更全面的讨论嵌入式系统C 语言编程技巧的方方面面,所有设备都会成为后文的分析目标。嵌入式系统需要良好的软件开发环境的支持,由于嵌入式系统的目标机资源受限,不可能在其上建立庞大、复杂的开发环境,因而其开发环境和目标运行环境相互分离。因此,嵌入式应用软件的开发方式一般是,在宿主机(Host)上建立开发环境,进行应用程序编码和交叉编译,然后宿主机同目标机(Target)建立连接,将应用程序下载到目标机上进行交叉调试,经过调试和优化,最后将应用程序固化到目标机中实际运行。 CAD-UL 是适用于 x86 处理器的嵌入式应用软件开发环境,它运行在Windo

6、ws 操作系统之上,可生成 x86 处理器的目标代码并通过 PC 机的 COM 口(RS-232 串口)或以太网口下载到目标机上运行,如图 2。其驻留于目标机FLASH 存储器中的 monitor 程序可以监控宿主机 Windows 调试平台上的用户调试指令,获取 CPU 寄存器的值及目标机存储空间、I/O 空间的内容。后续章节将从软件架构、内存操作、屏幕操作、键盘操作、性能优化等多方面阐述 C 语言嵌入式系统的编程技巧。软件架构是一个宏观概念,与具体硬件的联系不大;内存操作主要涉及系统中的 FLASH、RAM 和 NVRAM 芯片;屏幕操作则涉及显示控制器和实时钟;键盘操作主要涉及键盘控制器

7、;性能优化则给出一些具体的减小程序时间、空间消耗的技巧。在我们的修炼旅途中将经过 25 个关口,这些关口主分为两类,一类是技巧型,有很强的适用性;一类则是常识型,在理论上有些意义。So, lets go.C C 语言嵌入式系统编程注意事项之软件架构篇语言嵌入式系统编程注意事项之软件架构篇模块划分的“划“是规划的意思,意指怎样合理的将一个很大的软件划分为一系列功能独立的部分合作完成系统的需求模块划分模块划分模块划分的“划“是规划的意思,意指怎样合理的将一个很大的软件划分为一系列功能独立的部分合作完成系统的需求。C 语言作为一种结构化的程序设计语言,在模块的划分上主要依据功能(依功能进行划分在面向

8、对象设计中成为一个错误,牛顿定律遇到了相对论),C 语言模块化程序设计需理解如下概念:(1) 模块即是一个.c 文件和一个.h 文件的结合,头文件(.h)中是对于该模块接口的声明;(2) 某模块提供给其它模块调用的外部函数及数据需在.h 中文件中冠以extern 关键字声明;(3) 模块内的函数和全局变量需在.c 文件开头冠以 static 关键字声明;(4) 永远不要在.h 文件中定义变量!定义变量和声明变量的区别在于定义会产生内存分配的操作,是汇编阶段的概念;而声明则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。如:/*module1.h*/int a = 5; /* 在

9、模块 1 的.h 文件中定义 int a */*module1 .c*/ #include “module1.h“ /* 在模块 1 中包含模块 1 的.h 文件 */*module2 .c*/ i nclude “module1.h“ /* 在模块 2 中包含模块 1 的.h 文件 */*module3 .c*/ i nclude “module1.h“ /* 在模块 3 中包含模块 1 的.h 文件 */以上程序的结果是在模块 1、2、3 中都定义了整型变量 a,a 在不同的模块中对应不同的地址单元,这个世界上从来不需要这样的程序。正确的做法是:/*module1.h*/ extern i

10、nt a; /* 在模块 1 的.h 文件中声明 int a */*module1 .c*/ i nclude “module1.h“ /* 在模块 1 中包含模块 1 的.h 文件 */ int a = 5; /* 在模块 1 的.c 文件中定义 int a */*module2 .c*/ i nclude “module1.h“ /* 在模块 2 中包含模块 1 的.h 文件 */*module3 .c*/ i nclude “module1.h“ /* 在模块 3 中包含模块 1 的.h 文件 */这样如果模块 1、2、3 操作 a 的话,对应的是同一片内存单元。一个嵌入式系统通常包括两

11、类模块:(1)硬件驱动模块,一种特定硬件对应一个模块;(2)软件功能模块,其模块的划分应满足低偶合、高内聚的要求。多任务还是单任务多任务还是单任务所谓“单任务系统“是指该系统不能支持多任务并发操作,宏观串行地执行一个任务。而多任务系统则可以宏观并行(微观上可能串行)地“同时“执行多个任务。多任务的并发执行通常依赖于一个多任务操作系统(OS),多任务 OS 的核心是系统调度器,它使用任务控制块(TCB)来管理任务调度功能。TCB 包括任务的当前状态、优先级、要等待的事件或资源、任务程序码的起始地址、初始堆栈指针等信息。调度器在任务被激活时,要用到这些信息。此外,TCB 还被用来存放任务的“上下文

12、“(context)。任务的上下文就是当一个执行中的任务被停止时,所要保存的所有信息。通常,上下文就是计算机当前的状态,也即各个寄存器的内容。当发生任务切换时,当前运行的任务的上下文被存入TCB,并将要被执行的任务的上下文从它的 TCB 中取出,放入各个寄存器中。嵌入式多任务 OS 的典型例子有 Vxworks、ucLinux 等。嵌入式 OS 并非遥不可及的神坛之物,我们可以用不到 1000 行代码实现一个针对 80186 处理器的功能最简单的 OS 内核,作者正准备进行此项工作,希望能将心得贡献给大家。究竟选择多任务还是单任务方式,依赖于软件的体系是否庞大。例如,绝大多数手机程序都是多任务

13、的,但也有一些小灵通的协议栈是单任务的,没有操作系统,它们的主程序轮流调用各个软件模块的处理程序,模拟多任务环境。单任务程序典型架构单任务程序典型架构(1)从 CPU 复位时的指定地址开始执行;(2)跳转至汇编代码 startup 处执行;(3)跳转至用户主程序 main 执行,在 main 中完成:a.初试化各硬件设备; b.初始化各软件模块;c.进入死循环(无限循环),调用各模块的处理函数用户主程序和各模块的处理函数都以 C 语言完成。用户主程序最后都进入了一个死循环,其首选方案是:while(1) 有的程序员这样写:for(;) 这个语法没有确切表达代码的含义,我们从 for(;)看不出

14、什么,只有弄 明白 for(;)在 C 语言中意味着无条件循环才明白其意。下面是几个“著名“的死循环:(1)操作系统是死循环;(2)WIN32 程序是死循环;(3)嵌入式系统软件是死循环;(4)多线程程序的线程处理函数是死循环。你可能会辩驳,大声说:“凡事都不是绝对的,2、3、4 都可以不是死循环 “。Yes,you are right,但是你得不到鲜花和掌声。实际上,这是一个没有太 大意义的牛角尖,因为这个世界从来不需要一个处理完几个消息就喊着要 OS 杀 死它的 WIN32 程序,不需要一个刚开始 RUN 就自行了断的嵌入式系统,不需要 莫名其妙启动一个做一点事就干掉自己的线程。有时候,过

15、于严谨制造的不是 便利而是麻烦。君不见,五层的 TCP/IP 协议栈超越严谨的 ISO/OSI 七层协议栈 大行其道成为事实上的标准?经常有网友讨论:printf(“%d,%d“,+i,i+); /* 输出是什么?*/ c = a+b; /* c=? */等类似问题。面对这些问题,我们只能发出由衷的感慨:世界上还有很多 有意义的事情等着我们去消化摄入的食物。实际上,嵌入式系统要运行到世界末日。中断服务程序中断服务程序中断是嵌入式系统中重要的组成部分,但是在标准 C 中不包含中断。许多 编译开发商在标准 C 上增加了对中断的支持,提供新的关键字用于标示中断服 务程序(ISR),类似于_inter

16、rupt、#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 = G

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 中学教育 > 高中教育

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号