大家好,上次和大家介绍了单片机的开发流程。也就是大家可以根据单片机的开发流程进行单片机程序的烧写,但这仅限于已经写好的程序(假设对C语言还没有了解),接下来这章我们开始介绍单片机中C语言的编写。
一、关键字介绍
既然是编程语言那么就得有自己的规则,其实单片机C语言和标准C语言区别并不是很大,毕竟是利用的标准C语言扩展来的。既然说到规则定义,那我们从单片机C语言的关键字说起。
ANSIC的关键字有32个,其中包括9种控制语句,如下:
auto、const、signed、unsigned、static、extern、
char、long、short、int、float、double、void、volatile、register、union、struct、enum、
sizeof、typedef、
if、while、break、case、continue、defualt、do、else、for、goto、return、switch
单片机中除了上面的关键字还有自己的变量:
bit:定义为变量的关键字;
sbit:定义特殊功能寄存器的位变量;
sfr:定义特殊功能寄存器变量;
sfr16:16位特殊功能寄存器变量定义;
除此之外还有单片机特有的存储器类型:
data:直接访问内部数据数据存储器,访问速度最快;
bdata:可位寻址内部数据存储器,允许位与字节混合访问;
idata:间接访问内部数据存储器,允许访问全部内存地址;
pdata:分页访问外部数据存储器,等效汇编movx @Ri访问;
xdata:外部数据存储器访问,等同汇编的movx @DPTR;
code:程序存储器访问,等同汇编的movc @A+DPTR;
二、命名规则介绍
所谓的变量就是对存储空间的区域进行命名,其内存区域存储的值可以改变。变量定义必须放在变量使用之前,一般放在函数体的开头部分。要区分变量名和变量值是两个不同的概念,其对应关系如下:
以下未命名的规则:
1. 标识符由字母(A-Z,a-z)、数字(0-9)、下划线“_”组成,并且首字符不能是数字,但可以是字母或者下划线。例如,正确的标识符:abc,a1,prog_to。
2. 不能把C语言关键字作为用户标识符,例如if ,for, while等.
3. 标识符长度是由机器上的编译系统决定的,一般的限制为8字符(注:8字符长度限制是C89标准,C99标准已经扩充长度,其实大部分工业标准都更长)。
4. 标识符对大小写敏感,即严格区分大小写。一般对变量名用小写,符号常量命名用大写。
5. 标识符命名应做到“见名知意”,例如,长度(length)圆周率(pi)等等。
三、变量定义
有了关键字和标识符的规则就可以定义变量了,一般定义变量的表达式如下:
[存储种类] 数据类型 [存储器类型] 变量名表
可以看到在定义格式中除了数据类型和变量名表是必要的,其它都是可选项。
存储种类有四种:自动(auto),外部(extern),静态(static)和寄存器(register),缺省类型为自动(auto)。
数据类型就对应前边介绍的unsigned/signed char、int 、bit、double等数据类型;
而存储器类型就是前面介绍的data、idata、pdata、code等存储器类型,值得注意的是在keil软件中如果忽略存储器类型编译器会根据选择的编译模式SMALL,COMPACT和LARGE去指定变量的存储区域;这三种模式选择可以在右键targetàoption选项中去选择,如下图:
SMALL存储模式把所有函数变量和局部数据段放在8051系统的内部数据存储区这使访问数据非常快,但SMALL存储模式的地址空间受限。在写小型的应用程序时,变量和数据放在data内部数据存储器中是很好的因为访问速度快,但在较大的应用程序中data区最好只存放小的变量、数据或常用的变量(如循环计数、数据索引),而大的数据则放置在别的存储区域。
COMPACT存储模式中所有的函数和程序变量和局部数据段定位在8051系统的外部数据存储区。外部数据存储区可有最多256字节(一页),在本模式中外部数据存储区的短地址用@R0/R1。
LARGE存储模式所有函数和过程的变量和局部数据段都定位在8051系统的外部数据区外部数据区最多可有64KB,这要求用DPTR数据指针访问数据。
四、几种常用变量的定义方法示例
? 常量的定义
常量可以作为一种特殊的变量存在于内存中,只是在程序执行过程中其数值不发生变化,因此通常将称量定义在程序存储器中(有时候有很多常量定义成表格,固化到程序存储器)。
常量的定义有直接定义常量和用预编译的命令来代替的符号常量:
直接定义的常量有直接出现在程序中的数据,比如“2”“a”等。另一种是定义常量比如:const char code a =10;在程序存储器空间定义一个常量a,其值为10,在以后的程序中a的值不能发生变化。
还有一种是利用宏定义的方法来实现,比如:#define Pi 3.14 定义Pi的值为3.14,这样后边程序引用Pi的时候都会被3.14取代,这样的好处是可以方便调试。
? 变量的定义
变量根据作用域可以分为局域变量和全局变量,根据其有无static修饰可以分为静态变量和动态变量,因此这两种组合可以分成四个变量:动态全局变量,静态全局变量,动态局部变量,静态局部变量,其作用域以及生命周期的对比如下表格所示:
动态全局 变量 | 作用域为整个项目,即最终编译成可执行文件的所有文件中均可以使用动态全局变量。生命周期为从程序运行到程序退出,即贯穿整个运行时间。无显式初始化时默认初始化值为0。 |
静态全局变量 | 作用域为当前文件,从定义/声明位置到文件结尾。生命周期为从程序运行到程序退出,即贯穿整个运行时间。无显式初始化时默认初始化值为0 |
动态局部变量 | 作用域为当前函数,从定义位置,到其所在的{}的结束位置。生命周期为从函数调用到函数退出。无显式初始化时默认初始化值为随机值。 |
静态局部变量 | 作用域为当前函数,从定义位置,到其所在的{}的结束位置。生命周期为从程序运行到程序退出,即贯穿整个运行时间,当下次函数调用时,静态局部变量不会被再次初始化,而是沿用上次函数退出时的值。无显式初始化时默认初始化值为0。 |
对于初学者来说开始可能对这些变量的作用域和生命周期很难理解,是在理解不了就先记着,在我们介绍完函数后再回头看这些东西就有一个比较深刻的认识。
下面就主要举例介绍各个不同的变量以及应用,首先是对于单片机中常用的变量进行定义:
1. Bit定义位变量
bit位变量有点类似于高级语言中的boolean变量,其值只能取0和1。在单片机中经常用作标志位,比如:bit flag;定义一个flag标志位
2. sbit 定义可寻址变量
sbit是定义可寻址对象,定义特殊寄存器的某位。在实际中经常来定义端口的位的。其定义数据的方法:(1) sbit 位变量名=地址;(2) sbit 位变量名=特殊寄存器^位位置;(3) sbit 寄存器字节地址^位位置;比如:
sbit LED=P1^1//表示P1端口的第一位定义为LED;其中P1是特殊寄存器的名称(实际使用sfr关键字定义的)。sbit LED=0x91//0x91的位地址就是P1^1;sbit LED=0x90^1//0x90是P1寄存器的字节地址,0x90^1表示P1的第一位,上面的三个定义是一样的,只是表达方式不同,如果用地址来直接定义那么首先对单片机非常了解,而直接用寄存器的名称去定义的话就得包含单片机的头文件。
3. sfr定义特殊寄存器变量
sfr来定义8位寄存器变量,一般在头文件中经常看到这种定义方式,比如我们打开reg51.h的头文件,我看可以看到如下的定义方式:
sfr P0 = 0x80;
sfr P1 = 0x90;
sfr P2 = 0xA0;
sfr P3 = 0xB0;
其中P0、P1、P1、P3是寄存器变量的名称,而后面的一串数字是这个寄存器的地址,因此我们在编写程序的时候直接写P1、P2等,但是如果在编写程序的时候没有包含头文件直接写P1、P2等就会出现报错的信息,当然如果要将报错的信息解除的话要么包含头文件,要么自己去重新定义这些变量。当然有些时候也会看到sfr16,这个是用来定义长度为16位的寄存器变量的。
4. volatile修饰变量
volatile修饰的变量表示是随时可以发生变化的,因此当编译器用到这个值的时候会重新去调用其数值,而不是使用保存在寄存器的备份。Volatile一般来修饰寄存器变量,因为寄存器里面存数的数据会经常发生变化。Volatile只是一个修饰关键字因此可以用数据类型放在前边修饰比如:const volatile a;表示a是一个volatile类型的常量,说是常量是因为在程序运行中程序不能更改这个数值,但是它可能会意想不到的发生改变。在头文件中常来定义寄存器变量,比如:
#define PWMC (*(unsigned int volatile xdata *)0xfff0)
#define PWMCH (*(unsigned char volatile xdata *)0xfff0)
#define PWMCL (*(unsigned char volatile xdata *)0xfff1)
#define PWMCKS (*(unsigned char volatile xdata *)0xfff2)
通过指针来定义寄存器变量。
5. union定义变量
union定义联合体变量,他和后面要讲述的struct不一样的地方在于,union变量在内存的长度是由联合体内成员变量中长度最长的变量来决定的,他们共同占用一块内存。然而struct变量是struct中变量长度的总和。比如:union ADC{unsigned intADC_VAL;unsigned char AD[2]}其中DAC_VAL是两个自字节长度的变量,而AD[2]是单字节变量的数组,他们的排列如下图所示:
ADC_VAL | MSB | LSB | ||||||||||||||
AD | AD[0] | AD[1] |
可以看到ADC_VAL的长度和AD[]数组的长度一样,因此可以认为ADC_VAL是用两个8位的数据合成的一个无符号的int数据,因此可以直接调用这个数据即可。
6. struct 定义的变量
Struct定义变量可以有很多的成员,而这些成员的地址是连续的,因此这在一些定义库函数的时候有个很大的方便只要得到结构体的首地址就可以得到一系列的寄存器的地址,这样方便管理。具体的示例在以后可以讲解(在STM32库函数中可以找到)。
在这主要介绍一些常用的变量,对于int,char,等变量的定义和初始化和标准的C语言都相同,最容易出错的是定义变量的类型和采集的数据之间存在不一致,这样调试的时候编译器不会报错,这就需要我们在编程的时候认真。
总结一下:
本次课程的主要内容是,介绍了C语言的关键字以及变量标识符的规则,同时着重介绍了单片机中C语言的变量和定义的问题,最后通过举例说明了具有典型意义的变量的定义。