案例解说单片机C语言开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.2 PIC16F87XA中的C语言

PIC16F87XA支持C语言和汇编语言两种不同的开发语言,C语言适用于较复杂的大型程序编写,汇编语言则适用于对效率要求很高的场合,尤其是底层函数的编写。PIC16F87XA的C语言和标准C语言基本完全兼容,本节将详细介绍PIC16F87XA的C语言基础。

1.2.1 PIC16F87XA中C语言的数据类型、运算符和表达式

数据是 PIC16F87XA操作的对象,是具有一定格式的数字或者数值。数据按照一定的数据类型进行的排列、组合和架构称为数据结构,PIC16F87XA的 C语言支持的数据类型如表1.23所示。

表1.23 PIC16F87XA的C语言支持的数据类型

注意:PIC16F87XA的C语言本身是不支持位变量和位操作的,但是某些编译器可以提供宏文件来支持对位变量和位操作。

1.常量和变量

PIC16F87XA中C语言的数据可以分为常量和变量,常量在程序执行过程中其值不能发生变化,变量在程序执行过程中其值可以改变;常量通常用#define关键字来定义,而变量通常用变量名来表示,其用户自己定义,是一个起始字符为字符或者下划线,随后字符必须是字母、数字或者下划线的字符组合,变量在使用之前必须先定义。

2.PIC16F87XA中C语言的算术运算、赋值、逻辑运算以及关系运算

在 PIC16F87XA中 C语言把用算术运算符和括号将运算对象连接起来的表达式称为算术表达式,运算对象包括常量、变量、函数、数组和结构等。在算术表达式中需要遵守一定的运算优先级,其规定为先乘除(余),后加减,最优先括号,同级别从左到右,和数学计算相同。

PIC16F87XA中C语言一共支持5种算术运算符,如表1.24所示。

表1.24 算术运算符

PIC16F87XA中C语言值运算符包括普通赋值运算符和符合赋值运算符两种,普通的赋值运算符使用“=”,而符合赋值运算符是在普通赋值运算符之前加上其他二目运算符所构成的赋值符,使用赋值运算符连接的变量和表达式则可以构成赋值表达式。

赋值运算涉及变量类型的转换,可以分为两种:一种是自动转换,一种是强制转换。自动转换是不使用强制类型转换符,直接将赋值运算符号右边表达式或者变量的值类型转换为左边的类型,一般说来是从“低字节宽度”向“高字节宽度”转换,表1.25给出了转换前后的变换。强制转换则是使用强制类型转换符将一种类型转换为另一种类型,强制类型转换符号和变量类型相同。

表1.25 赋值中的自动类型转化

PIC16F87XA中C语言提供了三种逻辑运算符,分别为:

● 逻辑与:&&。

● 逻辑或:||。

● 逻辑非:!。

使用逻辑运算符将表达式或者变量连接起来的表达式称为逻辑表达式,逻辑运算内部运算次序是先逻辑非后逻辑与和逻辑或,相同等级为从左到右,逻辑表达式的值为“真”或“假”,在PIC16F87XA的C语言语言中使用“0”代表“假”,使用“非0”代表逻辑“真”,但是逻辑运算表达式结果只能使用“1”来表示“真”。

PIC16F87XA的C语言提供了六种关系运算符,分别为:

● 小于:<。

● 大于:>。

● 小于等于:<=。

● 大于等于:>=。

● 如果等于:==。

● 如果不等于:!=。

使用关系运算符连接的表达式或者变量称为关系表达式,关系运算符中前两种优先级别高于后两种,同等优先级下遵守从左到右的顺序,关系运算式的运算结果是逻辑真“1”或者是逻辑假“0”。

3.PIC16F87XA C语言的自增减、复合和逗号运算

PIC16F87XA中 C语言的自增减运算分别使变量的值增加或者减少1,相当于“变量=变量+1”或者“变量=变量–1”操作,其应用形式是“变量++”、“++变量”、“变量--”和“--变量”。

复合运算是将普通运算符和赋值符号结合起来的运算,有两个操作数的运算符都可以写成“变量、运算符=变量”的形式,相当于“变量=变量、运算符、变量”。

逗号运算的关键字为“,”,其一般应用形式如“表达式1,表达式2,……,表达式n”,逗号表达式按照从左到右的方式运算,整个表达式的值取决于最后一个表达式。

4.PIC16F87XA中C语言的位运算

PIC16F87XA中 C语言支持位操作,恰当的位操作能极大地方便用户编程, PIC16F87XA的位操作包括位逻辑运算和移位运算两种类型。

位逻辑运算包括位与、位或、位异或、位取反。

● 位与:关键字“&”,如果两位都为“1”,则结果为“1”,否则为“0”。

● 位或:关键字“|”,如果两位其中有一位为“1”,则结果为“1”,否则为“0”。

● 位异或:关键字“^”,如果两位相等则为“1”,否则为“0”。

● 位取反:关键字“~”,如果该位为“1”,则取反后为“0”,如果该位为“0”,则该位取反后为“1”。

移位运算包括左移位和右移位运算。

● 左移位:关键字“<<”,将一个变量的各位全部左移,空出来的位补0,被移出变量的位则舍弃不要。

● 右移位:关键字“>>”,操作方式相同,移动方向向右。

5.PIC16F87XA中C语言运算的优先级

表1.26给出了PIC16F87XA中C语言运算符的优先级。

表1.26 运算符优先级

1.2.2 PIC16F87XA中C语言的结构

为了根据不同的情况做出不同的控制动作,PIC16F87XA中C语言和标准C语言一样,提供了控制流语句,通过不同的控制流语句的嵌套和组合可以控制单片机实现复杂的功能。控制流语句包括if、else if、switch、while等。

PIC16F87XA中C语言的程序结构可以分为顺序结构、选择结构和循环结构,这三种结构可互相组合和嵌套,组成复杂的程序结构,完成相应的功能。

1.顺序结构

顺序结构是最简单和基本的程序结构,程序从程序空间的低地址位向高地址位执行。

2.选择结构

在选择结构中,程序首先测试一个条件语句,如果条件为“真”时执行某些语句,如果条件为“假”时执行另一些语句。选择语句可以分为单分支结构及多分支结构,多分支结构又包括串行多分支结构和并行多分支结构。选择语句构成了单片机判断和转移的基础,是模块化程序的重要组成部分,PIC16F87XA中C语言常用的选择语句有if语句、switch语句,其中if语句有if…else、if和else if三种形式。

3.循环结构

循环语句用于处理需要重复执行的代码块,在某个条件为“真”时,重复执行某些相同的代码块。循环语句一般由循环体(循环代码)和判定条件组成,PIC16F87XA中C语言常用的循环语句有while语句、do while语句和for语句。

4.break、continue和goto语句

在循环语句执行过程中,如果需要在满足循环判定条件的情况下跳出代码块,可以使用break、continue语句,如果要从任意地方跳到代码的某个地方,可以使用goto语句。

1.2.3 PIC16F87XA中C语言的函数

PIC16F87XA中C语言支持把整个程序划分为若干个功能比较单一的小模块,通过模块之间的嵌套和调用来完成整个功能,这些具有单一功能的小模块称为函数,也可以称为子程序或者过程。PIC16F87XA中C语言的程序就是由一个个函数构成的,其从一个主函数开始执行,调用其他函数后返回主函数,进行其他的操作,最后从主函数中退出整个PIC16F87XA中的C语言程序。

1.函数的定义、参数和返回值

函数按照定义形式可以分为无参数函数和有参数函数,其定义方法如下:

            类型标识符  函数名()                  //无参数函数
            {
                声明语句和代码块;
            }
            类型标识符  函数名(形式参数列表)    //有参数函数
            {
                声明语句和代码块;
            }

函数的值是在函数执行完成之后通过 return语句返回给调用函数语句的一个值,返回值的类型和函数的类型相同,函数的返回值只能通过 return 语句返回。在一个函数中可以使用一个以上的 return 语句,但是最终只能执行其中的一个 return 语句。如果函数没有返回值,则使用void标志,

2.函数的调用

一般而言,函数调用有使用函数名调用、函数结果参与运算以及函数结果作为另一个函数的实际参数三种调用方式,需要注意的是函数在被调用之前必须首先被声明。

注意:PIC16F87XA中C语言的函数不支持递归调用。

3.局部变量和全局变量

局部变量是在某个函数中存在的变量,也可以成为内部变量,它只在该函数内部有效。局部变量可以分为动态局部变量和静态局部变量,使用关键字auto定义动态局部变量,使用关键字static定义静态局部变量。

全局变量是在整个源文件中都存在的变量,又称为外部变量。全局变量的有效区间是从定义点开始到源文件结束,其间的所有函数都可以直接访问该变量,如果定义点之前的函数需要访问该变量则使用extern关键字对该变量进行声明,如果全局变量声明文件之外的源文件需要访问该变量也使用extern关键字声明。

1.2.4 PIC16F87XA中C语言的数组和指针

数组是一组由若干个具有相同类型的变量所组成的有序集合。一般被存放在内存中一块连续的存储空间,数组中每一个元素都相继占有相同大小的存储单元。数组的每个元素都有一个唯一的下标,通过数组名和下标可以访问数组的元素。构成数组的变量类型可以是基本的数据类型,也可以是下一节中讲到的用户自定义结构、联合等类型。由整型变量组成的数组称为整型数组,由字符型变量组成的数组称为字符型数组,同理还有浮点型数组和结构型数组等。数组可以是一维的、二维的和多维的,其定义方式如下:

            类型  数组名[size]            //一维数组
            类型  数组名[sizeA][sizeB]    //二维数组
            char c_Name[10]                //字符数组

当一个数组被创建时,PIC16F87XA中C语言编译器就会在存储空间开辟一个连续的区域存放该数组的内容。对于一维数组来讲,会根据数组的类型在内存中连续开辟一块大小等于数组长度乘以数组类型长度(类型占有的字节数)的区域。

当PIC16F87XA中C语言定义一个变量后编译器就给这个变量在内存中分配相应的存储空间。如对于字符型(char)变量会在内存中分配1字节的内存单元,而对于整型(int)变量则会分配2字节的内存单元。

假设程序中定义了3个整型变量i、j、k,它们的值分别是1、3,5,假设编译器将地址为1000和1001的2字节内存单元分配给变量i,将地址为1002和1003的两字节内存单元分配给了变量j,将地址为1004和1005的2字节内存单元分配给变量k,则变量i、j、k在内存中的对应关系如图1.12所示。

图1.12 变量和内存地址的对应关系

在内存中变量名 i、j、k是不存在的,对变量的存取都是通过地址进行的。而存取的方式又分为两种:一种是直接存取方式,如int x=i*3,这时读取变量 i的值是直接找到变量 i在内存中的位置,即地址1000,然后从1000开始的2字节中读取变量i的值再乘以3作为结果赋给变量x;另一种方式是间接存取方式,在这种方式下变量i的地址1000已经存在某个地址(如2000)中,这时当要存取变量 i的值时,可以先从地址2000处读出变量 i的地址1000,然后再到1000开始的2字节中读取变量i的值,这就是指针。

关于指针有两个重要的概念:变量的指针和指向变量的指针变量。

● 变量的指针:变量的指针就是变量的地址,如上面的例子中变量 i的指针就是地址1000。

● 指向变量的指针变量:在上例中如果把用来存放变量 i的地址的内存单元2000和一个变量关联,就像变量i关联地址单元1000一样,那么这个变量就称为指向变量 i的指针变量,显然指针变量的值是指针(变量的地址)。

指针变量定义的一般形式为:

            类型  * 变量名

指针变量的引用是通过取地址运算符“&”来实现的,在定义完指针变量的定义和引用之后就可以通过指针变量对内存进行间接访问了。这时要用到指针运算符”*”。其运算形式为:

            *指针变量

其含义是指针变量所指向的变量的值,如果要将变量 a的值赋给整型变量 x,就可以有两种访问方式。

● 直接访问方式:x=a;

● 使用指针变量p进行间接访问:x=*p,此时程序先从指针变量 p中读出变量 a的指针(地址),然后从此地址的内存中读出变量a的值再赋给x。

在PIC16F87XA的C语言中,指针和数组的关系十分密切,任何能由数组下标完成的操作也都可用指针来实现,而且程序中使用指针可使代码更紧凑、更灵活。

注意:关于指针和数组的详细关联关系,读者可以自行参考其他C语言的资料,在此不多做叙述。

1.2.5 PIC16F87XA中C语言的自构造类型

构造新的数据结构是PIC16F87XA中C语言的重要特点之一,结构、联合和枚举类型是PIC16F87XA中C语言支持用户自行构造的新数据类型。

1.结构体

结构体是一种或多种类型变量的结合,这些变量可以是字符型、整型等,还可以是另一个结构体,统称为结构体的成员,其构造方法如下:

定义为结构体数据类型的变量被称为结构体变量,需要注意的是只能对其中单个成员进行赋值和引用。

            struct 结构名  //构造方法1
            {
            类型说明符     成员1;
            类型说明符     成员2;
            ……
            类型说明符     成员n
            }
            结构名 变量名1,变量名2 ……;
            struct 结构名   //构造方法2
            {
            类型说明符     成员1;
            类型说明符     成员2;
            ……
            类型说明符     成员n
            }变量名1,变量名2 ……;
            struct             //构造方法3
            {
            类型说明符     成员1;
            类型说明符     成员2;
            ……
            类型说明符     成员n
            }变量名1,变量名2 ……;

2.联合体

联合体又称为共用体,和结构体一样是一种构造类型,该类型用于在一块内存空间中存放不同类型的数据,在该内存空间并不是所有类型数据所占用的内存大小的总和,而是由最大的变量空间决定,其构造方法如下,定义为联合体数据类型的变量被称为联合体变量,它同样只能对其中单个成员进行赋值和引用。

            union 结构名  //构造方法1
            {
            类型说明符    成员1;
            类型说明符    成员2;
            ……
            类型说明符    成员n
            }
            结构名 变量名1,变量名2 ……;
            union 结构名  //构造方法2
            {
            类型说明符    成员1;
            类型说明符    成员2;
            ……
            类型说明符    成员n
            }变量名1,变量名2 ……;
            union    //构造方法3
            {
            类型说明符    成员1;
            类型说明符    成员2;
            ……
            类型说明符    成员n
            }变量名1,变量名2 ……;

需要注意的是,联合体变量在进行内存分配时是按照联合体变量成员中需要内存资源最大的变量分配的,在联合体变量占用的内存空间中始终只能保存联合体的一个成员有效数据,但是这个数据可以通过其他成员引用。

3.枚举

枚举数据类型同样也是构造类型,是某些整数型常量的集合,枚举类型数据变量的取值只能是这些常量中的一个,枚举类型变量的取值必须是定义中的整数值,其构造方式和结构体变量类似,说明如下:

枚举类型一般用于替代变量的整数赋值。

            enum  枚举名  //构造方法1
            {
                枚举值列表;
            };
            枚举名   变量1,变量2,……;
            enum  枚举名  //构造方法2
            {
                枚举值列表;
            }枚举名  变量1,变量2,……;