C语言是国际上广泛流行的计算机高级语言,它是一种源于编写UNIX操作系统的结构化语言,程序有清晰的层次结构。
1.C语言程序结构
AVR单片机的C语言程序结构与一般C语言有很大的区别,每个AVR单片机的C语言程序至少有一个main()函数(即主函数)且只能有一个,它是C语言程序的基础,是程序代码执行的起点,而其他函数都是通过main()函数直接或间接调用的。
〖AVR单片机的C语言程序结构的特点〗
☺ 一个C语言源程序由一个或多个源文件组成,主要包括一些C源文件(即后缀名为“.C”的文件)和头文件(即后缀名为“.h”的文件)。对于一些支持C语言与汇编语言混合编程的编译器而言,它还可包括一些汇编源程序(即后缀名为“.asm”的文件)。
☺ 每个源文件至少包含一个 main()函数,也可以包含一个 main()函数和其他多个函数。头文件中声明一些函数、变量或预定义一些特定值,而函数是在C源文件中实现的。
☺ 一个 C 语言程序总是从 main()函数开始执行的,而不论 main()函数在整个程序中的位置如何。
☺ 源程序中可以有预处理命令(如 include 命令),这些命令通常放在源文件或源程序的最前面。
☺ 每个声明或语句都以分号结尾,但预处理命令、函数头和花括号“{}”后不能加分号。
☺ 标志符、关键字之间必须加一个空格以示间隔。若已有明显的间隔符,也可不再加空格来间隔。
☺ 源程序中所用到的变量都必须先声明,然后才能使用,否则编译时会报错。
C源程序的书写格式自由度较高,灵活性很强,有较大的任意性,但这并不表示C源程序可以随意乱写。为了书写清晰,并便于阅读、理解、维护,在书写程序时最好遵循以下规则。
☺ 通常情况下,一个声明或一个语句占用一行。在语句的后面可适量添加一些注释,以增强程序的可读性。
☺ 不同结构层次的语句从不同的起始位置开始,即在同一结构层次中的语句,应缩进同样的字数。
☺ 用“{}”括起来的部分表示程序的某一层次结构。“{}”通常写在层次结构语句第 1个字母的下方,与结构化语句对齐,并占用一行。
在此以下面的程序为例,进一步说明AVR单片机C语言的程序结构特点及书写规则。程序清单如下所示。
这个小程序的作用是让接在ATmega16的PC0端口上的LED进行秒闪显示。下面分析这个C程序源代码。
第1行至第5行为注释部分。传统的注释定界符使用斜杠-星号(即“/*”)和星号-斜杠(即“*/”)。斜杠-星号用于注释的开始。编译器一旦遇到斜杠-星号(即“/*”),就忽略后面的文本(即使是多行文本),直到遇到星号-斜杠(即“*/”)为止。简而言之,在此程序中,第1行至第5行的内容不参与编译。在程序中还可使用双斜杠(即“//”)来作为注释定界符。若使用双斜杠(即“//”),则编译器会忽略该行语句中双斜杠(即“//”)后面的一些文本。
第6行和第7行分别是两条不同的预处理命令。在程序中,凡是以“#”开头的均表示这是一条预处理命令语句。第6行为文件包含预处理命令,其意义是把双引号(即“”)或尖括号(<>)内指定的文件包含到本程序中,成为本程序的一部分。第7行为宏定义预处理命令语句,表示uint为无符号整数类型。被包含的文件通常是由系统提供的,也可以由程序员自己编写,其后缀名为“.h”。C语言的头文件中包括了各个标准库函数的函数原型。因此,在程序中调用一个库函数时,都必须包含函数原型所在的头文件。在AVR单片机中,不同的C语言编译器,其头文件也不尽相同。此例中,由于使用的是CVAVR编译器,所以头文件是“mega16.h”,这个文件里定义了ATmega16的各个寄存器。若使用ICCAVR编译器,则其头文件应为“iom16v.h”。
第8行定义了一个延时函数,其函数名为“delay”,函数的参数为“uint k”。该函数采用了两个层次结构和两重循环语句。第9行至第15行表示外部层次结构,其中第9行表示延时函数从此处开始执行,第15行表示延时函数的结束。第12行至第14行表示内部层次;第10行至第14行为数据说明和执行语句部分。第11行为外循环部分;第13行为内循环部分。
第16行定义了main主函数,函数的参数为“void”,意思是函数的参数为空,即不用传递给函数参数,函数即可运行。同样,该函数也采用了两个层次结构,第17行至第24行为外部层次结构;第20行至第23行为内部层次结构。
2.标志符与关键字
C语言的标志符用于标志源程序中变量、函数、标号和各种用户定义的对象的名字。CVAVR C中的标志符只能由字母(A~Z、a~z)、数字(0~9)、下画线(_)组成。其中第1个字符必须是字母或下画线,随后只能取字母、数字或下画线。标志符区分大小写,其长度不能超过32个字符。
注意:
标志符中不能使用中文。
关键字是由C语言规定的具有特定意义的特殊标志符,有时又称为保留字。关键字应当以小写形式输入。在编写C语言源程序时,用户定义的标志符不能与关键字相同。表3-1列出了CVAVR C中的一些关键字。
表3-1 CVAVR C中的一些关键字
3.数据类型
具有一定格式的数字或数值称为数据,数据是计算机操作的对象。数据的不同格式称为数据类型。
CVAVR C支持的数据类型有位变量型(bit)、字符型(char)、无符号字符型(unsigned char)、有符号字符型(signed char)、整型(int)、短整型(short int)、无符号整型(unsigned int)、有符号整型(signed int)、长整型(long int)、无符号长整型(unsigned long int)、有符号长整型(signed long int)、单精度浮点型(float)、双精度浮点型(double)等,如图3-1所示。
基本类型就是使用频率最高的数据类型,其值不可以再分解为其他类型。CVAVR C基本数据类型的长度和范围见表3-2。若在CVAVR编译器中执行菜单命令“Project”→“Configure”,并在“Configure Project”对话框的“Code Generation”选项卡中将“char is unsigned”复选框选中,或者在源程序中使用了“#pragma uchar+”指令时,则char的范围为0~255。
图3-1 CVAVR C支持的数据类型
表3-2 CVAVR C基本数据类型的长度和范围
续表
说明:
在CVAVR C中,若一个表达式中有两个操作数的类型不同,则编译器会自动按以下原则将其转换为同一类型的数据。
(1)如果两个数有一个为浮点型(即单精度或双精度浮点型),则另一个操作数将转换成浮点型。
(2)如果两个数有一个是长整型或无符号长整型,则另一个操作数将转换成相同的类型。
(3)如果两个数有一个是整型或无符号整型,则另一个操作数将转换成相同的类型。
(4)字符型和无符号字符型的优先级最低。
4.常量、变量及存储空间
1)常量 所谓常量就是指在程序运行过程中,其值不能改变的数据。根据数据类型的不同,常量可分为整型常量、字符常量和实数型常量等。
整型常量可以用二进制、八进制、十进制和十六进制数进行表示。表示二进制数时,应在数字的前面加上“0b”的标志,其数码取值只能是“0”和“1”,如“0b10110010”表示二进制的“10110010”,其值为十进制数的1×2 7 +1×2 5 +1×2 4 +1×2 1 =178;表示八进制数时,在数字的前面应加上“O”的标志,其数码取值只能是“0~7”,如“O517”表示八进制的“517”,其值为十进制数的5×8 2 +1×8 1 +7×8 0 =335;表示十六进制时,应在数字的前面加上“0x“或“0X”的标志,其数码取值是数字“0~9”、字母“a~f”或字母“A~F”,如“0x3a”和“0X3A”均表示相同的十六进制数值,其值为十进制数的3×16 1 +10×16 0 =58。
无符号整数常量应在一个数字后面加上“u”或“U”,如6325U;长整型整数常量应在一个数字后面加上“l”或“L”,如97L。无符号长整型整数常量应在一个数字后面加上“ul”或“UL”,如25UL;实数型常量应在一个数字后面加上“f”或“F”,如3.146F。字符常量是用单引号将字符括起来表示的,如‘a’;字符串常量是用引号将字符括起来表示的,如“CVAVR”。
如果将一个字符串作为一个函数的参数来引用,则这个字符串将自动被当做常量来使用,并且会被放在Flash中。例如:
注意:
①常量可以被定义成数组,最多为8维;②由于常量存在Flash中,所以必须使用关键词flash或const;③ 常量表达式在编译时会自动求解;④常量可以在函数内部声明。
2)变量 所谓变量就是在程序运行过程中,其值可以改变的数据。
【局部变量与全局变量】 根据实际程序的需求,变量可被声明为局部变量或全局变量。
局部变量是在创建函数时由函数分配的存储器空间,这些变量只能在所声明的函数内使用,而不能被其他的函数访问。但是在多个函数中可以声明变量名相同的局部变量,而不会引起冲突,这是因为编译器会将这些变量视为每个函数的一部分。
全局变量是由编译器分配的存储器空间,可被程序内所有的函数访问。全局变量能够被任何函数修改,并且会保持全局变量的值,以便由其他函数使用。
如果未对全局变量或静态局部变量赋初值,则相当于给该变量赋初值0,因为CVAVR在程序开始时会对全部数据内存清零。在CVAVR中,若没有对一个局部变量赋初值,则该变量的值是不确定的。
定义局部变量与全局变量的语法格式如下:
变量也可以被定义成数组,且最多为8维,第一个数组元素编号为0。如果全局变量数组没有赋初值,则在程序开始时会被自动赋值为0。例如:
对一些需要被不同的函数调用,并且必须保存其值的局部变量而言,最好将这些局部变量声明为静态变量(static)。如果静态变量没有赋初值,在程序开始时会被自动赋值为0。例如:
如果变量在其他的文件中声明,则必须使用关键字extern。例如:
如果某个变量需占用寄存器,则必须使用register关键词告诉编译器分配寄存器给该变量。例如:
为了防止把一个变量分配到寄存器,必须使用volatile关键词,并且通知编译器这个变量的赋值受外部变化的支配。所有未被分配到寄存器的全局变量存放在SRAM的全局变量区;所有未被分配到寄存器的局部变量动态地存放在SRAM的数据堆栈区。例如:
【指定全局变量保存在SRAM的地址单元】 在程序中可以使用“@”操作符指定全局变量保存在SRAM的地址单元。例如:
【位变量】 位变量是存储在寄存器R2~R15的特殊全局变量,用关键词bit声明。其语法格式如下:
根据声明的顺序,位变量的分配从寄存器R2的bit0开始,按升序排列。由于R2~R15中每个寄存器均有8位,所以最多可声明112个位变量。
若在CVAVR编译器中执行菜单命令“Project”→“Configure”,并在“Configure Project”对话框的“Code Generation”选项卡中选中“bit Variables Size”选项,即可给位变量分配寄存器空间。为了给其他的全局变量分配寄存器,给位变量分配的空间要尽可能小一些。
如果位变量没有赋初值,则在程序开始时会自动赋值为0。在表达式赋值中,位变量会自动转变为无符号字符型。
【给变量分配寄存器】 为了给其他的全局变量分配寄存器,给位变量分配的空间要尽可能小。若在CVAVR编译器中执行菜单命令“Project”→“Configure”,并在“Configure Project”对话框的“Code Generation”选项卡中选择“Automatic Global Register Allocation”复选框,或者在程序中使用#pragma regalloc+编译指示,则CVAVR编译器会自动将位变量未使用完所剩下的R2~R15中相关的寄存器分配给字符型和整形全局变量,直到 R15 也分配完为止。
如果自动寄存器分配被禁止,则可以用关键字“register”指定将某个全局变量分配到相应寄存器中。例如:
3)存储空间 AVR单片机采用Harvard(哈佛)结构,CPU中拥有3个独立的空间,即Flash存储器、数据存储器和E 2 PROM存储器。由于C语言是针对冯·诺依曼结构的处理器开发的,并不适合描述单片机这3种不同的存储空间,所以AVR的C编译器均对此做了相应的扩充。CVAVR编译器对每个空间都做了相应的扩充,并引入了flash和eeprom两个关键词。
【Flash存储器】 Flash存储器单元的首地址为0x0000,其容量由AVR单片机的型号决定,ATmega16的Flash存储空间为16KB。Flash存储器是非易失性存储器,当电源被切断后,保存在其中的数据也不会丢失。
CVAVR编译器对字符串的处理方式与ICCAVR相同,即用户没有指定只保存在Flash中的字符串,在启动时将字符串从Flash存储器区复制到数据存储区。例如:
CVAVR编译器最多支持8个元素的字符串数组,若字符串数组超过8个元素,CVAVR编译器将会报错。
注意:
这个限制仅针对字符串数组而言,对普通数组没有这个限制。
例如:
【数据存储器】 AVR单片机的数据存储器一般包括3个相互独立的读/写存储区。全部共有1120个数据存储器地址,前96个地址为32个通用工作寄存器(地址空间为0x0000~0x001F)和64个I/O寄存器(地址空间为0x0020~0x005F),剩下的就是内部SRAM(地址空间为0x0060~0x045F)。
通用工作存储器一般用于存储程序运行过程中的局部变量和其他暂时性的数据,甚至还可以用于存储全局变量。64个I/O寄存器则用做微控制器上I/O设备的外设接口,而内部SRAM则用做通用的变量存储空间,同时也是处理器的堆栈。
【E 2 PROM存储器】 存储器中的E 2 PROM区域是一块可以读/写非易失性存储空间的区域,它常用于存储那些在掉电后不能丢失的和微控制器要反复运用的数据。E 2 PROM的首地址为0x000,最大的地址取决于所用的单片机型号。
虽然E 2 PROM存储器是可读写的,但很少用它来存放一般的变量,这是因为E 2 PROM的写速度非常慢,它要用1ms才能完成1B数据的写操作。大量使用这类存储器来存放变量会在很大程度上降低处理器的速度。
在CVAVR中,可以使用“eeprom”关键字将全局变量分配至E 2 PROM区域。例如: