首页 理论教育 嵌入式C语言简介及应用

嵌入式C语言简介及应用

时间:2023-08-20 理论教育 版权反馈
【摘要】:嵌入式系统软件开发中,C语言是最为常用的开发语言。使得C语言的开发基础进一步扩大和流行。2)C语言是一种功能强大的开发语言并且不失应用的灵活性。4)C语言相对来讲,简单易学,规则明确。C语言的基本操作符1)赋值操作:使用“=”符号对变量进行赋值。表7-1 C语言算术运算符3)位逻辑操作符:主要用来对变量中的位进行操作运算,见表7-2。在嵌入式系统开发中,对这两个数据类型的应用没有限制。

嵌入式C语言简介及应用

嵌入式系统软件开发中,C语言是最为常用的开发语言。过去,汇编语言曾经流行,但是随着C语言的普及使用,越来越多的开发环境支持C语言,越来越多的开发团队使用C语言。

1)C语言已经是世界汽车界普遍公认的开发语言,不但大部分主机厂、供应商应用C语言开发控制系统,大部分高校也将C或C++作为机电工程、机械工程和车辆工程等学科的主要计算机语言类课程。使得C语言的开发基础进一步扩大和流行。

2)C语言是一种功能强大的开发语言并且不失应用的灵活性。它比汇编语言简练、易懂同时又接近底层,不会出现像面向对象的开发环境中常见的层级过高而很难纠错(Debug)的现象。同时很多公司开发出来功能强大的操作系统、编译器和其他相关工具链。

3)C语言对于汽车电子控制系统软件开发,有很强的可移植性。由于拥有很好的设计规则、软件架构,C语言所编译的程序可以移植到许多微处理器上或开发环境中。

4)C语言相对来讲,简单易学,规则明确。旧的C标准(20世纪90年代)只有30多个关键字,其模块化设计思路支持复用和即插即用的理念。

5)对于动力总成特别是变速器控制系统软件开发来讲,C语言还有很好的经济性。它几乎支持变速器目前所应用的所有目标CPU。工具链完整而强大(意味着价格更合理),绝大部分变速器供应商都已经有过用C开发变速器控制软件的经验,开发成本大大降低。

(1)C语言的主要组成部分

1)主程序main():这是唯一一个所有C程序都必须有的程序入口。操作系统一般从这里开始执行。

main()978-7-111-48720-3-Chapter07-3.jpg/∗主程序的开始入口∗/978-7-111-48720-3-Chapter07-4.jpg

2)包含系统#include:将必要的内容文件在编译过程中加入到程序里。文件一般用“.h”为后缀。主要存储变量命名和库函数等。如:

#include<stdio.h>

3)对象:用于存储在内存中的内容。一般包括函数、变量、常量等。每个对象应该有唯一的名称并且和关键字有所区别。每个对象在使用前必须先定义。如:

int a;

const int b=2;

4)程序本体:实现主要功能的实体。一般来讲以行为单位,一行一般一个语句。如:

z=0x00FF;/∗赋值语句∗/

if(x<y)/∗条件语句∗/978-7-111-48720-3-Chapter07-5.jpgx=y;/∗将x值最小设为y∗/978-7-111-48720-3-Chapter07-6.jpg

5)函数定义:指用来执行某一个特定任务的一段独立代码。函数在被调用时执行。如:

int Addition(int x,int y)/∗执行加法的程序,返回两个数的和∗/978-7-111-48720-3-Chapter07-7.jpgreturn(x+y);978-7-111-48720-3-Chapter07-8.jpg

6)函数命名:函数在使用前必须先命名,这是告诉程序,有这样一个函数存在,可以被调用。如:

int Addition(int x,int y);

7)说明内容:一般来讲说明内容仅仅是起到辅助的说明作用,这部分内容会被编译器忽略。有用“//”的说明,更常用的是“/∗”开头,“∗/”结束的说明格式。

(2)C语言的基本操作符

1)赋值操作:使用“=”符号对变量进行赋值。如“A=10;”,也可以用来进行两个变量之间的赋值,如“A=B;”。在嵌入式系统中,一般多变量的相互赋值是应该避免的,如“A=B=C;”,赋值的变量间应该有同样的结构、形式、单位,否则不能直接进行赋值。

2)算术运算:C语言中有多个算术运算符,可以实现多种有效且简便的运算,见表7-1。

表7-1 C语言算术运算符

978-7-111-48720-3-Chapter07-9.jpg

3)位逻辑操作符:主要用来对变量中的位进行操作运算,见表7-2。

表7-2 C语言位逻辑操作符

978-7-111-48720-3-Chapter07-10.jpg

4)基本逻辑操作符:主要用来对变量进行基本逻辑操作,见表7-3。

表7-3 逻辑操作符及其功能

978-7-111-48720-3-Chapter07-11.jpg

进行与逻辑运算时,都需要用括号来表示前后顺序,比如,A>B&&C<D逻辑可读性不强,如果写成(A>B)&&(C<D),就可以看清是两个大小比较结果的与运算。对于逻辑或的运算,也应该应用同样的方法。

对于布尔变量的逻辑判断,许多人习惯直接用作if、while、do-while或for的逻辑判断,有时会产生逻辑不清的现象。所以,应该用显式的表达方式。比如,发动机转速高位布尔变量,逻辑表达if(EngineSpeedHigh)就不如用if(Engine SpeedHigh==CbTRUE)表达的清晰。逻辑取反操作除非万不得已应尽量避免使用,因为大部分情况都能找到正逻辑进行判断,比如if(A!=CbTRUE)就不如if(A==CbFALSE)来得更清晰。

比较双方应该有同样的数值类型,否则应该提前进行强制类型转换后再进行比较,否则比较容易出现问题。

5)地址运算:在嵌入式软件开发中,直接的地址运算”goto”“&”等应该避免。比如,A=∗(&Variable1+100);其原意是想将Variable1的地址加100后的内存中的内容赋值给A变量。由于嵌入式系统中的地址安排许多是自动调节的,所以地址的信息往往无法确认。另外,∗号的运算也应尽量避免,原因和上述类似。

6)函数调用:一般来讲,汽车电子的嵌入式系统软件的函数一般不设参数,不设返回值,如果需要对某参数进行赋值、运算,需要将其设置成全局变量,显式赋值。递归函数一般要避免应用,这种函数是自己调用自己的函数,如果边界条件没有设置好,就会出现多次调用,而一般嵌入式系统的堆栈空间有限,最后会导致堆栈溢出等问题。

7)结构体和联合体:实际应用中,有时候会出现,一组数据具有不同的数据类型,这个时候就需要考虑用结构体或联合体。结构体需要预定整个数据的内存,而联合体却只需要定义一个最大值。在嵌入式系统开发中,对这两个数据类型的应用没有限制。

8)大小操作sizeof():在嵌入式软件开发中需要避免,这是ANSI C标准库函数,但汽车电子中极少用到。

9)强制操作:一般来讲,汽车电子软件应避免使用强制操作,因为它会产生许多模糊状态,比如一个浮点变量,强制成整数进行运算,软件会将高出的位数直接砍掉,逻辑就有可能和原来设想的不同。如果必须进行,较好的做法是产生一个中间变量将其设置成强制目标类型,再进行操作。

10)条件和逗号运算:这两个运算在汽车电子中应避免,建议使用显示的逻辑。

11)预处理操作:#和##符号操作也应避免,这两种仅仅在有限的应用中使用。

12)代码操作内容应尽量分行进行:比如,在一行中写“a=b;b=c;”是要避免的,应分两行写。

代码操作一般由如下几种:标识、选择、循环、跳跃。

标识:所有的标识在汽车电子中只有case和default两种,也就是说在switch体中运用,其余的应用应该避免。

选择:主要是if和switch代码操作

978-7-111-48720-3-Chapter07-12.jpg

978-7-111-48720-3-Chapter07-13.jpg(www.zuozong.com)

循环(Loop):主要有while、do-while和for循环。对于它们的应用除了需要进行显式表达外(比如for的3个表达不应简略),其余没有太多的限制。

跳跃(Jump):主要由return,break,continue和goto组成。

对于return,汽车嵌入式系统软件一般不使用返回值,所以return在软件中主要返回void即空值。Break仅仅在switch的最后一句使用。而continue和goto一般不建议使用。

(3)声明符,分类符

汽车电控系统软件一般接受如下分类符:程序分类符、存储分类符、类型限定符。如果一段程序中含有多种分类符,应当按照上面的顺序进行声明。

1)程序分类符:主要是inline内联函数的应用。内联函数在应用中非常像宏,它不是在调用时才被触发,而是在编译时就由编译器将其内容嵌入到应用程序中。一般来讲,汽车电子的嵌入式系统软件中,内联函数是不提倡使用的。

2)存储分类符:一般由如下几个组成:typedef、extern、static、auto和register。

typedef:可以看做是宏命令。在编译前,由预处理器替代将程序中的所有遇到的相同定义词进行替代。

extern:表示其描述的变量或函数的定义在别的文件中,需要编译器到此变量和函数定义所在的外部模块中寻找。同时,要求变量或函数必须是static(静态)的。

static:静态标识符号,一般可以用来定义全局变量、局部变量和函数。如果全局变量被定义成静态的,定义的范围是整个程序,内容是不可变的(即保持上一次结果的内容)。如果定义的是局部静态变量,那么变量对于其他的文件是不可见的,但其内容可以保持上次的结果。如果定义的是函数,那么这个函数只对于这个文件是可见的,其余文件不可见。

auto:自动变量标识符,在嵌入式系统软件中不建议使用,因为变量定义默认就是auto,无须重复定义。

register:寄存器变量在嵌入式系统软件中不建议使用,因为变量定义在寄存器中,内容需要具体的物理地址指引。

3)类型限定符:主要有const和volatile两个。

const:用来限定一个变量的内容为常量而且不可更改。一般常量存放在ROM只读存储器中。

volatile:这是一个多线程访问(可修改)的限定符,对于一个系统来说,实际的意义是不产生优化,而结果是多个可变结果。比如

A=0;

A=1;

如果,A的定义不是volatile,那么,一般的编译系统就会将A=0;这个赋值语句省略掉。但是,在许多的嵌入式系统中,我们需要产生一些瞬时、多变的信号,这时就需要用到volatile定义的变量了。

4)数据类型定义符:嵌入式系统软件开发中,所有的变量,函数等都需要进行数据类型定义。有void、char、int、short、long、signed、unsigned、float、doub-le、enumeration、structure、union。

void:在C语言中表示“无类型”。在程序编写中对定义函数的参数类型、返回值、函数中指针类型进行声明,表示类型为“无”。

Char:容纳单个字符的一种基本数据类型,一般的数据类型都会显式地表明符号正负,但char是隐式表明被标识对象为正值。

int、short、long、signed、unsigned、float、double:各表示一种数据类型,数据长度见表7-4,存储值随所使用的CPU位数有所变化。

表7-4 不同处理器所能显示的数据长度

978-7-111-48720-3-Chapter07-14.jpg

IEEE规定浮点类型见表7-5。

表7-5 IEEE规定浮点类型

978-7-111-48720-3-Chapter07-15.jpg

枚举类型enumeration:枚举变量本身不是一个数据结构,它定义了从一个数据结构得到连续数据的方法,用来从含有多个元素的数据结构中得到的下一个元素。

结构体类型structure:结构体可以包含构造函数、常数、字段、方法、属性、索引器、运算符和嵌套类型。每个结构体定义后都应该有个标签,同时结构体应该有对象被定义,只有被定义的对象能够被使用。对于汽车电子控制系统,出于可移植性和控制器资源的考虑,一般不建议在结构体中使用点操作。另外,一个好的编写软件的习惯是在结构体定义中加入有效的说明。

联合体类型Union:联合体是一种特殊形式的变量。联合体变量定义与结构十分相似,但是他们的本质却不同。结构体定义了多个数据类型的集合,每个类型都是实际存在的单独个体。而联合体仅仅定义了所有可能用到的内部元素,而在使用时只会应用其中的一个,也就是说,联合体的元素共享同一个存储空间。

(4)预编译处理 嵌入式系统软件中有如下几种预编译处理。

1)条件包含预处理:有#ifdef、#ifndef、#if、#elif、#else、#endif。

#ifndef和#endif应该只在头文件中出现,其余的预处理不建议在嵌入式软件中使用。

2)文件包含预处理:文件包含#include主要用来将头文件包含在一个文件中,注意要和宏文件加以区分。在嵌入系统中文件的路径一般不需要注明(比如“/home/users/file.h”)。一些在传统C语言中广泛使用的被包含标准文件,见表7-6。

表7-6 标准头文件及其功能

978-7-111-48720-3-Chapter07-16.jpg

由于汽车嵌入式软件是相对独立的,这些头文件不会被使用。

3)宏替代预处理:嵌入系统软件的宏替代预处理主要指#define和#undef。#define一般应用于类似对象的宏处理中,而#undef一般不建议使用。

4)其余的预处理操作:像#line、#error、#pragma这些预处理操作在嵌入式系统软件开发中不建议使用。

预定义的宏应用:

__LINE__

__FILE__

__DATE__

__TIME__

__STDC__

这些预定义的宏应用应该避免使用。

(5)嵌套循环 嵌套循环是一个比较难具体规定的问题,在ANSI C中,规定嵌套不得超过15层,这和使用的硬件环境息息相关。用MFC等在PC机上运行的C语言时,一般先定义好堆栈。由于PC机的存储量和速度较高,嵌套循环的可执行层数就会很多,但许多编译器没有明确规定嵌套层级的大小,主要是因为语言开发时,应用的硬件系统差别可以很大。所以,有的规则就定义了一个较为实用的要求——15层,而且在开发时,会对系统内存有一定的要求(比如要求内存空间必须达到多少等)。因此在嵌入式系统开发中,应尽量避免多层嵌套,特别是recursive递归函数的调用(因为如果跳出条件设定不好,就会陷入死循环或堆栈溢出),应该避免。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈

相关推荐