关于c:C语言在嵌入式系统编程时的注意事项

27次阅读

共计 5079 个字符,预计需要花费 13 分钟才能阅读完成。

C 语言是一门通用计算机编程语言,利用宽泛。C 语言的设计指标是提供一种能以繁难的形式编译、解决低级存储器、产生大量的机器码以及不须要任何运行环境反对便能运行的编程语言。

只管 C 语言提供了许多低级解决的性能,但依然放弃着良好跨平台的个性,以一个规范规格写出的 C 语言程序可在许多电脑平台上进行编译,甚至蕴含一些嵌入式处理器(单片机或称 MCU)以及超级电脑等作业平台。

20 世纪 80 年代,为了防止各开发厂商用的 C 语言语法产生差别,由美国国家标准局为 C 语言订定了一套残缺的国际标准语法,称为 ANSI C,作为 C 语言最后的规范。

C 语言嵌入式零碎编程注意事项
不同于个别模式的软件编程,嵌入式零碎编程建设在特定的硬件平台上,势必要求其编程语言具备较强的硬件间接操作能力。无疑,汇编语言具备这样的特质。然而,归因于汇编语言开发过程的复杂性,它并不是嵌入式零碎开发的个别抉择。而与之相比,C 语言 – 一种“高级的低级”语言,则成为嵌入式零碎开发的最佳抉择。笔者在嵌入式零碎我的项目的开发过程中,一次又一次感触到 C 语言的精妙,沉醉于 C 语言给嵌入式开发带来的便当。

大多数嵌入式零碎的硬件平台。它包含两局部:

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

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

本文的探讨次要围绕以通用处理器为核心的协定解决模块进行,因为它更多地牵涉到具体的 C 语言编程技巧。而 DSP 编程则重点关注具体的数字信号处理算法,次要波及通信畛域的常识,不是本文的探讨重点。

着眼于探讨广泛的嵌入式零碎 C 编程技巧,零碎的协定解决模块没有抉择特地的 CPU,嵌入式零碎学习加意义气呜呜吧久林就易,而是抉择了家喻户晓的 CPU 芯片 –80186,每一位学习过《微机原理》的读者都应该对此芯片有一个根本的意识,且对其指令集比拟相熟。80186 的字长是 16 位,能够寻址到的内存空间为 1MB,只有实地址模式。C 语言编译生成的指针为 32 位(双字),高 16 位为段地址,低 16 位为段内编译,一段最多 64KB。

协定解决模块中的 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 串口)或以太网口下载到指标机上运行。其驻留于指标机 FLASH 存储器中的 monitor 程序能够监控宿主机 Windows 调试平台上的用户调试指令,获取 CPU 寄存器的值及指标机存储空间、I/ O 空间的内容。

后续章节将从软件架构、内存操作、屏幕操作、键盘操作、性能优化等多方面论述 C 语言嵌入式零碎的编程技巧。软件架构是一个宏观概念,与具体硬件的分割不大;内存操作次要波及零碎中的 FLASH、RAM 和 NVRAM 芯片;屏幕操作则波及显示控制器和实时钟;键盘操作次要波及键盘控制器;性能优化则给出一些具体的减小程序工夫、空间耗费的技巧。

在咱们的修炼旅途中将通过 25 个关口,这些关口主分为两类,一类是技巧型,有很强的适用性;一类则是常识型,在实践上有些意义。

So,let’s go.

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/

#i nclude“module1.h”/ 在模块 2 中蕴含模块 1 的.h 文件 /

/module3 .c/

#i nclude“module1.h”/ 在模块 3 中蕴含模块 1 的.h 文件 /

以上程序的后果是在模块 1、2、3 中都定义了整型变量 a,a 在不同的模块中对应不同的地址单元,这个世界上从来不须要这样的程序。正确的做法是:

/module1.h/

extern int 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 的话,对应的是同一片内存单元。

一个嵌入式零碎通常包含两类模块:

(1)硬件驱动模块,一种特定硬件对应一个模块;

(2)软件功能模块,其模块的划分应满足低偶合、高内聚的要求。

多任务还是单任务

所谓“单任务零碎”是指该零碎不能反对多任务并发操作,宏观串行地执行一个工作。而多任务零碎则能够宏观并行(宏观上可能串行)地“同时”执行多个工作。

多任务的并发执行通常依赖于一个多任务操作系统(OS),多任务 OS 的外围是系统调度器,它应用工作管制块(TCB)来治理任务调度性能。TCB 包含工作的以后状态、优先级、要期待的事件或资源、工作程序码的起始地址、初始堆栈指针等信息。调度器在工作被激活时,要用到这些信息。此外,TCB 还被用来寄存工作的“上下文”(context)。工作的上下文就是当一个执行中的工作被进行时,所要保留的所有信息。通常,上下文就是计算机以后的状态,也即各个寄存器的内容。当产生工作切换时,以后运行的工作的上下文被存入 TCB,并将要被执行的工作的上下文从它的 TCB 中取出,放入各个寄存器中。

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

到底抉择多任务还是单任务形式,依赖于软件的体系是否宏大。例如,绝大多数手机程序都是多任务的,但也有一些小灵通的协定栈是单任务的,没有操作系统,它们的主程序轮流调用各个软件模块的处理程序,模仿多任务环境。

单任务程序典型架构

(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=?/

等相似问题。面对这些问题,咱们只能收回由衷的感叹:世界上还有很多有意义的事件等着咱们去消化摄入的食物。

实际上,嵌入式零碎要运行到世界末日。

中断服务程序

中断是嵌入式零碎中重要的组成部分,然而在规范 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;

}

}

}

按上述办法设计的中断服务程序很小,理论的工作都交由主程序执行了。

模块划分的“划”是布局的意思,意指怎么正当的将一个很大的软件划分为一系列性能独立的局部单干实现零碎的需要。

正文完
 0