版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第3章C51应用实训实训3.1C51程序开发环境实训3.2定时器/计数器C51程序设计实训3.3数码显示和矩阵式键盘C51程序设计实训3.4字符型LCD显示模块C51程序设计实训3.5单片机串行通信C51程序设计
实训3.6数字钟C51程序的实现实训3.1C51程序开发环境1.实训目的
(1)了解利用C51控制单片机系统的软硬件环境及C51程序的开发过程。
(2)了解C51程序的结构。
(3)了解8051特殊功能寄存器SFR和位名称在C51中的定义方法。
2.实训设备与器件实训设备:单片机集成开发环境、综合实训板。3.项目设计要求编制C51程序使目标板上连接在P1口的8个LED闪动。4.实训内容目标板上连接在P1口的8个LED闪动的C51程序源代码如下://ex1.c#include"REG51.H" //头文件为REG51.H,定义了51单片机的SFR和位名称/*-----------------延时函数-----------------*/delay(intt) {inti,j;for(i=0;i<t;i++)for(j=0;j<10;j++);}/*-----------------main函数-----------------*/main() {inti;while(1){P1=0xff; //熄灭8个LED,0xff为十六进制数0ffH,在C语言中用0x前缀表示十六进制数delay(1000);P1=0x00; //点亮8个LEDdelay(1000); }}
5.调试方法与步骤本实训教材采用FranklinC51编译器和MedWin中文版集成单片机开发环境,并假定FranklinC51编译器已经安装在计算机的D:\FC子目录下。
(1)将开发系统和目标板连接好,并接上电源。
(2)启动MedWin中文版,进入MedWin集成开发环境。
(3)设置编译环境。第一次在MedWin中使用C51编译环境需进行“编译、汇编、连接配置”(以后使用时不需再配置)。单击“设置”菜单项,如图3.1.1所示。选择“设置向导”,弹出如图3.1.2所示的“编译/汇编/连接配置”窗口1。图3.1.1设置菜单项图3.1.2“编译/汇编/连接配置”窗口1图3.1.3“编译/汇编/连接配置”窗口2
图3.1.4“Newfiles”窗口(4)编辑源程序。打开“文件”菜单,选择“新建”选项,在“Newfiles”窗口中输入新建的文件名,例如“ex1.c”(注意一定要输入文件扩展名),如图3.1.4所示。单击“打开”,进入源程序编辑窗口。
(5)输入源程序,并存盘。
(6)编译源程序。编译后在编辑窗口下面将出现状态窗口,如图3.1.5所示。
(7)连接程序并下载到仿真器中。
(8)在调试窗口中可以对程序进行单步运行、断点运行、全速运行等测试,如图3.1.6所示为程序全速运行时的画面。图3.1.5编译后出现的状态窗口区域图3.1.6全速运行程序
6.实训分析与总结
1)FranklinC51编译器简介
FranklinC51编译器的安装非常简单,直接点击setup,按照提示选择安装目录即可。安装完成后,在安装目录下有以下四个子目录:
LIB:库文件目录,包含了所有标准函数;
INC:头文件目录(#include);
BIN:可执行文件目录;
EXAMPLES:样例程序子目录。
这里对主要的可执行文件简介如下:
A51:MCS-51汇编程序,它可将按照A51汇编格式编制的汇编源程序编译成以Intel目标模块格式产生的可再定位目标代码。
C51:可将C51高级语言编制的源程序编译成以Intel目标模块格式产生的可再定位目标代码。
L51:连接定位器,可将几个不同程序模块复合为一个模块,并自动从库文件中挑选模块嵌入目标文件,同时定义绝对地址,并计算再定位段的地址。
LIB51:C51函数库管理器,可以进行函数库的生成与处理。2)C51程序结构图3.1.7C程序基本结构C51中的函数与汇编语言中的子程序概念是一样的。本例中包含delay和main两个函数,main函数是必不可少的主函数,也是程序开始执行的函数,delay函数的功能是延时,用于控制灯的闪动速度。LED的闪动过程是:点亮→延时→熄灭→延时
延时函数在很多程序设计中都会用到,本程序中使用了双重循环,外循环的循环次数由形式参数t提供,总的循环次数是10×t,循环体是空操作。for语句的使用方法与ANSIC语言相同。
C51程序设计中,函数的定义和调用方法同ANSIC语言相同。3)8051单片机的SFR寄存器和位名称及其C51定义在8051单片机中,除了程序计数器PC和四组通用寄存器组之外,其他所有的寄存器均称为特殊功能寄存器(SFR)。
MCS-51系列单片机内部定义了21个特殊功能寄存器,它们分散在片内RAM区的高128字节中,地址为80H~0FFH,SFR只能用直接寻址方式。
SFR中有11个寄存器具有位寻址能力(这些寄存器的字节地址都能被8整除,即字节地址是以8或0作为尾数的),且每一位均定义了位名称。在汇编语言程序设计中可以直接使用这些寄存器名称和位地址名称,例如: MOV A,#0ffh CLR P1.1
那么,在C51程序设计中,如何使用这些寄存器和位名称呢?在头文件“REG51.H”已经预定义了所有SFR和位名称。在REG51.H头文件中使用了“sfr”和“sbit”两个关键字。
(1)关键字“sfr”用于定义特殊功能寄存器的地址,其格式为:
sfr特殊功能寄存器名=特殊功能寄存器地址;注意后面的地址必须为常数,范围是0x80到0xff,例如:sfrPSW=0xD0;sfrACC=0xE0;sfrB=0xF0;(2)关键字“sbit”与“sfr”类似,用于定义一些特殊的位,其格式为:
sbit位名称=位地址例如:
sbitCY=0xD7; sbitAC=0xD6; sbitF0=0xD5;也可以写成:
sbitCY=0xD0^7; sbitAC=0xD0^6; sbitF0=0xD0^5;
如果在前面已定义了特殊功能寄存器PSW,那么上面的定义也可以写成:sbitCY=PSW^7;sbitAC=PSW^6;sbitF0=PSW^5;
在C51程序设计中,编程员可以直接在自己的程序中利用关键字sfr和sbit来定义这些特殊功能寄存器和特殊位名称,也可以把“REG51.H头文件包含在自己的程序中,直接使用SFR名称和位名称。头文件REG51.H的内容如下:7.思考题(1)修改源程序,使8个LED模拟霓虹灯的各种显示方式。(2)利用对P1口某一位的操作设计霓虹灯的显示方式。(3)以单步、断点等各种方式运行程序。(4)在程序运行过程中如何查看单片机各种资源的状态。实训3.2定时器/计数器C51程序设计1.实训目的(1)复习MCS-51单片机定时器/计数器和中断的知识。(2)掌握利用C51进行单片机内部资源控制的方法。(3)掌握利用C51进行中断编程的方法。(4)了解C51的基本语法。2.实训设备与器件实训设备:单片机集成开发环境,综合实训板。
3.项目设计要求
(1)利用定时器查询方式,编制C51程序使目标板上连接在P1口的8个LED循环显示,时间间隔为1s。
(2)利用定时器中断方式,编制C51程序使目标板上连接在P1口的8个LED循环显示,时间间隔为1s。
4.实训内容
1)定时器查询方式C51程序设计
(1)程序设计方法:用定时器1的方式1编制1s的延时程序,假定系统采用12MHz晶振,定时器1、方式1定时50ms,再循环20次即可定时到1s。(2)源程序如下://ex2.c#include"REG51.H"/*-----------------延时函数-----------------*/voiddelay(){intj;for(j=0;j<0x14;j++) //设置20次循环次数
{TH1=0x3c; //置定时器初值
TL1=0xb0;TR1=1; //启动定时器1while(!TF1); //查询计数是否溢出,即定时50ms时间到,TF=1TF1=0; //50ms时间到,将定时器溢出标志位TF清零
}}/*-----------------main函数-----------------*/main(){inti,w;TMOD=0x10; //置定时器1为方式1while(1){w=0x01; //灯的位置初值为01hfor(i=0;i<8;i++){P1=~w; //循环点亮灯
w<<=1; //点亮灯的位置移动
delay(); //调用1ms延时
}}}2)定时器中断方式C51程序设计
(1)程序设计方法:用定时器1的方式1编制1s的延时程序,假定系统采用12MHz晶振,定时器1、方式1定时50ms,采用中断方式编程,需中断20次,外部变量count作为计数次数,位变量flag为1s定时到标志位。
(2)源程序如下://ex3.c#include"REG51.H"intcount; //定义外部变量bitflag; //1s时间到标志/*-----------------中断函数-----------------*/voiddelay()interrupt3//interrupt3表示该函数为中断号3的中断函数{TH1=0x3c; //重新置定时器1初值
TL1=0xb0;TR1=1; //开定时器1中断
count--; //中断次数减1if(count==0)flag=1; //若20次中断已完成,则置1s延时时间到标志
}/*-----------------main函数-----------------*/main(){intj,w;TMOD=0x10; //初始化定时器1TH1=0x3c;TL1=0xb0;EA=1; //开总中断ET1=1; //定时器1开中断
TR1=1; //启动定时器1while(1){w=0x01;for(j=0;j<8;j++){flag=0; //初始标志
count=0x14; //设置中断次数
P1=~w; //循环点亮灯
while(flag==0); //等待1s定时时间到
w<<=1; //点亮灯的位置移动
}}}
5.实训分析与总结
1)C51程序基本结构
C51的程序结构同ANSIC语言相同。C语言是一种结构化编程语言。结构化程序由若干模块组成,每个模块中包含着若干个基本结构,而每个基本结构中有若干条语句。总的来说,C语言有3种基本结构:顺序结构、选择结构和循环结构。顺序结构是一种最基本、最简单的程序结构,程序由低地址到高地址顺序执行程序代码。选择结构也称为分支结构,根据条件测试结果选择不同的程序执行方向,常用的选择语句有:if,elseif,switch/case语句。在程序ex3.c的delay的断函数中有下面的选择语句:
if(count==0)flag=1;//若20次中断已完成,则置1s延时时间到标志
循环结构是指重复执行某一程序段的程序结构,是选择结构的一种特殊情形,程序设计中使用非常广泛。C语言中用于循环的语句有:while,dowhile,for语句。前面的例程中使用了很多循环语句,举例如下:
(1)单片机控制程序的主程序,即main函数中都有一个后台无限循环语句,结构如下:main(){...... //初始化部分
while(1){ //无限循环
}}(2)在例程ex1.c中,延时函数delay采用了双重循环结构,其代码如下:delay(intt) //延时函数{inti,j;for(i=0;i<t;i++)for(j=0;j<10;j++);}
在上面的双重循环结构中,循环体是空的,表示什么都不做,仅用于延时的功能。(3)在例程ex2.c中,延时函数采用了查询方式编程,查询语句如下:
while(!TF1);//查询计数是否溢出,即定时50ms时间到,TF=1
当定时时间未到时,TF=0,此时测试条件!TF的结果为1,即条件为真,继续重复执行循环体(此处循环体为空);当定时时间到时,TF=1,条件!TF的结果为0,即条件为假,退出while循环,继续执行下面的语句。在单片机程序设计中,查询方式编程经常采用上面的结构,例如:
while((P1&0x01)==0);该语句用于测试P1口的P1.0的电平状态。当P1.0=0时,条件成立,继续等待;若P1口的P1.0由0变为1,则循环终止,继续执行下面的语句。2)C51的数据与运算法则简介(1)C51的数据类型。表3.2.1FranklinC51编译器支持的数据类型
当计算结果隐含着另外一种数据类型时,数据类型可以自动转换。例如将一个位变量赋给一个整型变量时,位值自动转换为整型值,有符号变量的符号也能自动进行处理。
FranklinC51编译器也支持符号常量和变量,符号常量的定义方法如下:
#defineCOUNT20
符号常量在整个程序中,其值不可变,通常是具有一定含义的英文单词或符号。因此,采用符号常量可以增加程序的可读性和可修改性。
下面结合本实训中的两个例程,对变量定义应注意的几个问题加以讨论。①变量数据类型的选择。在C51程序设计中,变量数据类型的定义极其重要,因为在所有数据类型中只有bit和unsignedchar两种数据类型可以直接支持机器指令,对于其他的数据类型C51编译器都要进行一系列复杂的变量数据和变量类型的处理,而这种处理将会对应很长一段机器指令,最终会使程序变得复杂、庞大,运行速度降低。由此可见,在C51程序设计过程中,在满足数据要求的情况下,应尽可能使用unsignedchar变量和bit变量。signedchar变量虽然也只占用一个字节,但需要进行额外的操作来测试代码的符号位,因此会降低代码执行效率。
在例程ex2.c的main函数中定义了整型变量i和w,仔细分析发现,i的取值范围是0~8,而表示亮灯位置的变量w的取值也在00H~0ffH之间,所以,这两个变量应该定义成unsignedchar,而不是int。同样,在该程序的delay函数中,也定义了整型变量j,分析一下,最好是将j定义成什么数据类型呢?同样,在例程ex3.c的main函数中的变量i和w也应该定义成unsignedchar,外部变量count也应该定义成unsigedchar。②全局变量和局部变量。这里应该注意的是:在例程ex3.c中,整型变量count和位变量flag是定义在所有函数外部的变量,称为外部变量,即全局变量;而变量i和w是定义在main函数内部的,称为内部变量,即局部变量。全局变量与局部变量的区别是:前者可以在本文件的所有函数中使用,其有效范围是从定义变量的位置开始到本源文件结束为止。例如count和flag,不仅在delay函数中使用,也在main函数中使用,后者只在定义本函数的范围内有效,即只能在本函数内使用。③变量的定义。
bit数据类型是ANSIC语言所没有的数据类型,在此做如下说明。位变量的定义格式为:
bit变量名;在程序ex3.c中,定义了位变量flag作为定时时间到的标志,若定时时间未到,则flag=0,若定时时间到,则flag=1。在单片机C语言程序设计中,为了使程序更加优化,当需要设定只有两种状态的标志变量时,应该将其定义成位变量,而不要定义成字符或整型变量。④用于访问sfr的数据类型。
FranklinC51编译器的数据类型除了包括ANSIC语言所具有的位寻址、字符、整数、长整、浮点数、指针等之外,还有3种用于访问SFR的数据类型,如表3.2.2所示。表3.2.2访问SFR的数据类型(2)C51的运算符。
C51编译器所支持的运算符与ANSIC语言相同,分为算术运算符、关系运算符、逻辑运算符、位操作符、自增减运算符和复合运算符等。算术运算符:+、-、*、/、%。关系运算符:<、>、<=、>=、==、!=。逻辑运算符:&&、||、!。位操作符:&、|、^、~、<<、>>。自增减运算符:++、--。复合运算符:凡是二目运算符,都可以与赋值运算符“=”一起组成复合赋值运算符,包括+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=。
尤其值得注意的是:由于单片机C语言程序和单片机硬件紧密相关,因此会用到大量的位运算或逻辑运算。在例程ex2.c和ex3.c中,为了使亮灯的顺序移动,采用了取反和左移操作,具体操作如下:初值:w=0x01;w:00000001(初值)P1=~w;11111110(“1”使相应灯熄灭,“0”使相应灯点亮)W<<=1;00000010(左移一位)思考:如果将该程序中初值直接写成:“11111110”,应该如何修改程序?3)中断函数的编写方法
FranklinC51编译器支持在C源程序中直接以函数形式编写中断过程。常用的中断函数定义语法如下:
void函数名()interruptnC51编译器允许0~31个中断,下列中断及其相关地址为8051控制器所提供的外部中断:
0:EXTERNAL0 地址:0003H 1:TIMER/COUNTER0 地址:000BH 2:EXTERNAL1 地址:0013H 3:TIMER/COUNTER1 地址:001BH 4:SERIALPORT 地址:0023H
在例程ex3.c中,使用了TIMER/COUNT1中断,中断号为3,因此该中断函数的结构如下:voiddelay()interrupt3//interrupt3表示该函数为中断号3的中断函数{
::}
编写中断函数时应遵循下列规则:
(1)不能进行参数传递,如果中断过程包括任何参数声明,则编译器将产生一个错误信息。
(2)无返回值,如果想定义一个返回值将产生错误,但是,如果返回整型值编译器将不产生错误信息,因为整型值是默认值,编译器不能清楚识别。
(3)在任何情况下不能直接调用中断函数,否则编译器会产生错误。由于退出中断过程是由指令RETI完成的,该指令影响MCS-51单片机的硬件中断系统,直接调用中断函数时硬件上没有中断请求存在,因而这个指令的结果是不定的并且通常是致命的。(4)编译器从绝对地址8n+3处产生一个中断向量,其中n为中断号,该向量包括一个中断过程的跳转,向量的产生可由编译器控制指令NOINTVECTOR压缩,因而程序员可以从独立的汇编模块中提供中断向量。
(5)可以在中断函数定义中使用using指定当前使用的寄存器组,格式如下:
void函数名([形式参数])interruptn[usingm]MCS-51单片机有四组寄存器R0~R7,程序具体使用哪一组寄存器由程序状态字PSW中的两位RS1和RS0来确定。在中断函数定义时,可以用using指定该函数具体使用哪一组寄存器,m在0,1,2,3这4个数中取值,对应四组寄存器组。例如:
voiddelay()interrupt3using2(6)在中断函数中调用的函数所使用的寄存器组必须与中断函数相同,当没有使用using指令时,编译器会选择一个寄存器组作绝对寄存器访问。程序员必须保证按要求使用相应寄存器组,C编译器不会对此检查。
(7)如果在中断函数中执行浮点运算,必须保存浮点寄存器状态,当没有其他程序执行浮点运算时,可以不保存。4)C51的数据存储类型与8051存储器结构由于MCS-51单片机的存储器结构特点是:程序存储器(ROM)和数据存储器(RAM)分开,并各自有其独立的寻址方式。具体来说,MCS-51单片机的存储空间分为四部分:片内数据存储器空间(256字节)、片外数据存储器空间(64K字节)、片内程序存储器空间和片外程序存储器空间,那么程序中定义的变量在哪个存储区域呢?FranklinC51编译器通过将变量、常量定义成不同的存储类型的方法,将它们定位在不同的存储区中。C51编译器支持的存储器类型如表3.2.3所示。表3.2.3C51编译器支持的存储器类型存储器类型可以和数据类型一起使用,例如:intdatai;//整数i为内部数据存储器中的变量intxdataj;//整数j定义在外部数据存储器(64K字节)内一般在定义变量时经常省略存储器类型的定义,采用默认存储器类型,而默认存储器类型和存储器模式有关。5)C51编译器支持的存储器模式表3.2.4C51编译器支持的存储器模式SMALL模式:所有缺省变量参数均装入内部RAM(与使用显式的data关键字来定义结果相同)。使用该模式的优点是访问速度快,缺点是空间有限,而且分配给堆栈的空间比较少,遇到函数嵌套调用和函数递归调用时必须小心,该模式适用于较小的程序。
COMPACT模式:所有缺省变量均位于外部RAM区的一页(与使用显式的pdata关键字来定义结果相同),最多能够定义256字节变量。使用该模式的优点是变量定义空间比SMALL模式大,但运行速度比SMALL模式慢。使用本模式时,程序通过@R0和@R1来访问变量。LARGE模式:所有缺省变量可放在多达64K字节的外部RAM区(与使用显式的xdata关键字来定义结果相同),均使用数据指针DPTR来寻址。该模式的优点是空间大,可定义变量多,缺点是速度较慢,一般用于较大的程序,或扩展了大容量外部RAM的系统中。存储模式决定了变量的默认存储类型、参数传递区和无明确存储类型的说明。例如若定义chars,则在SMALL存储模式下,s被定位在DATA存储区;在COMPACT存储模式下,s被定位在IDATA存储区;在LARGE存储模式下,s被定位在XDATA存储区。
存储模式定义关键字SMALL、COMPACT和LARGE属于C51编译器控制指令,可以在命令行输入,也可以在源文件的开始直接使用下面的预处理语句:
#pragmaSMALL//定义为SMALL模式除非特殊说明,本书中的C51程序均运行在SMALL模式下。
6.思考题
(1)例程ex3.c进行优化修改后的程序如下,试指出哪些地方做了修改?修改后有什么好处?//ex3_1.c#include"REG51.H"#defineTIME20unsignedcharcount; //定义外部变量bitflag; //1s时间到标志/*-----------------中断函数-----------------*/voiddelay()interrupt3 //interrupt3表示该函数为中断号3 的中断函数{TH1=0x3c; //重新置定时器1初值
TL1=0xb0;TR1=1; //开定时器1中断
count--; //中断次数减1if(count==0)flag=1; //若20次中断已完成,则置1s延时时间到标志}/*-----------------main函数-----------------*/main(){unsignedcharj,w;TMOD=0x10; //初始化定时器1TH1=0x3c;TL1=0xb0;EA=1; //开总中断
ET1=1; //定时器1开中断
TR1=1; //启动定时器1while(1){w=0x01;for(j=0;j<8;j++){flag=0; //初始标志
count=TIME; //设置中断次数
P1=~w; //循环点亮灯
while(flag==0); //等待1s定时时间到
w<<=1; //点亮灯的位置移动
}}}(2)修改实训中的两个例程,分别采用定时器/计数器0的方式0和方式2实现定时功能。实训3.3数码显示和矩阵式键盘C51程序设计
1.实训目的
(1)复习MCS-51单片机扩展可编程接口芯片8155的方法。
(2)复习8155编程方法及矩阵式键盘的硬件接口及扫描方法,复习LED的动态接口方法。
(3)掌握利用C51进行键盘和LED显示的编程思路。
(4)掌握利用C51进行绝对地址访问的方法。
2.实训设备与器件实训设备:单片机集成开发环境、综合实训板。
3.项目设计要求
(1)编制C51程序使实验板上的8个数码管移动显示“0~F”。
(2)编制C51程序使实验板上的8个数码管移动显示“0~F”,移动速度是1s移动一个字符。
(3)编制C51矩阵式键盘扫描程序,当按下实验板上任意键时,在某个数码管上能够显示其键值,当按键释放时,显示关闭。
4.实训内容
1)LED显示程序设计初步
(1)硬件电路分析。当单片机提供的并行I/O口不够用户使用时,常常需要扩展I/O口,8155和8255是扩展I/O口时使用较多的I/O芯片。在实训电路中,用扩展的8155连接了8个LED数码管和16个按键。通过扩展8155可以提供3个并行I/O口A、B、C口,3个I/O口的工作方式、输入/输出方向是由用户的编程来确定的。8位LED显示的位选码由8155的A口控制,段选码由8155的B口控制,LED为共阴极数码管,接口方式为动态显示接口方式。8155的地址如下:控制口:4400HA口:4401HB口:4402HC口:4403H(2)软件设计思路。
LED动态显示的硬件连接特点是各位数码管的段选线相应并联在一起,由同一个I/O口控制,各位的位选线(公共阴极或阳极)由另外的I/O口线控制。当程序向数码管传送一个显示码时,该显示码会同时到达每个数码管,到底哪个数码管会显示,由位选线确定。当以动态方式显示时,各数码管分时轮流选通,要使其稳定显示,必须采用扫描方式,利用人眼睛的视觉暂留效应,每一时刻显示一个字符,一位一位轮流显示,只要每位显示间隔时间足够短,就可以给人以同时显示的感觉。图3.3.1LED显示示意图
在上述移动显示方式中,要分别显示23屏不同的数据,就可以达到移动显示的效果。每屏显示数据之间有一定的逻辑关系,把这些显示字符顺序写成下面的格式:
×××××××0123456789AbCdEF×××××××(×表示不显示)
led
可以看到,第1屏显示数据为“×××××××0”,第二屏显示数据为“××××××01”,依此类推,第23屏显示数据为“F×××××××”。
如果把以上显示数据的显示码按顺序存放到内部存储器中,首地址为led,那么第1屏显示码的首地址为led,第2屏显示码的首地址为led+1,第3屏显示码的首地址为led+2,依此类推,可以得到第i屏显示码的首地址为led+i-1。如果显示屏从第0屏开始数起,那么第i屏显示码的首地址即为led+i。(3)C51源程序代码如下://ex4.c#include"REG51.H" //定义头文件#include"absacc.h"#defineCTRL8155XBYTE[0x4400] //采用绝对地址访问方式定义8155口 地址#definePORTA8155XBYTE[0x4401]#definePORTB8155XBYTE[0x4402]#definePORTC8155XBYTE[0x4403]voidscanled(unsignedcharn[]); //函数声明,LED扫描函数,该函数 将8个LED //轮流扫描一遍,入口参数为8个显示码存放的首地址voiddelay(unsignedchart);//延时函数,入口参数t确定延时时间,用于控制
//每位显示间隔时间/*-----------------main函数-----------------*/main(){unsignedchari,j;unsignedcharled[]={0,0,0,0,0,0,0,0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d, 0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0,0,0,0,0,0,0}; //定义显示码数组,其中显示码0没有任何显示CTRL8155=0x03; //写入8155控制字,C口输入,A,B口输出for(;;){for(i=0;i<23;i++) //共显示23屏
for(j=0;j<100;j++) //每屏扫描100遍,用于控制每屏显示时间
//同时也控制了移动显示的速度
scanled(led+i); //调用LED扫描函数
}}/*-----------------LED扫描函数-----------------*/voidscanled(unsignedcharn[]){unsignedchari,temp=0x01;for(i=0;i<8;i++) //控制8个LED{PORTA8155=0xff; //位码送0xff,关闭所有显示
PORTB8155=n[i]; //B口送显示码,根据不同的位依次送显示 码n[0],n[1],…n[7]PORTA8155=~temp; //A口选位
temp<<=1; //位码左移一位,选择下一位LEDdelay(20); //每位显示时间
}}/*-----------------延时函数-----------------*/voiddelay(unsignedchart){unsignedchari;for(i=0;i<t;i++);}2)LED定时显示程序设计
(1)软件设计思路。1s定时采用实训3.2中的设计思路,用定时器1的方式1编制1s的延时程序。假定系统采用12MHz晶振,定时器1、方式1定时50ms,再循环20次即可定时到1s,定时器编程采用查询方式实现。本程序设计的重点是8位LED动态扫描函数如何同1s定时函数合理结合起来。动态扫描显示的基本原理要求调用扫描函数的时间间隔不能太长,否则显示就会出现闪烁的现象。那么,把扫描函数放到什么地方调用才能保证每次调用的时间间隔不能太长呢?现在试着把它放到主函数main中调用,调用程序段如下:voidscanled(unsignedcharn[]); //8位LED扫描函数,参见ex4.cvoiddelay(); //定时器1延时1s函数,参见ex2.cunsignedchardata*p; //指针定义main(){unsignedchardatai,j;unsignedchardataled[]={0,0,0,0,0,0,0,0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0,0,0,0,0,0,0}; TMOD=0x10; for(;;){ p=led; for(i=0;i<23;i++,p++){ scanled(p); //调用8位LED扫描函数
delay(); //调用1s延时函数
} }}
把上面程序补充完整,运行该程序会出现什么现象呢?LED无法正常显示。因为在主函数main中每隔1s调用一次扫描函数,扫描间隔时间太长(按照视觉暂留原理,一般要保证1s内扫描次数在24次以上才可以保证显示不闪烁),所以把扫描函数直接放到main函数中调用是无法完成显示功能的。分析delay函数发现,执行它的绝大部分时间是在查询等待定时时间到,查询语句如下:
while(!TF1);
把扫描函数放到查询语句中调用,是否可以保证能正确稳定显示结果呢?调用语句如下:
while(!TF1){scanled(p);}(2)程序源代码如下://ex5.c#include"REG51.H"#include"absacc.h"#defineCTRL8155XBYTE[0x4400]#definePORTA8155XBYTE[0x4401]#definePORTB8155XBYTE[0x4402]#definePORTC8155XBYTE[0x4403]voidscanled(unsignedcharn[]);voiddelay1(unsignedchart);voiddelay();unsignedchardata*p;/*-----------------main函数-----------------*/main(){unsignedchardatai,j;unsignedchardataled[]={0,0,0,0,0,0,0,0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0,0,0,0,0,0,0};TMOD=0x10;for(;;){p=led;for(i=0;i<23;i++,p++)delay();}}/*-----------------延时1s函数-----------------*/voiddelay(){unsignedchardataj;for(j=0;j<0x14;j++){ //设置20次循环次数
TH1=0x3c; //置定时器初值
TL1=0xb0;TR1=1; //启动定时器1while(!TF1){scanled(p);};//查询计数是否溢出,即定时50ms时间到,TF=1TF1=0; //50ms时间到,将定时器溢出标志位TF清0TH1=0x3c; //重新置计数器初值
TL1=0xb0;}}/*-----------------LED扫描函数-----------------*/voidscanled(unsignedchar*n){unsignedchardatai,temp=0x01;CTRL8155=0x03; //C口输入,A,B口输出
for(i=0;i<8;i++) {PORTA8155=0xff; PORTB8155=n[i]; //B口送段码
PORTA8155=~temp; //A口选位
temp<<=1;delay1(20);}}/*-----------------延时函数-----------------*/voiddelay1(unsignedchart){unsignedchardatai;for(i=0;i<t;i++);}3)矩阵式键盘程序设计
(1)硬件电路连接。矩阵式键盘由行线和列线组成,按键位于行列的交叉点上。实验板中4×4矩阵式键盘由可编程并行接口芯片8155控制,行线与8155的PC0~PC3相连,列线与8155的PA0~PA3相连,值得注意的是PA口同时又是8位动态LED的位选线。
(2)软件设计思路。矩阵式键盘扫描一般采用逐列扫描法,即将键盘的列线逐一清0,然后读取行线的值。如果行线的值都为1,则表示该列没有键按下;否则表示该列有键按下,但该列有4个按键,到底是哪个按键按下了,由行值中为0的位确定。
针对实验板上的电路,将列值逐一清0,也就是分别往8155的A口依次写入下列值:0xfe、0xfd、0xfb、0xf7,将其定义成如下数组:unsignedcharlie[]={0xfe,0xfd,0xfb,0xf7};0xfe:将PA0连接的列清0,列号为0;0xfd:将PA1连接的列清0,列号为1;0xfb:将PA2连接的列清0,列号为2;0xf7:将PA3连接的列清0,列号为3。定义变量unsignedchara存放列号,a的取值为0~3。
如果某一列有键按下,那么从C口读取的行值可能为下面的值:0x0e,0x0d,0x0b,0x07,将其定义成如下数组:unsignedcharsum[]={0x0e,0x0d,0x0b,0x07};0x0e:与PC0连接的行有键按下,行号为0;0x0d:与PC1连接的行有键按下,行号为1;0x0b:与PC2连接的行有键按下,行号为2;0x07:与PC3连接的行有键按下,行号为3。定义变量unsignedcharb存放行号,b的取值为0~3。
把16个按键对应的键值定义成如下的二维数组:unsignedcharkey[4][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11},{12,13,14,15}};
该数组的第一个下标为行号,第二个下标为列号,即key[0][0]为第0行第0列的键值,k[1][3]为第1行第3列的键值。键盘扫描程序一般包括以下3步:第一步:判断是否有键按下。将列值(全0)写入8155的A口,从C口读取行值,若行值不全为1,则表示有键按下,软件延时去抖动,再次用同样的方法判断是否有键按下。若第二次判断也有键按下,则可以确定有键按下;若第二次判断无键按下,则可能第一次为误判断,确定为无键按下,继续下一次写入列值。
值得注意的是,软件延时去抖动这一步不要省略,否则对按键的判断可能会产生误判断。在使用动态扫描显示的程序中,一般采用动态扫描显示程序作为去抖动的延时。第二步:求按键位置并得到键值。将四列依次置为0,送到8155的A口,读取行值,若行值不全为1,则确定该列有键按下,假设此列号为a;将读取的行值采用逐一查询的方法,从行号b=0开始,判断行值与预先定义的行值数组中的元素是否相等,若相等即可得到行号b,同列号a一起确定按键位置,从而将键值数组key中相应的键值取出来,该程序段如下:for(b=0;b<4;b++){ //查询按下的行号
if(c==sum[b]){ //c中为读取的行值
save=key[b][a]; //得到键值保存到变量save中
break;}
由于保存键值的变量save的值在main函数中修改,又在显示函数display中使用,因此将其定义成外部变量。
第三步:判断闭合的按键是否释放。判断按键是否释放的算法同判断是否有键按下的算法正好相反,判断有键按下的标志为:读取的行值不全为1,而判断按键是否释放的标志是:读取的行值全为1。键盘扫描源程序代码。//ex6.c#include"reg51.h"#include"absacc.h"#defineCTRLXBYTE[0x4400]#definePAXBYTE[0x4401]#definePBXBYTE[0x4402]#definePCXBYTE[0x4403]voiddisplay();voiddelay1(unsignedchart);unsignedcharsave; //保存键值,初值为0unsignedcharledd[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//定义LED显示码/*-----------------main函数-----------------*/main(){unsignedchara,b,c;unsignedcharlie[]={0xfe,0xfd,0xfb,0xf7}; //逐次选中键盘的列的码
unsignedcharsum[]={0x0e,0x0d,0x0b,0x07}; //相应行值
unsignedcharkey[4][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11},{12,13,14,15}};//键值
CTRL=0x03; //写控制字,C口输入,A、B输出
PB=0x00; //屏蔽B口
save=0x00;while(1){PB=0x00; //关LED显示
PA=0x00; //A口输出全0c=PC; //读C口
c=c&0x0f; //屏蔽掉C口高4位
if(c==0x0f)continue; //若列线为全1,则表示没有键按下,重新判断
else{ //否则,延时去抖,第二次判断是否有键按下
delay(20);PA=0x00;c=PC;c=c&0x0f;if(c==0x0f)continue;//第二次判断无键按下,重新判断
else{ //两次判断均有键按下,表示的确有键按下
for(a=0;a<4;a++){ //逐次选中键盘的列,确定按键位置
PA=lie[a];c=PC; //读C口行值c=c&0x0f; //屏蔽高四位
if(c!=0x0f)break;}for(b=0;b<4;b++){ //查询按下的行号
if(c==sum[b]){save=key[b][a];break; //得到键值保存到变量save中
}}do{display(); //调用显示函数,并等待按键是否释放
PB=0x00; //关显示
PA=0x00; //判断按键是否释放
c=PC; //读C口行值
c=c&0x0f; }while(c!=0x0f);//屏蔽高四位
}}}}/*-----------------显示函数-----------------*/voiddisplay(){unsignedchardatai;for(i=0;i<10;i++){PA=0x7f; //无键按下,送LED显示位码
PB=ledd[save];} //将键值对应的显示码送LED}/*-----------------延时函数-----------------*/voiddelay1(unsignedchart){unsignedchardatai;for(i=0;i<t;i++);}
5.实训分析与总结
1)外部RAM与扩展I/O地址的C51定义
MCS-51单片机扩展外部I/O口采用与片外RAM相同的寻址方法,所有扩展的I/O口以及通过扩展I/O口连接的外设都与片外RAM统一编址,在汇编语言程序设计中,使用以下指令访问外部I/O口地址:MOVX@DPTR,A ;寻址外部64K字节地址范围0000H~FFFFHMOVX A,@DPTRMOVX @Ri,A ;寻址低256字节地址范围00H~FFHMOVX A,@Ri
而在C51程序设计中,如何定义外部RAM和扩展I/O口的地址呢?首先在程序中必须包含“absacc.h”绝对地址访问头文件,然后用关键字XBYTE来定义I/O口地址或外部RAM地址。#include“absacc.h"#defineCTRL8155XBYTE[0x4400] //定义8155控制口地址#definePORTA8155XBYTE[0x4401] //定义8155的A口地址#definePORTB8155XBYTE[0x4402] //定义8155的B口地址#definePORTC8155XBYTE[0x4403] //定义8155的C口地址有了以上定义后,就可以直接在程序中对已定义的I/O口名称进行读写了,例如:
CTRL8155=0x43;
在绝对地址访问头文件absacc.h中,定义了MCS-51单片机所有存储区域的绝对地址访问关键字CBYTE、DBYTE、PBYTE和XBYTE,可以对相应的存储区域的绝对地址进行字节寻址。其中包括,CBYTE寻址CODE区,DBYTE寻址DATA区,PBYTE寻址分页XDATA区(低256字节),XBYTE寻址XDATA区。如果要访问外部数据存储区域0x2000处的内容,可以使用如下语句:unsignedcharval;val=XBYTE[0x2000];
也可以像例程ex4.c中一样,将绝对地址先预定义成一个易于识别的符号,如CTRL8155、PORTA8155等。2)C51中数组定义
FranklinC51编译器支持ANSIC语言中的构造数据类型,包括数组、结构、共用体、枚举等。在C51程序中定义和使用数组的方法与ANSIC语言中相同。例程ex4.c中将显示码定义成了一个一维数组led:Unsignedcharled[]={0,0,0,0,0,0,0,0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f, 0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0,0,0,0,0,0,0};
该数组在存储器中的存放方式:占据了以符号地址led为首地址的一串连续的字节位置。在例程中,由于LED扫描函数voidscanled(unsignedcharn[])的入口参数是8个显示码的首地址,因此在main函数中调用LED扫描函数scanled的格式如下: scanled(led+i);其中,led+i(i是0~22之间的整数)表示该数组元素的地址,当i=0时,led+i就是该数组的首地址;当i=1时,led+i就是该数组的第一个元素的地址,依此类推。利用指针访问数组也是一个比较好的方法。3)C51中指针定义修改例程ex4.c的源代码如下://ex4_1.c,本程序中的斜体部分为修改内容#pragmaSMALL#include"REG51.H" //定义头文件#include"absacc.h"#defineCTRL8155XBYTE[0x4400] //采用绝对地址访问方式定义8155口地址#definePORTA8155XBYTE[0x4401]#definePORTB8155XBYTE[0x4402]#definePORTC8155XBYTE[0x4403]voidscanled(unsignedchar*n); //函数声明,LED扫描函 数,该函数将8个 //LED轮流扫描一遍,入口参数为8个显示码存放的首地址voiddelay(unsignedchart); //延时函数,入口参数t确定延时时间,用 于控制每位显示间隔时间/*-----------------main函数-----------------*/main(){unsignedchari,j;unsignedcharled[]={0,0,0,0,0,0,0,0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0,0,0,0,0,0,0};unsignedchar*p; //定义无符号字符指针
CTRL8155=0x03; //写入8155控制字C口输入,A,B口输出
for(;;){p=led; //数组首地址赋给指针
for(i=0;i<23;i++,p++) //i增1的同时,指针也增1for(j=0;j<100;j++)scanled(p); //将指针p中的地址作为实参
}}/*-----------------LED扫描函数-----------------*/voidscanled(unsignedchar*n){unsignedchari,temp=0x01;for(i=0;i<8;i++) {PORTA8155=0xff; PORTB8155=*(n+i);//B口送段码
PORTA8155=~temp;//A口选位
temp<<=1;delay(50);}}/*-----------------延时函数-----------------*/voiddelay(unsignedchart){unsignedchari;for(i=0;i<t;i++);}
比较例程ex4_1.c和ex4.c会发现,二者的不同之处在于:ex4.c采用数组名表示该数组的首地址,从而访问数组中的数据;而ex4_1.c中采用指针访问数组中的数据,首先把数组的首地址赋给指针,然后通过指针增1来修改该地址,从而访问数组中的不同数据。指针是C语言的一个重要概念,也是C语言的重要特色之一。FranklinC51编译器支持“一般指针”和“基于存储器”的指针。一般指针和ANSIC语言中的指针定义相同,需3个字节:第一个字节为存储器类型,第二个字节为高字节偏移地址,第三个字节为低字节偏移地址。一般指针可以被用来指示MCS-51单片机存储器中的任何类型的变量。
例如在例程ex4_1.c中定义的一般指针如下:
unsignedchar*p; //定义无符号字符指针一般指针定义时也可以使用前面介绍过的data、idata、pdata、xdata等关键字对指针的存储位置进行声明,例如:char*xdatastr; //存放在xdata的通用指针int*datanum; //存放在data的通用指针基于存储器的指针以存储器类型为参考,一般来说,在定义的时候包含了数据类型和数据空间的说明,例如:chardata*str; //data的字符串指针intxdata*num; //xdata的int指针longcode*pow; //code的long指针
因为数据类型会在编译的时候处理,所以基于存储器的指针不需要用来存放数据类型的字节,它只需要一个字节(当数据类型为idata、data、bdata、pdata时)或者两个字节(当数据类型为code、xdata时)。与一般指针相比,少了一个字节来指示类型,所以在程序执行时速度快一些。同一般指
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 广州铁路职业技术学院《采矿工程》2023-2024学年第一学期期末试卷
- 2025年浙江省安全员B证(项目经理)考试题库
- 2025黑龙江省安全员考试题库附答案
- 2025年-河北省安全员《B证》考试题库
- 《电影天堂》课件
- 植物的逆境生理-课件
- 上海市初中劳技试题解析2014
- 【大学课件】国际投资的企业形式
- 《植物病原病毒》课件
- 《探析权健肿瘤医院》课件
- 矿山隐蔽致灾普查治理报告
- 零星维修工程 投标方案(技术方案)
- 护理基础测试题+参考答案
- 副总经理招聘面试题与参考回答(某大型国企)2024年
- 2024年SATACT家教培训合同
- 《ESPEN重症病人营养指南(2023版)》解读课件
- 智慧茶园监控系统的设计
- 2024年宜宾发展产城投资限公司第三批员工公开招聘高频难、易错点500题模拟试题附带答案详解
- 2024年省宿州市“宿事速办”12345政务服务便民热线服务中心招考15名工作人员高频考题难、易错点模拟试题(共500题)附带答案详解
- 2024年安徽省行政执法人员资格认证考试试题含答案
- 中国2型糖尿病运动治疗指南 (2024版)
评论
0/150
提交评论