嵌入式C语言编程要点
模块划分
(1) 模块即是一个.c文件和一个.h文件的结合,头文件是对于模块接口的声明。
(2) 某模块提供给其它模块调用的外部函数及数据需要在头文件中以extern声明。
(3) 模块内的函数和全局变量需要冠以static关键字声明。
(4) 永远不要在头文件中定义变量。定义变量和变量声明的区别在于“定义”会产生内存分配的操作,是汇编阶段的概念;而“声明”则只是告诉包含该声明的模块在连接阶段从其它模块寻找外部函数和变量。
(5) 防止重定义,使用“#ifndef - #define - #endif”结构。
单任务程序典型架构
(1) 从CPU复位时的指定地址开始执行。
(2) 跳转至汇编代码的startup处执行。
(3) 跳转至用户主程序main执行并完成:硬件设备和软件模块的初始化,进入无限循环以调用各模块的处理函数。
中断服务程序
中断是嵌入式系统中重要的组成部分,但是在标准C中不包含中断,许多编译器开发商在标准C中增加对于中断的支持,提供新的关键字用于中断服务程序(ISR),类似于__interrupt等。当一个函数被定义为ISR时,编译器会自动为该函数增加中断服务程序所需要的中断现场保护的入栈和出栈代码。
中断服务程序需要满足如下要求:
(1) 不能返回值
(2) 不能传递参数
(3) ISR应该尽量短小
(4) printf函数会带来重入和性能问题,不能采用
硬件驱动模块
硬件初始化过程:
(1) 修改寄存器,设置硬件参数
(2) 将ISR入口地址写入中断向量表(vector table)
(3) 设置CPU针对该硬件的控制线,如果控制线可用于PIO(可编程I/O)和控制信号用,则设置CPU内部对应寄存器使其作为控制信号;设置CPU内部的针对该设备的中断屏蔽位,设置中断方式(电平触发或边缘触发)
(4) 提供一系列针对该设备的操作接口函数
C的面向对象化
在面向对象语言里,出现了“类”的概念。类是对特定数据的特定操作的集合体。类包含两个范畴:数据和行为。而C语言中的struct仅仅是数据的集合,我们可以利用函数指针将struct模拟为一个包含数据和操作的“类”。
数据指针
在使用绝对地址指针时,要注意指针自增自减的结果取决于指针指向的数据类型。p++或++p的结果等同于p = p + sizeof(int)。
int
*
p
=
(
int
*
)
0x0000FFFF
;
CPU以字节为单位编址,而C语言指针以指向的数据类型长度作自增和自减。
函数指针
(1) 调用函数实际上等同于“跳转指令+参数传递处理+回归位置入栈”,本质上最核心的操作是将函数生成的目标代码的首地址赋给CPU的pc寄存器。
(3) 函数无他,唯指令集合耳。
数组和动态申请内存
(1) 尽可能选用数组,因为数组不能越界访问。
(2) 如果使用动态申请内存,则申请后一定要判断是否申请成功,并且malloc与free应成对出现。
关键字const
const
int
a;
//
该变量的值不能改变
int
const
a;
//
该变量的值不能改变
const
int
*
a;
//
该指针指向的内存区域的值不能改变
int
*
const
a;
//
该指针的值不能改变
int
const
*
a
const
;
//
该指针的值与指向的内存区域的值都不能改变
(1) 为阅读你的代码的人传达非常有用的信息。
(2) 使编译器很自然地去保护那些不希望被改变的参数,可以减少bug的出现。
关键字volatile
这样优化的结果很可能导致错误,因为在第一次读取I/O空间0x100端口的值后,很可能被其它程序写入新值,所以第二次读取的值和第一次的值就会不一样。而优化后,两次读取的值就一样了。在变量a定义时加上volatile关键字可以防止编译器的优化。
volatile变量一般使用于如下情况:
(1) 并行设备的硬件寄存器(如:状态寄存器)
(2) 一个中断服务程序中会访问到的非自动变量(也就是全局变量)
(3) 多线程应用中被几个任务共享的变量
CPU字长于存储器位宽
为什么在上例中偏移要乘以2?
因为16位的CPU与8位的NVRAM之间互联只能以地址线A1对其A0,CPU本身的A0与NVRAM不连接,因此NVRAM的地址只能是偶数地址,故每次以0x10为单位。
寄存器变量
当对一个变量频繁读写时,需要反复访问内存,从而花费大量存取时间。为此,C语言提供了一种变量,即寄存器变量。这种变量存放在CPU的寄存器中,使用时,不需要访问内存,而直接从寄存器中读写,从而提高效率。寄存器变量的说明符为register,循环计数是应用寄存器变量的最好候选者。
只有局部自动变量和形参才能定义成寄存器变量。因为寄存器变量属于动态储存方式,凡采用静态存储方式的变量都不能定义为寄存器变量,包括:模块间全局变量、模块内全局变量、局部static变量。
register是一个“建议型”关键字,表示程序建议该变量放于寄存器中,但最终该变量可能因为条件不满足而未能成为寄存器变量,被放置于存储器中,这个情况下,编译器并不会报错(在C++语言中,还有一“建议型”关键字,inline)。
内嵌汇编
程序中对时间要求苛刻的部分可以用内嵌汇编来重写,以带来速度上的显著提高。但是,开发和测试汇编代码是一件艰苦的工作,它将花费更长的时间,因而要慎重选择汇编的部分。
在程序中,存在一个80-20原则,即20%的程序消耗了80%的时间,因而我们要改进效率,最主要是考虑改进那20%的部分。
利用硬件特性
首先要明白CPU对各种存储器的访问速度,基本上是:
CPU内部RAM > 外部同步RAM > 外部异步RAM > FLASH/ROM
对于程序代码,已经被烧录在FLASH或ROM中,我们可以让CPU直接从中读取代码执行,但通常不是一个好方法,我们最好在系统启动时将FLASH或ROM中的目标代码复制到RAM中再执行以提高取指令的速度。
对于UART等设备,其内部有一定容量的接受BUFFER,我们应尽量在BUFFER被占满后再向CPU提出中断。
对某设备能采取DMA方式,就应该采用DMA方式。DMA方式在读取目标中包含的存储信息较大时效率较高,其数据传输基本单位是块,而所传输的数据是从设备直接送入内存。DMA方式比起中断驱动方式,减少了CPU对外设的干预,进一步提高了CPU与外设间的并行处理。
位操作
对于以2的指数次方为“*”、“/”或“%”因子的数学运算,转化为移位运算通常可以提高算法的效率,因为乘除运算指令周期通常比移位运算要大。