嵌入式C语言进行曲之要诀
一. 良好的编程风格
1.排版:a. 代码缩进空格数为4 个。若是可能,尽量用空格来代替Tab键,因为有些编译器不支持Tab键(我自己至今未见过,但确实有这个风险),这给程序的移植带来了问题。
b. 较长的语句要分2行来书写,并用‘\’符号隔开。
c. 函数代码的参数过长,分多行来书写。void UARTSendAndRecv(UINT8 *ucSendBuf,
UINT8 ucSendLength,
UINT8 *ucRecvBuf,
UINT8 ucRecvLength)
d. if、do、while 、switch、for、case、default 等关键字,必须加上大括号{}。
c. 函数头应该进行注释,例如函数名称、输入参数、返回值、功能说明。
e. 复杂的宏定义同样要加上注释。
2.模块化程序设计应该理解以下概述:
(1) 模块即是一个.c 文件和一个.h 文件的结合,头文件(.h)中是对于该模块接口的声明;
一个嵌入式系统通常包括两类(注意是两类,不是两个)模块:
(1)硬件驱动模块,一种特定硬件对应一个模块;
(2)软件功能模块,其模块的划分应满足低偶合、高内聚的要求。
下面以 keil C 编译器为例,讲一下模块化编程的步骤。下面这个程序分为三层,共 7 个模块,共同为主程序服务(它们之间也会相互调用)。
程序主要模块和功能简介:
一. 底层驱动
1. 红外键盘:程序通过红外键盘进行操作。红外键盘独占定时器0 和外部中断 0 ,以实现红外解码和键盘键值的识别。红外键盘定义了五个按键,分别为上翻、下翻、左翻、右翻和确认键。
2. LCD 液晶显示:程序主要通过 LCD 显示信息,LCD 液晶显示驱动提供显示汉字、图形和 ASCII码的函数接口。可以全屏、单行显示汉字,任意位置显示ASCII码,还可以全屏、半屏显示图形。
二. 功能模块
1. LCD 菜单程序:菜单程序可以使人机交互更加方便、容易。本菜单程序的菜单级别深度受 RAM 大小的限制,每增加一级菜单将多消耗 4 字节的 RAM 。菜单程序主要完成菜单功能函数的调度,LCD 显示刷新。
2. 计算器程序:实现 65536 以内的加、减、乘、除,超出范围会出现溢出,溢出发生时,LCD 显示“错误:出现溢出”的错误提示,同时本次运算被忽略。对于负数会显示“-”号,除数为零时 LCD 显示“错误:除数为零”的错误提示。
3. 开机次数记忆程序:主要对基于 IIC 总线的 EEPROM 进行读写,单片机每次上电后,将开机次数写入 EEPROM.
4. 串口测试程序: 进入该程序后,单片机向电脑发送字符串“Hello Word!”,发送数字24(以字符的形式显示)。编写此程序的目的是为了能够方便的向电脑发送字符串和变量,便于程序的调试。串口占用串口资源,与频率测量程序共享定时器 1
5. 频率测量:复用定时器 1,占用外部中断 1,实现 5~20KHZ 频率的测量.
三. 主程序
主程序主要完成程序的初始化,LCD 菜单显示,监视键盘程序并根据键值更新菜单。
定义 红外键盘模块:key.c 与key.h
菜单模块:menu.c与menu.h
串口通信模块:uart_.c 与uart.h
计算器模块:counter.c 与counter.h
频率测量模块:mea_fre.c 与mea_fre.h
开机次数记忆模块:eepram.c 与eepram.h
1.static 关键字:在嵌入式C语言当中,它有三个作用:
作用一:在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
作用说明了局部静态变量的特性:在函数体(子),一个被声明为静态的变量(也就是局部静态变量)在这一函数被调用过程中维持其值不变(子,局部会释放为0)。声明函数的一个局部变量,并设为static 类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。
作用二:在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。(一个C项目可以分为多个模块C文件,一个C文件可以包含多个函数体)。(相同名字的变量,不会发生冲突)
作用三:在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用(静态函数)。(相同名字的函数,不会发生冲突;)
最后说一下存储说明符,在标准C语言中,存储说明符有以下几类:
auto、register 、extern 和static 对应两种存储期:自动存储期和静态存储期。
auto和register 对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。
关键字extern 和static 用来说明具有静态存储期的变量和函数。用static 声明的局部变量具有静态存储持续期(static storage duration),或静态范围(static extent)。虽然他的值在函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内。静态局部对象在程序执行到该对象的声明处时被首次初始化。
2. const 关键字
a. const关键字修饰的变量可以认为有只读属性,但它绝不与常量划等号。const int i=5;
int j=0;
i= j; // 非法,导致编译错误,因为只能被读
j=i; // 合法
b. const关键字修饰的变量在声明时必须进行初始化。如下代码:
const int i=5; // 合法
const int j; // 非法,导致编译错误
c. 用const声明的变量虽然增加了分配空间,但是可以保证类型安全。
d. C 标准中,const定义的常量是全局的。
e. 必须明白下面语句的含义,方法是:若是想定义一个只读属性的指针,那么关键字const要放到‘* ’后面。
char *const cp; //指针不可改变,但指向的内容可以改变
char const *pc1; // 指针可以改变,但指向的内容不能改变
const char *pc2; // 同上(后两个声明是等同的)
f. 将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。
参数const通常用于参数为指针或引用的情况,且只能修饰输入参数;若输入参数采用“值传递”方式,由于函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,所以不用const修饰。例子:
void fun0(const int * a );
void fun1(const int & a);
调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,如形参为const int * a,则不能对传递进来的指针所指向的内容进行改变,保护了原指针所指向的内容;如形参为const int & a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。
g. 修饰函数返回值,可以阻止用户修改返回值。(在嵌入式C 中一般不用,主要用于C++ )
h. const消除了预处理器的值替代的不良影响,并且提供了良好的类型检查形式和安全性,在可能的地方尽可能的使用const对我们的编程有很大的帮助,前提是:你对const有了足够的理解。
最后,举两个常用的标准C 库函数声明,它们都是使用const的典范。
1. 字符串拷贝函数:char *strcpy(char *strDest,const char *strSrc );
2. 返回字符串长度函数:int strlen (const char *str);