单片机应用技术 (C语言版) 第4章 C51程序设计入门 2019/6/9
第4章 单片机C语言及程序设计 目录 4.1 C51的基本数据类型 4.2 C51变量的存储区域 4.3 C51的运算符 4.4 数组 4.4 数组 4.5 指针 4.6 结构 4.7 C51程序设计 4.8 函数的定义与调用 2019/6/9
本章内容完全是结合单片机来讲解,也就是补充C语言在单片机方面的概念、数据定义和函数定义等。 2019/6/9
1、 C51概述 “C51”概念:把“单片机C语言”称为“C51”,也称为“Keil C”。 用C51编写单片机程序,与用ANSI C编写程序的不同之处是,需要根据单片机存储器结构及内部资源,定义相应的数据类型和变量。 其它的语法规定、程序结构及程序设计方法,都与ANSI C相同。 2019/6/9
2、 C语言编程的优势 在编程方面,使用C51较汇编语言有诸多优势: 1)编程容易 2)容易实现复杂的数值计算 3)容易阅读与交流 4)容易调试与维护程序 5)容易实现模块化开发 6)程序可移植性好 2019/6/9
用汇编语言编写程序时,必须考虑其存储器的结构,尤其要考虑其片内数据存储器、特殊功能寄存器是否正确合理的使用,以及按照实际地址端口数据的处理。 2019/6/9
3、 C51扩展的关键字 由于单片机在结构及编程上的特殊要求,C51有自己的特殊关键字,称之为C51扩展的关键字,下面给出常用的C51扩展的关键字。 _at_ bdata bit code data idata interrupt pdata reentrant sbit sfr sfr16 xdata 这些关键字在后面会陆续接触到,此处先不给出它们的含义。 2019/6/9
4.1 C51基本数据类型 1、数据类型:数据的不同格式。 2、数据类型分类:基本型(表4.1)、构造类型(数组、结构体、共同体、枚举)、指针类型和空类型 2019/6/9
在计算机中不能随意给一个变量赋任意的值,因为变量在单片机的内存中要占空间的,变量不同,所占的空间不同。 为了合理利用单片机的内存空间,在编程时要设定合适的数据类型。 2019/6/9
表4-1 C51数据类型、长度和数值范围 数据类型 表示方法 长 度 数 值 范 围 无符号字符型 unsigned char 1字节 0~255 有符号字符型 signed char -128~127 无符号整型 unsigned int 2字节 0~65535 有符号整型 signed int -32768~32767 无符号长整型 unsigned long 4字节 0~4294967295 有符号长整型 signed long -2147483648~2147483647 浮点型 float ±1.1755E-38~±3.40E+38 特殊功能寄存器型 sfr sfr16 位类型 bit、sbit 1位 0或1 2019/6/9
补充: C51特殊功能寄存器与位变量的定义(P82) 主要内容 1、 8位特殊功能寄存器的定义 2、 16位特殊功能寄存器的定义 3、 sbit型位变量的定义 4、 bit型位变量的定义 2019/6/9
1、 8位特殊功能寄存器的定义 定义的一般格式为: sfr 特殊功能寄存器名 = 地址常数 地址常数范围:0x80~0xff。 1、 8位特殊功能寄存器的定义 定义的一般格式为: sfr 特殊功能寄存器名 = 地址常数 地址常数范围:0x80~0xff。 特殊功能寄存器定义例子(见reg51.h、reg52.h等文件): sfr P0=0x80; //定义P0寄存器 sfr P1=0x90; //定义P1口寄存器 sfr PSW=0xd0; //定义PSW sfr IE=0xa8; //定义IE 2019/6/9
2、 16位特殊功能寄存器的定义 定义的一般格式为: sfr16 特殊功能寄存器名=地址常数 地址常数范围:0x80~0xff。 2、 16位特殊功能寄存器的定义 定义的一般格式为: sfr16 特殊功能寄存器名=地址常数 地址常数范围:0x80~0xff。 例如(见reg51.h、reg52.h等文件): sfr16 DPTR=0x82; sfr16 T2=0xcc; //含TL2和TH2 2019/6/9
1)定义特殊功能寄存器中的地址必须在0x80~0xff范围内。 2)定义特殊功能寄存器,必须放在函数外面作为全局变量。 几点说明: 1)定义特殊功能寄存器中的地址必须在0x80~0xff范围内。 2)定义特殊功能寄存器,必须放在函数外面作为全局变量。 3)用sfr或sfr16每次只能定义一个特殊功能寄存器。 4)用sfr或sfr16定义的是绝对定位的变量(因为名字是与确定地址对应的),具有特定的意义,在应用时不能像一般变量那样随便使用。 2019/6/9
3、 sbit型位变量的定义 特殊功能寄存器的位声明 一般格式为: sbit 位变量名 = 位地址表达式 这里的位地址表达式有三种形式: 直接位地址 特殊功能寄存器名带位号 字节地址带位号 2019/6/9
sbit RS0=0xd3; //定义PSW的第3位 sbit ET0=0xa9; //定义IE的第1位 定义特殊功能寄存器的位。例如: sbit P0_0=0x80; sbit P1_1=0x91; sbit RS0=0xd3; //定义PSW的第3位 sbit ET0=0xa9; //定义IE的第1位 2019/6/9
sbit OV=PSW^2; //定义PSW的第2位 sbit ES=IE^4; //定义IE的第4位 定义格式为: sbit 位变量名 = 特殊功能寄存器名^ 位号常数 这里的位号常数为0~7。例如: sbit P0_3=P0^3; sbit P1_4=P1^4; sbit OV=PSW^2; //定义PSW的第2位 sbit ES=IE^4; //定义IE的第4位 2019/6/9
c、寄存器地址带位号定义位变量 定义格式为: sbit 位变量名 = 特殊功能寄存器地址^位号常数 这里的位号常数同上,为0~7。例如: sbit P0_6=0x80^6; sbit P1_7=0x90^7; sbit OV=0xd0^2; //定义PSW的第2位 sbit ES=0xa8^4; //定义IE的第4位 2019/6/9
1)用sbit定义的位变量,必须能够按位操作,而不能够对无位操作功能的位定义位变量。 d、几点说明 1)用sbit定义的位变量,必须能够按位操作,而不能够对无位操作功能的位定义位变量。 2)用sbit定义位变量,必须放在函数外面作为全局位变量,而不能在函数内部定义。 3)用sbit每次只能定义一个位变量。 4)用sbit定义的是一种绝对定位的位变量(因为名字是与确定位地址对应的),具有特定的意义,不能随便使用。 2019/6/9
4、 bit型位变量的定义 常说的位变量指的就是bit型位变量。C51的bit型位变量定义的一般格式为: [,位变量名2[=初值]] [,…] bit位变量被保存在RAM中的位寻址区域 例如: bit flag_run=0; static bit send_bit; 2019/6/9
数据类型转换(p56) 1)自动转换 把赋值号右边的类型转换成左边的类型。 A: 实型赋予整型 B: 整型赋予实型 C: 字符型赋予整型 D: 整型赋予字符型 2019/6/9
像ANSI C一样,通过强制类型转换的方式进行转换。如: unsigned int b; float c; b=(int)c; 2)强制转换 像ANSI C一样,通过强制类型转换的方式进行转换。如: unsigned int b; float c; b=(int)c; 2019/6/9
4.2 C51变量存储区域 1、 C51变量的定义 2、 C51变量的存储类型 3、 C51变量的存储区域 4、 C51变量定义举例 2019/6/9
1、 C51变量的定义 C51变量定义的一般格式为: [存储类型] 数据类型 [存储区域] [存储类型] 数据类型 [存储区域] 变量名1[=初值] [,变量名2[=初值]] [,…] 或 [存储类型] [存储区域] 数据类型 static unsigned char data i=0; 2019/6/9
2、 C51变量的存储类型 按照ANSI C,C语言的变量有4种存储类型: (P71) 动态存储(auto) 静态存储(static) 全局存储(extern) 寄存器存储(register) 2019/6/9
动态(存储)变量:用auto定义的为动态变量,也叫自动变量。 (1)动态变量 动态(存储)变量:用auto定义的为动态变量,也叫自动变量。 作用范围:在定义它的函数内或复合语句内部。执行时,分配存储空间,结束时释放存储空间。 定义变量时,auto可以省略 2019/6/9
静态(存储)变量:用static定义的为静态变量。函数退出时,变量的值不消失。分为静态局部和静态全局变量。 (2)静态变量 静态(存储)变量:用static定义的为静态变量。函数退出时,变量的值不消失。分为静态局部和静态全局变量。 静态局部变量:在函数体内定义 静态全局变量:在函数体外部定义 若非必要,不要多用静态变量 2019/6/9
外部(存储)变量:用extern声明的变量为外部变量,是在其它文件定义过的全局变量。 用extern声明后,便可以在所声明的文件中使用。 (3)外部变量 外部(存储)变量:用extern声明的变量为外部变量,是在其它文件定义过的全局变量。 用extern声明后,便可以在所声明的文件中使用。 2019/6/9
函数的定义:函数功能的确立,包括制定函数名、函数值类型、形参及其类型、函数体等,是一个完整的、独立的函数单位。 补充: 函数的定义:函数功能的确立,包括制定函数名、函数值类型、形参及其类型、函数体等,是一个完整的、独立的函数单位。 函数的声明:把函数的名字、函数类型以及形参类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查。 2019/6/9
#include<stdio.h> void main() { int max(int,int);//对被调函数的声明 int A=13,B=3;定义外部变量 printf(“%d\n”,max(A,B)); } int max(int x,int y)//定义子函数 int z; z=x>y?x:y; return(z); 2019/6/9
寄存器(存储)变量:用register定义的变量为寄存器变量。 寄存器变量存放在CPU的寄存器中,这种变量处理速度快,但数目少。 (4)寄存器变量 寄存器(存储)变量:用register定义的变量为寄存器变量。 寄存器变量存放在CPU的寄存器中,这种变量处理速度快,但数目少。 C51中的寄存器变量: C51的编译器在编译时,能够自动识别程序中使用频率高的变量,并将其安排为寄存器变量,用户不用专门声明,对C51无实际意义。 2019/6/9
3、 C51变量的存储区域(P57) 变量的存储区属性是单片机扩展的概念,非常重要,它涉及到6个新的关键字。 MCS-51单片机有四个存储空间,分成三类,它们是片内RAM、片外RAM和ROM。 2019/6/9
表4.2 C51存储区与存储空间的对应关系 关键字 对应的存储空间及范围 data 片内RAM,直接寻址,低128字节 bdata 片内RAM,位寻址区0x20~0x2f,可字节访问 idata 片内RAM,间接寻址,256字节,与 @Ri 对应 pdata 片外RAM,低256字节,与MOVX @Ri 对应 xdata 片外RAM,64KB全空间 code ROM空间,64KB全空间 2019/6/9
4、 C51变量定义举例 1)定义存储在data区域的动态unsigned char变量: unsigned char data sec=0, min=0, hou=0; 2)定义存储在data区域的静态unsigned char变量: static unsigned char data scan_code=0xfe; 3)定义存储在data区域的静态unsigned int变量: static unsigned int data d; 2019/6/9
4)定义存储在bdata区域的动态unsigned char变量: unsigned char bdata operate, operate1; 5)定义存储在idata区域的动态unsigned char数组: unsigned char idata temp[20]; 6)定义在pdata区域的动态有符号int数组: int pdata send_data[30]; 2019/6/9
7)定义存储在xdata区域的动态unsigned int数组: unsigned int xdata receiv_buf[50]; 8)定义存储在code区域的unsigned char数组: unsigned char code a[10]= {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d,0x7d,0x07,0x7f,0x6f}; //定义共阴极数码管段码数组 2019/6/9
5、 C51变量的存储模式(P58) 存储模式:如果在定义变量时缺省了存储区,则编译器会自动选择默认的存储区域,也就是存储模式。 存储模式分为三种:小模式(small)、紧凑模式(compact)和大模式(large)。编译模式由编译控制命令决定。 存储模式(编译模式)决定了变量的默认存储区域和参数的传递方法。 2019/6/9
(1)small模式 在small模式下,变量的默认存储区域是“data”、“idata”,并且堆栈也安排在该区域中。 2019/6/9
在compact模式下,变量的默认存储区域是“pdata”,最大变量数为256字节,并且堆栈也安排在该区域中。 compact模式的其特点:是存储容量较small模式大,速度较small模式稍慢,但比large模式要快。 2019/6/9
在large模式下,变量的默认存储区域是“xdata”,最大变量数可达64KB。 large模式的特点:存储容量大,速度慢 2019/6/9
默认存储模式:如果文件或函数未指明存储模式,则编译器按small模式处理。 存储模式控制命令: “#pragma small(或compact、large)”应放在文件的开始。 2019/6/9
4.3 C51的运算符 2019/6/9
思考题1: 10/3=? 10/3.0=? 10/3.00=? 2019/6/9
求:(1)a=c++;后,a,c值是多少? (2)a=++c;后,a,c值是多少? 思考题2: j++; j--; ++j; --j; 例:char c=1; char a; 求:(1)a=c++;后,a,c值是多少? (2)a=++c;后,a,c值是多少? 2019/6/9
1||1=? 4||5=? 2019/6/9
2019/6/9
复合运算符: a+=b; x*=a+b; 复合运算的表达方式有利于提高编译效率,产生质量较高的目标代码。但可读性相对较差。 2019/6/9
4.4 数组 1、数组:同一类型变量的有序组合。 注:数组和普通变量一样,要先定义再使用。 2、定义方式: 数据类型 数组名 [常量表达式] 4.4 数组 4.4.1 一维数组 1、数组:同一类型变量的有序组合。 注:数组和普通变量一样,要先定义再使用。 2、定义方式: 数据类型 数组名 [常量表达式] unsigned int a[10]; 2019/6/9
数据类型 [存储区域] 数组名 [常量表达式] ={常量表达式}; unsigned char b[2]={1,2}; 3、赋值方式: 数据类型 [存储区域] 数组名 [常量表达式] ={常量表达式}; unsigned char b[2]={1,2}; unsigned char code table[]={ 0x3f,0x06,0x5b,0x4f, 0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c, 0x39,0x5e,0x79,0x71}; 2019/6/9
1、字符数组:基本类型为字符类型的数组。用来存放字符的。 2、定义:方法同一般数组。 4.4.2 字符数组 1、字符数组:基本类型为字符类型的数组。用来存放字符的。 2、定义:方法同一般数组。 如:char a[5]={‘h’,’e’,’l’,’l’,’o’}; for(i=0;i<5;i++) printf(“%c”,a[i]); 2019/6/9
3、字符串:在C语言中,将字符串作为字符数组来处理的。 用字符串常量来使字符数组初始化: char a[ ]={“hello”}; 问:在内存中怎么存储的? 2019/6/9
注: C语言规定以’\0’作为字符串结束的标志,在程序中依靠检测的位置来判定字符串是否结束。 char a[ ]={“hello”}; i = 0; while(a[i] != ‘\0’) { write_data(a[i]); i++; } 2019/6/9
4.5 指针 主要内容 4.5.1 指针概念 4.5.2 指针变量的定义、赋值与引用 4.5.3 Keil C51的指针类型 4.5 指针 主要内容 4.5.1 指针概念 4.5.2 指针变量的定义、赋值与引用 4.5.3 Keil C51的指针类型 2019/6/9
指针:对于一个存储单元来讲,单元的地址即为指针。 指针变量:存放指针的变量,用来指向另一个变量。 区别:一个指针是指一个地址,是一个常量; 4.5.1 指针概念 指针:对于一个存储单元来讲,单元的地址即为指针。 指针变量:存放指针的变量,用来指向另一个变量。 区别:一个指针是指一个地址,是一个常量; 一个指针变量可以被赋予不同的指针(地址),是一个变量。 2019/6/9
1、定义 4.5.2 指针的定义、赋值与引用 其格式为: [存储类型] 数据类型 *指针名1 [,*指针名2] [,…] 例如: [存储类型] 数据类型 *指针名1 [,*指针名2] [,…] 例如: char *cpt; //定义了一个指针变量cpt,它所指向的是一个字符型的数据。 或定义cpt为指向字符型变量的指针变量 指针说明符 2019/6/9
int *p=&c; (int *p; p =&c;) 2、赋值(指针变量使用前必须赋值) 指针变量只能赋予地址。 int c; int *p=&c; (int *p; p =&c;) 2019/6/9
注意:指针变量的定义和引用中*的意义不同!! 在指针变量定义中:*是类型说明符,说明其后的变量是指针类型 3、指针变量的引用 注意:指针变量的定义和引用中*的意义不同!! 在指针变量定义中:*是类型说明符,说明其后的变量是指针类型 在指针变量的引用中:*是运算符,(取内容) int *p=&a, int a,c; c=*p; 2019/6/9
4.7 C51的程序设计 4.7.1 C51程序结构 包含<头文件> 函数声明 常用C51的头文件: 全局变量、参数定义 reg51.h (定义特殊功能寄存器等); math.h (数学函数); ctype.h (字符函数); stdio.h (一般IO函数); stdlib.h (标准函数); absacc.h (绝对地址访问); string.h (串函数) … …. main() { 局部变量定义 <程序体> } func1() { 局部变量定义 <程序体> } … … funcN() 2019/6/9
函数声明 主函数 子函数 #include <reg51.h> sbit LED1=P3^2; //定义SFR中引脚的“位” void delayms(unsigned int x); //毫秒延时函数 void main(void) //主程序 { while(1) // 非0为真,此为无限循环 LED1=0;LED2=1;LED3=1; delayms(1000); LED1=1;LED2=0;LED3=1; delayms(1000); LED1=1;LED2=1;LED3=0; delayms(1000); } void delayms(unsigned int x) //毫秒延时函数 unsigned char j; while(x--) //非0,为真,即x=0时退出循环 for(j=0;j<123;j++){;} 包含文件 全程变量、 参数定义 函数声明 主函数 子函数 2019/6/9
4.7.2 C51流程控制 1.选择语句if 注:语句中为单一语句, 可以不用花括弧。 if(表达式) { 语句; } { 语句; } 例:if(p1!=0) { c=30; } if (条件表达式) {语句1;} else {语句2;} 例:if (a==b) {a++;} else {a--;} 当 a 等于 b 时, a=a+1, 否则 a=a-1 if (表达式1) {语句1;} else if (表达式2) {语句2;} else if (表达式3) {语句3;} … … else if (表达式m) {语句m;} else {语句n;} 注:语句中为单一语句, 可以不用花括弧。 2019/6/9
嵌套 if (表达式1) { if( ) 语句1; else( ) 语句2; } else 2019/6/9
举例: if(k1==0) { num++; if(num==10) num=0; } 2019/6/9
2. switch/case语句 switch (表达式) { case 常量表达式1:{语句1;}break; …… case 常量表达式n:{语句n;}break; default:{语句n+1;} } 也不是必须的 2019/6/9
3. while语句 非零为真 while(P0!=0) while (条件表达式真) { { 先判断 x=P0; 语句; 后循环 } } 例 2019/6/9
补充: (a) while(1) { ;}//无限循环 (b) { ... break; }//退出大循环 (c) ... { ;}//无限循环 (b) { ... break; }//退出大循环 (c) ... while(1);//停止(等待) 2019/6/9
int sum=0, i=0; do { do i++; { sum= sum+i ; 语句; 先循环 } while(i<=10) 后判断 例 2019/6/9
4.for语句 例:int i, sum=0 ; for (i=0; i<=10; i++) { sum=sum+i; } 循环语句; } 例:int i, sum=0 ; for (i=0; i<=10; i++) { sum=sum+i; } 2019/6/9
补充: 延时 for嵌套 unsigned int i,j; for( ; ;) for(i=1000;i>0;i--) { { { ;} } 延时 unsigned int i,j; for(i=1000;i>0;i--) { for(j=110;j>0;j--); } 外层是多少,延时大约多少ms 2019/6/9
相关c语言的系统知识请同学们自行复(学)习。 2019/6/9
4.8 函数的定义与调用 主要内容 4.8.1 函数 4.8.3 C51中调用汇编 4.8.4 预处理命令 4.8.5 头文件 4.8 函数的定义与调用 主要内容 4.8.1 函数 4.8.3 C51中调用汇编 4.8.4 预处理命令 4.8.5 头文件 2019/6/9
C51函数定义的一般格式如下: 函数类型 函数名(形参表) { 局部变量定义 执行语句 } 2019/6/9
void delay(usigned int z) { usigned int x,y; for(x=z;x>0;x--) 延时若干ms的子函数: void delay(usigned int z) { usigned int x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); } 子函数的调用: delay(500); 2019/6/9
主函数: void main() { } 2019/6/9
2、调用函数时,多个参数要用逗号隔开,且每个实参的类型、位置与形参一一对应。 注: 1、{ }里也可以什么不写,为空函数。 2、调用函数时,多个参数要用逗号隔开,且每个实参的类型、位置与形参一一对应。 3、调用的函数是无参函数时,后面的括号不能省。 4、带参数的函数声明时,必须将参数类型写上,类型后面的变量名可有可无。 5、主函数不能被其他函数调用。 2019/6/9
4.8.4 预处理命令 预处理:在进行编译的第一遍扫描之前所做的工作。 (1)宏定义: #define M(y) y*y+3*y 4.8.4 预处理命令 预处理:在进行编译的第一遍扫描之前所做的工作。 (1)宏定义: #define M(y) y*y+3*y #define uint unsigned int #define uchar unsigned char 2019/6/9
1、宏名一般用大写字母表示,以区别于一般的变量。 注: 1、宏名一般用大写字母表示,以区别于一般的变量。 2、使用宏名替代一无规律字符串,宏名易记住,且当需要改变某一常量时,只需改#define命令行,一改全改。 #define PI 3.1415926 3、宏定义不是C语句,不必在行末加分号。 4、宏定义命令写在文件的开头,函数之前。有效范围为定义命令之后到本源文件结束。 2019/6/9
(2)文件包含:将另外的文件包含到本文件中。 #include “文件名” 说明: 1、一个#include 命令只能指定一个被包含文件,若要包含n个文件,要用n个#include 命令。 2、文件名可以用<>或“” 3、后面不加分号。 2019/6/9
4.8.5 头文件 1、头文件作用:对所使用的引脚进行定义以及对同名的驱动函数中的自定义函数作出声明,等。 4.8.5 头文件 1、头文件作用:对所使用的引脚进行定义以及对同名的驱动函数中的自定义函数作出声明,等。 如:reg51.h,reg52.h,math.h,ctype.h,stdio.h,stdlib.h,intrins.h等。 2019/6/9
2、定义头文件(LED.h): #ifndef_LED_H_ #define_LED_H_ #endif 单独存盘 (P89、P92) 3、另外的文件中引用头文件: #include“LED.h” 说明: (1)头文件定义完后,另写一个与头文件同名的驱动程序(LED.c) (2)在头文件中也可以把所声明的函数程序一起写入,这样不用再写驱动程序了。 (P89、P92) 2019/6/9
本章小结 本章首先认识了单片机C语言的优势及其与ANSI C的区别。 2019/6/9
本章小结(续) 本章是用单片机C语言进行程序设计的基础(非C语言基础),必须要掌握好本章的内容,才能够比较顺利地编写单片机C语言程序,成为单片机程序设计的高手,进而成为单片机应用的高手。 2019/6/9
2019/6/9