学习任务一:8位流水灯的设计与调试

任务描述

在Proteus仿真软件和单片机开发板上实现8位流水灯顺序点亮效果,并能控制它们的点亮速度。

任务目标

(1)能正确分析单片机最小系统的电路结构及各部分的功能。

(2)学会根据任务要求,自主设计8位流水灯的硬件电路。

(3)正确理解Keil C语言的基本结构、数据类型、常数、变量、运算符、循环指令及选择指令的知识点。

(4)熟练使用Proteus和Keil μVision3软件,完成程序的设计与调试。

(5)能正确使用STC-ISP-V488程序下载软件,完成程序的下载,并观察开发板上8位流水灯的工作过程。

建议课时:18课时

任务分析

单片机P2口连接8个发光二极管,利用各引脚输出电位的变化,控制发光二极管的亮灭:输出电位为高电平,发光二极管灭;输出电位为低电平,发光二极管亮。为了清楚地分辨发光二极管的点亮和熄灭,编写延时程序,在P2口输出信号由一种状态向另一种状态变化时,实现一定时间的间隔。

任务实施

一、硬件电路设计

1.硬件设计思路

设计思路:利用STC单片机芯片,外加振荡电路、复位电路、控制电路、电源,组成一个单片机最小系统。在最小系统的基础上,利用P2口的8个引脚控制8个发光二极管。由于发光二极管具有普通二极管的共性—单向导电性,因此只要在其两极间加上合适的正向电压,发光二极管即可点亮;将电压撤除或加反向电压,发光二极管即熄灭。根据发光二极管的特性,结合单片机P2口的输出信号,即可实现流水灯的控制效果。

2.电路硬件设计

选用51单片机芯片,该芯片共有40个引脚,如图1-1-1所示。

图1-1-1 单片机芯片引脚

(1)主电源电路

VCC(40脚):接+5V电源,又称电源引脚。

GND(20脚):接地。

(2)时钟电路

单片机时钟信号的提供有两种方式:内部方式和外部方式。

内部方式是指使用内部振荡器,在XTAL1(19脚)和XTAL2(18脚)之间外接石英晶体和陶瓷电容C1和C2[图1-1-2(a)],它们和单片机的内部电路构成一个完整的振荡器,振荡频率和石英晶体的振荡频率相同。电容器C1和C2容量为30pF,石英晶体的振荡频率为12MHz。

图1-1-2 单片机时钟电路

当使用外部信号源为单片机提供时钟信号时,XTAL1为空引脚,XTAL2外接时钟信号,如图1-1-2(b)所示。

本任务使用内部振荡器,因此在XTAL1和XTAL2之间外接12MHz的石英晶体和30pF的陶瓷电容C1和C2即可。

(3)复位电路

复位是单片机的初始化操作,使CPU及其他功能部件都处于一个确定的初始状态,并从这个状态开始工作。除系统正常上电(开机)外,在单片机工作过程中,如果程序运行出错或操作错误使系统处于死机状态,也必须进行复位,使系统重新启动。

复位引脚是第9脚,此引脚连接高电平超过两个机器周期,即可产生复位的动作。以12MHz的时钟脉冲为例,每个时钟脉冲为1/12μs,两个机器周期为2μs,因此,在该引脚上产生一个2μs以上的高电平脉冲,即可产生复位的动作。

复位有上电复位和按键复位两种,如图1-1-3所示。上电复位[图1-1-3(a)]利用复位电路电容充放电来实现;而按键复位[图1-1-3(b)]通过使RST端经电阻器R与+5V电源接通而实现,它兼有自动复位的功能。

图1-1-3 单片机复位电路

电路中R和C组成一个典型的充放电电路,充放电时间T=1/RC。根据理论计算结果可知,选择时钟频率为12MHz,一个机器周期是1μs。只要T>2μs,就可以复位。本任务开发板选用R=10kΩ,C=10μF。

(4)存储器设置电路

31脚EA为复用引脚。当EA为低电平时,系统使用外部存储器。当EA为高电平时,系统使用内部存储器。对于初学者而言,所写的程序比较简单,大多使用内部存储器,所以就把31脚直接接到VCC。

30脚ALE是地址锁存信号,其功能是在存取外部存储器时,将原本在P0的地址信号锁存到外部存储器IC,让P0口空出来,以传输数据。简单讲,当外接存储器电路时,让ALE=1,P0被用作地址总线;让ALE=0,P0被用作数据总线。

29脚PSEN是程序存储器使能端,其功能是读取外部存储器。通常此引脚连接到外部存储器的OE引脚。

相对于前面的引脚,29、30脚比较难以理解。但是只要不用外部存储器,就可以当它们不存在,悬空处理即可。

(5)流水灯控制电路

发光二极管的连接方法:若将它们的阴极连接在一起,阳极信号受控制,即构成共阴极接法,如图1-1-4(a)所示;若将它们的阳极连接在一起,阴极信号受控制,则构成共阳极接法,如图1-1-4(b)所示。由于P2口引脚输出高电位时电压大约是5V,为保证发光二极管可靠工作,必须在发光二极管和单片机输出引脚间连接一只限流电阻。

图1-1-4 发光二极管的接法

本任务选用硅型普通发光二极管,限流电阻取220Ω。

3.硬件电路原理图

单片机的P0、P1和P2端口都是双向的I/O端口,P3端口既可作为普通的I/O端口,又可用于第二功能操作中。在该任务中,选择P2端口作为流水灯的控制端口,实现数据的输入输出。综上分析,得到图1-1-5所示的8位流水灯的电路原理图。

图1-1-5 8位流水灯的电路原理图

根据电路原理图,确定本任务所需要的元器件清单,见表1-1-1。

表1-1-1 海珠桥灯饰元器件清单

4.在Proteus仿真软件上绘制流水灯电路原理图

(1)打开软件

选择“程序”→“Proteus 7Professional”→“ISIS 7Professional”命令,启动Proteus仿真软件,出现ISIS Professional图像编辑窗口,如图1-1-6所示。

图1-1-6 ISIS Professional图像编辑窗口

(2)从Proteus库中选取元器件

以电阻RES为例,讲述元器件的选择方法。

在元件选择器工具栏(Mode Selector Toolbar)中单击选择元器件按钮,单击元器件列表上方的“P”按钮,打开元器件选择窗口,如图1-1-7所示。

图1-1-7 元器件选择窗口

在元器件选择窗口左上角的关键字栏中输入关键字,例如需要电阻就输入“res”,从元件库中选取元器件。以此类推,可以选取单片机、电容、发光二极管、按钮、晶振等元器件。

(3)放置元器件

在对象选择器中单击要放置的元器件(蓝色高亮条表示目前选取的元器件),然后在编辑窗口中合适的位置单击就放置了一个元器件。依次把各元器件放入编辑区中的适当位置。

若要改变元器件的放置方向,可以右击选中元器件后再单击按钮。若要镜像,可以先右击选中元器件,再单击按钮。若要多个元器件一起转向,可先按住左键拖出方框选中多个元器件,再单击相应的操作按钮。

(4)放置电源和地

单击元件选择器工具栏中的端子按钮,在对象选择器中选取电源(POWER)、地(GROUND),分别放置于编辑窗口的合适位置上。

(5)连线

分别单击要连线(元器件引脚、终端、线)的起点和终点,在这两点间会自动生成一条线。若终点在空白处,双击即可结束画线。

(6)元器件属性设置

先左键双击各元器件,在弹出的属性编辑对话框(Edit Component)中,按电路原理图中各元器件的值设置相应的属性。

(7)绘制原理图并保存设计

原理图绘制完毕后,单击“File”→“Save as”保存到指定的文件夹,如图1-1-8所示。

图1-1-8 保存“八位流水灯电路原理图”

二、软件程序设计与调试

1.软件设计思路

利用单片机的P2口来控制8个LED,让这8个LED依次点亮,其设计步骤如下。

当P2口的引脚输出低电平(0)时,其所连接的LED呈现正向偏压而发亮;当引脚输出高电平(1)时,其所连接的LED呈现反向截止而熄灭。因此,我们的程序设计就要让P2.0口接的灯亮,输出为11111110,以十六进制表示为“FE”;延时一段时间后,P2.1口接的灯亮,输出为11111101,以十六进制表示为“FD”。以此类推,周而复始。

2.绘制程序流程图

有了设计思路后,可以将思路转换成流程图,如图1-1-9所示。

图1-1-9 8位流水灯循环点亮程序流程图

3.编写程序

8位流水灯循环点亮的程序如下:

4.在Keil μVision3集成开发环境中新建工程和文件,编写流水灯程序

(1)在“开始”菜单中,选择“程序”→“Keil μVision3”选项,即可进入集成开发环境,如图1-1-10所示。

图1-1-10 Keil μVision3集成开发环境

(2)打开一个项目,启动“工程”菜单下的新建工程命令,出现如图1-1-11所示的对话框。

图1-1-11 保存项目

(3)在“文件名”栏中填写要新增的项目名称,再单击“保存”按钮,出现如图1-1-12所示的对话框。

图1-1-12 选择器件

(4)选择所要使用的CPU芯片,如Atmel公司的AT89C51,再单击“确定”按钮,关闭对话框,出现如图1-1-13所示的对话框。

图1-1-13 添加启动代码

(5)这时系统询问我们要不要将汇编语言的启动代码放入所编辑的项目文件,在此单击“否”按钮关闭此对话框,则在左边产生“目标1”项目,如图1-1-14所示。

图1-1-14 新建项目界面

(6)单击“工程”→“为目标1设置选项”,出现如图1-1-15所示的对话框。

图1-1-15 选择晶振频率

(7)在此对话框中设置此芯片的工作频率为12MHz,然后单击“输出”选项卡,如图1-1-16所示。

图1-1-16 选择产生十六进制文件

(8)选择产生十六进制文件,单击“确定”按钮关闭对话框。

(9)单击“文件”→“新建文件”,编辑区将打开一个全新的编辑窗口,然后在对话框中输入要保存的文件名,后缀名为“.c”,再单击“保存”按钮,如图1-1-17所示。

图1-1-17 保存C文件

(10)选择“目标1”下面的“源代码组1”项,单击鼠标右键,弹出快捷菜单,选择“添加文件到组‘源代码组1’”,如图1-1-18所示。

图1-1-18 添加源代码步骤1

(11)单击刚才编辑的“Text1.c”文件,再单击“Add”按钮,最后单击“Close”按钮,如图1-1-19所示,即可把Text1.c文件加入源代码组。

图1-1-19 添加源代码步骤2

(12)在编辑窗口输入源代码,接着单击菜单“工程”→“创建目标”,即可进行编译与连接,并将过程记录在下方的输出窗口,如图1-1-20所示。“0个错误,0个警告”表示没有错误。

图1-1-20 编译与连接

5.调试程序

(1)选择菜单栏中的“调试”→“启动/停止调试”命令,进入程序调试状态。选择菜单栏中的“外围设备”→“I/O-Ports”→“Port 2”命令,弹出Parallel Port 2小窗口,当前P2口的值为0xFF,如图1-1-21所示。

图1-1-21 程序调试状态

(2)采用“单步调试”方式调试。

单击“调试”→“单步”,或者按快捷键F10,光标停在程序第7行,P2口的值为0xFF,如图1-1-22所示。

图1-1-22 单步调试状态1

然后再按F10键三次,黄色箭头走到程序的第10行,P2口的值为0xFE,如图1-1-23所示。依此方法可以完成其他代码的调试。

图1-1-23 单步调试状态2

(3)采用“断点”方式调试。

程序运行至第7行,但是想在第15行设置一个断点,可以用鼠标双击该行或者单击工具栏中的“调试”→“插入/删除断点”,再单击“调试”→“全速运行”按钮。还可以采用“运行到光标处”的方法,即把光标放在第15行,然后单击“调试”→“运行到光标处”按钮,如图1-1-24所示。运行后,黄色箭头走到程序的第15行,如图1-1-25所示。P2口的值为0xFB。断点调试方法可以越过某一段程序,提高调试效率。

图1-1-24 断点调试状态1

图1-1-25 断点调试状态2

6.在Proteus软件中仿真程序

(1)在Proteus软件中打开绘制好的“8位流水灯电路原理图”,双击单片机,弹出如图1-1-26所示的对话框,单击,添加相应的文件,再单击“OK”按钮。

图1-1-26 添加文件

(2)在Proteus软件界面上,单击仿真按钮,即可看到8位流水灯依次点亮的仿真效果,如图1-1-27所示。

图1-1-27 8位流水灯依次点亮仿真效果图

7.在开发板上实现流水灯亮灭

该开发板采用STC12C5A60S2单片机,该单片机和AT89C51单片机的工作原理和编程方法一致。但是STC12C5A60S2单片机的运行速度比AT89C51单片机快6倍。所以编程时,只要把延时时间加长,即可实现异曲同工的效果。下载的具体步骤如下。

(1)把单片机放入40DIP插座中,并卡住。然后用排线把单片机的P2脚与发光二极管相连。最后用USB线把开发板和PC连接在一起,按下电源开关,观察电源指示灯是否亮。若亮说明开发板与PC连接正常,可以工作。

(2)双击PC桌面上的“STC-ISP-V488”图标,启动STC单片机程序下载软件,如图1-1-28所示。在“MCU Type”框中选择STC12C5A60S2单片机,单击“打开程序文件”,添加相应的HEX文件,选择对应的COM口,本书实例中的计算机分配COM3口。

图1-1-28 STC程序下载软件界面

(3)关闭开发板电源,单击“Download/下载”,稍等片刻,按下电源按钮,等待下载完毕,在信息栏中可以看见程序的下载过程。

(4)下载完成后,即可看见开发板上的8个发光二极管依次亮灭,实现了该任务的要求,如图1-1-29所示。

图1-1-29 开发板上8位流水灯效果

知识点提升

一、移位法实现8位流水灯的灯饰效果

上述任务的程序设计采取的方法是传送法,原理是利用二极管的单向导电性,直接给P2口送一个数据,从而控制发光二极管的亮灭。如果有8个发光二极管,就编写8小段程序。如果有较多的发光二极管,则该编程方法不够科学。因此,编者设计了另一种方法:移位法。程序如下:

从程序设计上可以看出,本实例采取循环移位法,首先左移7次,再右移7次,如此不断循环。左移采用“LED<<1”指令,右移采用“LED>>1”指令。对于计数循环方式,采用for语句即可达到目的。

LED的初始值为11111110,左移时,右边将移入0,变成11111100,所以,必须将最右边的位变为1。我们在左移后再利用OR运算,即“LED=(LED<<1)|0x01”指令,可将11111100变成11111101。在进行右移时,可应用“LED=(LED>>1)|0x80”。

二、查表法实现8位流水灯的灯饰效果

除了传送法、移位法,编者还设计了查表法供读者参考,程序如下:

从程序可看出,如果编写各种各样的代码,流水灯可以实现丰富多彩的流水效果,如从左到右依次亮、从右到左依次亮、从两边向中间亮、从中间向两边亮、闪烁两次等。这留给读者慢慢思考。

知识点链接

一、单片机的定义

微型计算机系统包括中央处理单元、存储器及输入/输出单元三大部分。中央处理器就像人体的大脑,控制整个系统的运行;存储器存放系统运行所需的数据及程序,包括数据存储器和程序存储器;输入/输出单元是计算机系统与外部沟通的管道。这三部分分别由不同的元件组成,然后把它们组装在电路板上,形成一个微型计算机系统。

单片机与微型计算机有相似之处,它是把中央处理器、数据存储器、程序存储器、定时/计数器以及输入/输出接口电路等主要功能部件集成在一块芯片上的微型计算机。

单片机具有结构简单、控制功能强、可靠性高、性价比高等特点,被广泛应用于工业控制、智能仪器仪表、家用电器、电子玩具等领域。

二、单片机的分类

单片机生产厂商多、型号种类多,但是都以传统的8051单片机作为内核。

1.Intel公司的单片机

30年前,Intel公司推出了MCS-51系列单片机,它的基本芯片是8031、8051和8751,后来又推出了低功耗单片机,如80C31、80C51、80C52等,虽然型号不同,但是都是8051单片机的派生产品。现在这些单片机虽然停产了,但是Intel公司的8051是单片机领域的奠基石,成为后续单片机发展良好的技术平台。

2.Atmel公司的单片机

Atmel公司推出的MCS-51单片机在市场上占有一定的比例,它提供了丰富的外围接口和内部资源,常用的型号有AT89C51、AT89C52、AT89C51、AT89S52,同样也是8051的派生品。其中AT89S系列单片机具有系统编程ISP功能,无需专用的仿真器或编程器,只要通过ISP下载线和软件,就可以把程序下载到单片机中,在嵌入式控制领域得到了广泛的应用。

3.STC公司的单片机

STC公司是中国本土MCU领航者,生产了STC10、STC11、STC12C5A等系列单片机,它们是高速、低功耗、超强抗干扰的新一代8051单片机,其指令完全兼容传统的8051单片机,而且速度要快8~12倍。采用串口下载程序,其内部资源与Atmel公司的单片机差不多。

三、单片机的内部结构

单片机发展至今,虽然有许多厂商各自开发了不同的兼容芯片,但其基本结构并没有多大变化,图1-1-30为8051单片机的内部结构。

图1-1-30 8051单片机内部结构

1.中央处理器(CPU)

中央处理器由运算器和控制器组成,完成数据运算和控制功能。其中,运算器包括8位算术逻辑单元(ALU)、8位累加器(ACC)、8位暂存器、寄存器和程序状态寄存器,控制器包括指令寄存器(IR)、程序计数器(PC)、指令译码器(ID)等。

2.存储器

程序存储器(ROM):内部4KB,外部最多可扩展至64KB。

数据存储器(RAM):内部128B,外部最多可扩展至64KB。

3.I/O端口

4个8位双向I/O端口,即P0、P1、P2和P3。

4.全双工串行通信接口

该接口可以实现单片机与单片机之间或者单片机与计算机之间的数据通信。

5.定时器/计数器与中断

8051单片机内部有两个16位定时器/计数器(T0和T1),可实现定时或计数的功能。还有5个中断源,即INT0、INT1、T0、T1和RXD/TXD,可以进行中断源高、低优先级设置。

6.振荡电路

在单片机的XTAL1和XTAL2两端外接石英晶体即可产生一定频率的时钟信号。

四、单片机的存储器结构

存储器主要包括片内数据存储器、片外数据存储器、片内程序存储器和片外程序存储器。其中,程序存储器和数据存储器是独立编址的。不同单片机的片内存储器大小不尽相同,但它们的结构较为相似,单片机存储器结构如图1-1-31所示。

图1-1-31 单片机存储器结构

1.程序存储器

单片机程序存储器用于存放编译器编译出的二进制程序代码和程序执行过程中不会改变的原始数据,8051内核单片机的片内ROM大小不相同,如AT89C51片内有4KB的ROM,AT89S52片内有8KB的ROM,ST12C5A6052片内有60KB的ROM。由于当前单片机的内部ROM容量很大,一般不需要外扩ROM。

AT89C51片内外的ROM是统一编址的,如图1-1-31(a)所示。若单片机EA引脚为高电平,则执行片内ROM中的程序(地址范围0000H~0FFFH,即4KB地址);如果片外加有ROM(地址范围1000H~FFFFH),则CPU执行完内部的ROM指令,就会自动执行片外的ROM指令。若EA引脚为低电平,则只能从片外ROM开始执行。

程序存储器中有6个特殊的地址,见表1-1-2。相邻中断源入口地址的间隔为8个单元。在汇编语言中,当程序中断时,一般在这些入口地址处编写一条跳转指令,而相应的中断服务程序编写在转移地址中;如果没有用到相应的中断功能,这些特殊地址单元也可作为一般程序存储器用于存放程序代码。在C语言中,Cx51编译器会自动跳转到相应的中断入口地址,用户只要写好中断服务程序,其他事情由编译器完成。

表1-1-2 程序存储器中的特殊地址

2.数据存储器

AT89C51单片机片内数据存储器共有256字节(单元),分成两个部分:低128字节(地址00H~7FH)和高128字节(地址80H~FFH)。在这一区间的数据存储器又可分为4个部分,如图1-1-31(b)所示。

(1)工作寄存器区

一般8951内核单片机有4个工作寄存器区,占据内部RAM的00H~1FH,共计32个存储单元,可存放32字节的数据。具体配置见表1-1-3。

表1-1-3 寄存器组的选择

一般通过程序状态寄存器(PSW)中RS1和RS0位的组合状态来决定当前工作寄存器为4组中的哪一组。在C语言编程过程中,一般不会直接使用工作寄存器组,但是在汇编语言和C语言混合编程时,工作寄存器组是汇编子程序和C语言函数之间重要的数据传递工具。

(2)位寻址区

片内RAM的20H~2FH单元为位寻址区,共16个单元、128位,位寻址区既可进行字节操作,又可以对单元中的每一位进行位操作。

(3)用户RAM区

片内RAM的30H~7FH单元为用户RAM区,共80个单元,编程者可以用它来存放数据,一般应用中常把堆栈开辟在此区中。

(4)专用寄存器区

片内RAM的高128字节地址80H~FFH为专用寄存器区,存放的是特殊功能寄存器的地址。以汇编语言编写程序时,必须熟练掌握这些寄存器。若以C语言编写程序,它们就不是那么重要了。在程序的声明区放置Keil C所提供的“reg51.h”头文件,只要把它包含到程序里即可,而不必记忆这些位置。本书采用C语言编程,故不一一介绍特殊功能寄存器。

五、Keil C语言的基本结构

一般来说,C语言的程序可看成由一些函数所构成,其中的主程序是以“main”开始的函数,而每个函数可视为独立的个体,就像个模块一样,所以C语言是一种模块化的程序语言。C程序的基本结构如图1-1-32所示,其中各项说明如下。

图1-1-32 C语言的基本结构

1.指定头文件

头文件是一种预先定义好的基本数据。C51程序里,头文件是定义51单片机内部寄存器地址的数据。指定头文件的方式有两种。

第一种方式:#include〈头文件文件名〉。若采用这种方式,编译程序将从Keil μVision3的头文件夹中査找所指定的头文件。

第二种方式:#include“包含头文件文件名”。若采用这种方式,编译程序将从源程序所在文件夹中查找所指定的头文件。

2.声明区

在指定头文件之后,可声明程序之中所使用的常数、变量、函数等,其作用域将扩展至整个程序,包括主程序与所有函数。在此建议,若程序中用到函数,则可在此先声明所有用到的函数。这样,函数放置的先后顺序将不会受到影响。换言之,函数放置在引用该函数的程序之前或之后都可以。若没有在此声明函数,则在使用函数之前必须先定义该函数。

3.主程序

主程序(主函数)以main()开头,整个内容放置在一对大括号,即{ }里,分为声明区与程序区。在声明区里所声明的常数、变量等仅适用于主程序,而不影响其他函数。若在主程序之中使用了某变量,但在之前的声明区中没有声明,也可在主程序的声明区中声明。另外,程序区就是以语句所构成的程序内容。

4.函数定义

函数是一种具有独立功能的程序,其结构与主程序类似。可将所要处理的数据传入函数中,称为形式参数;也可将函数处理完成后的结果返回给调用它的程序,称为返回值。不管是形式参数还是返回值,在定义函数的第一行都应该交代清楚。其格式如下:

例如,要将一个无符号字符(unsigned char)实参传递给函数,函数执行完成时要返回一个整型(int)数据,此函数的名称为My_func,则函数定义为

若不要传入函数,则可在小括号内指定为void。同样,若不要返回值,则可在函数名称左边指定为void或不指定。另外,函数的起始符号、结束符号、声明区及程序区都与主程序一样。在一个C语言的程序里可使用多个函数,并且函数中也可以调用函数。

5.注释

注释就是说明,属于编译器不处理的部分。C语言的注释以“/*”开始,以“*/”结束,放置注释的位置可接续于语句完成之后,也可独立于一行。其中的文字,可使用中文。另外,也可以输入“//”,其右边整行都是注释。

六、数据类型

数据是计算机处理的对象,任何程序设计都要进行数据处理。C语言中包括字符(char)、整型(int)、浮点数(float)、空类型(void)、位类型(bit)、可寻址位(sbit)和特殊功能寄存器(sfr)几大类数据,具体见表1-1-4。

表1-1-4 C语言的数据类型

七、常量

在程序运行的过程中,其值不能改变的量,称为常量。其数据类型分为整型、浮点型、字符型、位类型和字符串型。常量的特点如下。

(1)整型常量可以表示为十进制数、十六进制数或八进制数等,如十进制数10、-40等。十六进制数以0x开头,如0x13、0xAB等;八进制数以字母o开头,如o13、o27等。

(2)浮点型常量可以分为十进制和指数两种表示形式,如0.456、5895.568、234e4等。

(3)字符型常量是用单引号括起来的单一字符,如'A'、'5'。

(4)字符串型常量是用双引号括起来的一串字符,如"51"、"hello"等。

(5)位类型的值是一个二进制数,即0或1。

根据上述常量特点可知,常量可以是数值常量,也可以是符号常量。数值常量可以在程序中直接引用,如a=15、a=2.65、a='c'等;但是符号常量不能直接使用,在使用之前必须用编译预处理命令“#define”先进行定义,例如:

在此语句之后的程序中,PI字符常量的值都为3.14,这样便于程序修改。一般把程序中多处出现的同一常量用字符常量来代替,若要修改常量的值,可在预定义的时候修改。

八、变量

在程序运行中,其值可以改变的量称为变量。一个变量主要由两部分构成,一部分是变量名,另一部分是变量值。每个变量都在内存中占据一定的存储单元(地址),并在该内存单元中存放该变量的值。

1.变量的定义

变量必须先定义后使用,用标识符作为变量名,并指出所用的数据类型和存储模式,这样编译系统才能为变量分配相应的存储空间。变量的定义格式如下:

其中,数据类型和变量名表是必需的;存储种类和存储类型是可选项,一般忽略。例如:

进行变量定义时,应注意以下几点。

(1)允许在一个数据类型标识符后,声明多个相同类型的变量,各变量名之间用逗号隔开。

(2)数据类型标识符与变量名之间至少用一个空格隔开。

(3)最后一个变量名必须以“;”结尾。

(4)变量声明必须放在变量使用之前,一般放在函数体的开头部分。

(5)在同一个程序中变量不允许重复定义。

例如:

2.变量的初始化

在定义变量的同时可以给变量赋初值,称为变量的初始化。变量初始化的一般格式为:

例如:

3.变量存储类型

单片机的存储器可以分为程序存储器(ROM)和数据存储器(RAM),它们又可以分为片内ROM、片外ROM、片内RAM和片外RAM四种物理存储空间。C51编译器支持这四种物理存储空间,见表1-1-5。

表1-1-5 存储器形式

C51内部的4KB程序存储器可扩展至64KB,程序存储器可以存放程序代码,也可以存放固定的数据,如七段数码显示器的显示码、LED点阵的显示码、LCM的显示字符串,以下就是七段数码显示器的显示码。

4.变量的作用范围

变量的作用范围或有效范围与该变量在哪里声明有关,大致可分为两种,说明如下。

(1)全局变量

在程序开头的声明区或没有大括号限制的声明区所声明的变量,其作用范围为整个程序,称为全局变量,如图1-1-33所示。其中的LED、SPEAKER就是全局变量。

图1-1-33 全局变量与局部变量

(2)局部变量

在大括号内的声明区所声明的变量,其作用范围将受限于大括号,称为局部变量,图1-1-34中的i、j就是局部变量。若在主程序与各函数之中都声明了相同名称的变量,则脱离主程序或函数时,该变量将自动无效,又称自动变量。

如图1-1-34所示,在主程序与delay子程序中各自声明了i、j变量,但主程序中的i、j与delay子程序中的i、j是各自独立的。

图1-1-34 局部变量

九、Keil C的运算符

运算符就是程序语句中的操作符号,Keil C的运算符可分为以下几种。

1.算术运算符

算术运算符是执行算术运算功能的操作符号,有加、减、乘、除、取余数、自增和自减运算符(表1-1-6)。

表1-1-6 算术运算符

程序范例:

程序结果:A=0x09、B=0x05、C=0x0E、D=0x03、E=0x01、x=0x08、y=0x01。

2.关系运算符

关系运算符用于处理两变量间的大小关系,见表1-1-7。

表1-1-7 关系运算符

程序范例:

程序结果:A=0x00、B=0x01、C=0x01、D=0x00、E=0x01、F=0x00。

3.逻辑运算符

逻辑运算符就是执行逻辑运算功能的操作符号,逻辑运算包括与、或和反相运算,其结果为1或0,见表1-1-8。

表1-1-8 逻辑运算符

程序范例:

程序结果:A=0x01、B=0x01、C=0x00。

4.布尔运算符

布尔运算符与逻辑运算符非常相似,两者最大的差异在于布尔运算符针对变量中的每一位,逻辑运算符则对整个变量进行操作。布尔运算符见表1-1-9。

表1-1-9 布尔运算符

程序范例:

程序结果:A=0x21、B=0x77、C=0x56、D=0xdA、E=0x28、F=0x02。

5.赋值运算符

赋值运算符是一种很有效率且特殊的操作符号,包括最常见的“=”,还有将算术运算、逻辑运算变形的操作符号,见表1-1-10。

表1-1-10 赋值运算符

程序范例:

程序结果:A=0x96、B=0xd0、C=0x6B、D=0x4e、E=0x01、F=0x11、G=0x90、H=0x9f、I=0xC3、J=0xa0、K=0x0e。

十、Keil C的循环指令

循环指令就是将程序流程控制在指定的循环里,直到符合指定的条件才脱离循环继续往下执行。Keil C所提供的循环指令有for语句、while语句、do-while语句。

1.for语句

for语句是一个很实用的计数循环,其格式如下:

其中有3个表达式,说明如下。

表达式1为初始值。例如,从0开始则写成“i=0;”,其中的i必须事先声明。“;”是分隔符,不可缺少。

表达式2为判断条件,以此为执行循环的条件。例如“i<20;”,则只要i<20就继续执行循环。若此表达式空白,只输入“;”,如“for(i=0;;i++)”或“for(;;)”,则会无条件执行循环,不会跳出循环。

表达式3为条件运算方式,最常见的是自增或自减,如“i++”或“i--”;当然也可以是其他运算方式,如每次增加2,即“i+=2”。

范例1:for(i=0;i<8;i++),说明循环执行8次。

范例2:for(x=100;x>0;x--),说明循环执行100次。

范例3:for(;;),说明是无限循环。

范例4:for(num=0;num<99;num+=5),说明循环执行20次。

在for语句下面,可利用一对大括号将所要执行的指令逐行写入。例如,用for语句求1~100的累加和,代码如下:

上述for语句的执行过程:先给i赋值1,判断i是否小于等于100,若是,则执行循环体“sum=sum+i”语句一次,然后i自增1,再重新判断,直到i=101时,条件i<=100不成立,循环结束。

若循环中只执行一条指令,可不使用大括号。例如,要从0到9,将table数组中的数据顺序输出到P2,代码如下:

2.while语句

while语句的一般格式如下:

while语句循环原理:“表达式”通常是逻辑表达式或关系表达式,为循环条件;“语句组”是循环体,即被重复执行的程序段。该语句的执行过程如下:首先判断“表达式”的值,当值为真时,执行“语句组”;否则,就不执行。例如,求整数1~100的累加和,代码如下:

变量i的取值范围为1~100,所以初值为1,while语句的条件“i<=100”,终值为100,循环次数为100。

while语句使用过程中的注意事项如下:

(1)使用while语句时要注意,当表达式的值为真时,执行循环体,循环体执行一次完成后,再次回到while,进行循环条件判断,如果仍然为真,则重复执行循环体程序;为假则退出整个while循环语句。

(2)如果循环条件一开始就为假,那么while后面的循环体一次都不会执行。

(3)如果循环条件总为真,如while(1),表达式为常量1,循环条件永远成立,则为无限循环,即死循环。在单片机C语言程序设计中,无限循环是一个非常有用的语句,在上述程序示例中都使用了该语句。

3.do-while语句

while语句是在执行循环体之前进行循环条件判断,如条件不成立,则该循环语句组不执行。但是有时候需要先执行一次循环体后,再进行循环条件的判断。do-while语句可以满足这种要求。do-while语句的一般格式如下:

do-while语句循环原理:先执行循环体“语句组”一次,再计算“表达式”的值,如果“表达式”的值为真,则继续执行循环体“语句组”,直到表达式的值为假为止。

do-while语句使用过程中的注意事项如下:

(1)在使用if语句、while语句时,表达式括号后面都不能加分号,但在do-while表达式括号后必须加分号。

(2)do-while语句与while语句相比,更适合处理不论条件是否成立,都需要先执行一次循环体的情况。

4.在循环体中使用break和continue语句

(1)break语句

当break语句用于while、do-while和for循环语句时,不论循环条件是否满足,都可以使程序立即终止整个循环而执行后面的语句。通常break语句总是与if语句一起使用,即满足if语句的条件时便跳出循环。例如:

在上述程序中,如果if语句的条件成立,则运行break语句,程序就跳出for循环体,执行sum1=sum语句。

(2)continue语句

continue语句的作用是结束本次循环,强行执行下一次循环。它与break语句的不同之处在于:break语句是直接结束整个循环,而continue语句是结束当前循环体的执行,再次进入循环条件判断,准备继续开始下一次循环体的执行。例如,求出1~100范围内所有不能被5整除的整数之和,代码如下:

程序分析:设置了一个for循环语句并进行if语句判断,若i对5取余运算的结果为0,即能被5整除,则执行continue语句,退出本次循环;若不成立,则跳过continue语句,执行sum=sum+i。再重新进行for循环条件判断。

十一、Keil C的选择语句

选择指令是按条件决定程序流程。Keil C所提供的选择指令有if-else语句及switch-case语句。

1.if-else语句

if-else语句提供条件判断的语句,称为条件选择,其格式如下:

在这个语句中,将先判断表达式是否成立。若成立,则执行循环体1,否则执行循环体2。其中else部分也可以省略,即

if-else语句也可利用else if指令串接为多重条件判断,其格式如下:

在这种流程中,从表达式1开始判断,若表达式1成立,则表达式2和表达式3都没有作用。若表达式1不成立,而表达式2成立,则表达式3没有作用。

2.switch-case语句

if语句一般用于单一条件或分支数目较少的场合,如果使用if语句来编写超过3个分支的程序,就会降低程序的可读性。C语言提供了一种用于多分支选择的switch-case语句,一般格式如下:

在switch-case语句中,表达式的值决定流程,并没有优先级的问题。若没有一个路径的常数与表达式的值相同,程序将执行default路径下的循环体。注意,每个case语句块结束时必须有一个break指令,否则会继续执行下一个case循环体。

任务评估

任务评估见表1-1-11。

表1-1-11 任务评估表