|
第1 章 C 语言概述及其开发环境的建立 学习一种编程语言,最重要的是建立一个练习环境,边学边练才能学好。Keil 软件是目 前最流行开发80C51 系列单片机的软件,Keil 提供了包括C 编译器、宏汇编、连接器、库 管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境 (μVision)将这些部份组合在一起。 在学会使用汇编语言后,学习C 语言编程是一件比较容易的事,我们将通过一系列的 实例介绍C 语言编程的方法。图1-1 所示电路图使用89S52 单片机作为主芯片,这种单片 机性属于80C51 系列,其内部有8K 的FLASH ROM,可以反复擦写,并有ISP 功能,支 持在线下载,非常适于做实验。89S52 的P1 引脚上接8 个发光二极管,P3.2~P3.4 引脚上接 4 个按钮开关,我们的任务是让接在P1 引脚上的发光二极管按要求发光。
1.1 简单的C 程序介绍 例1-1: 让接在P1.0 引脚上的LED 发光。
#include “reg51.h” sbit P1_0=P1^0; void main() { P1_1=0; } 这个程序的作用是让接在P1.0 引脚上的LED 点亮。下面来分析一下这个C 语言程序包 含了哪些信息。 1)“文件包含”处理。 程序的第一行是一个“文件包含”处理。 所谓“文件包含”是指一个文件将另外一个文件的内容全部包含进来,所以这里的程序 虽然只有4 行,但C 编译器在处理的时候却要处理几十或几百行。这里程序中包含REG51.h 文件的目的是为了要使用P1 这个符号,即通知C 编译器,程序中所写的P1 是指80C51 单 片机的P1 端口而不是其它变量。这是如何做到的呢? 打开reg51.h 可以看到这样的一些内容: /*------------------------------------------------------------------------- REG51.H Header file for generic 80C51 and 80C31 microcontroller. Copyright (c) 1988-2001 Keil Elektronik GmbH and Keil Software, Inc. All rights reserved. --------------------------------------------------------------------------*/ /* BYTE Register */ sfr P0 = 0x80; sfr P1 = 0x90; sfr P2 = 0xA0; sfr P3 = 0xB0; sfr PSW = 0xD0; sfr ACC = 0xE0; sfr B = 0xF0; sfr SP = 0x81; sfr DPL = 0x82; sfr DPH = 0x83; sfr PCON = 0x87; sfr TCON = 0x88; sfr TMOD = 0x89; sfr TL0 = 0x8A; sfr TL1 = 0x8B; sfr TH0 = 0x8C; sfr TH1 = 0x8D; sfr IE = 0xA8; sfr IP = 0xB8; sfr SCON = 0x98; sfr SBUF = 0x99; /* BIT Register */ /* PSW */ sbit CY = 0xD7; sbit AC = 0xD6; sbit F0 = 0xD5; sbit RS1 = 0xD4; sbit RS0 = 0xD3; sbit OV = 0xD2; sbit P = 0xD0; /* TCON */ sbit TF1 = 0x8F; sbit TR1 = 0x8E; sbit TF0 = 0x8D; sbit TR0 = 0x8C; sbit IE1 = 0x8B; sbit IT1 = 0x8A; sbit IE0 = 0x89; sbit IT0 = 0x88; /* IE */ sbit EA = 0xAF; sbit ES = 0xAC; sbit ET1 = 0xAB; sbit EX1 = 0xAA; sbit ET0 = 0xA9; sbit EX0 = 0xA8; /* IP */ sbit PS = 0xBC; sbit PT1 = 0xBB; sbit PX1 = 0xBA; sbit PT0 = 0xB9; sbit PX0 = 0xB8; /* P3 */ sbit RD = 0xB7; sbit WR = 0xB6; sbit T1 = 0xB5; sbit T0 = 0xB4; sbit INT1 = 0xB3; sbit INT0 = 0xB2; sbit TXD = 0xB1; sbit RXD = 0xB0; /* SCON */ sbit SM0 = 0x9F; sbit SM1 = 0x9E; sbit SM2 = 0x9D; sbit REN = 0x9C; sbit TB8 = 0x9B; sbit RB8 = 0x9A; sbit TI = 0x99; sbit RI = 0x98; 熟悉80C51 内部结构的读者不难看出,这里都是一些符号的定义,即规定符号名与地 址的对应关系。注意其中有 sfr P1 = 0x90; 这样的一行(上文中用黑体表示),即定义P1 与地址0x90 对应,P1 口的地址就是0x90 (0x90 是C 语言中十六进制数的写法,相当于汇编语言中写90H)。 从这里还可以看到一个频繁出现的词:sfr sfr 并标准C 语言的关键字,而是Keil 为能直接访问80C51 中的SFR 而提供了一个新 的关键词,其用法是: sfrt 变量名=地址值。 2)符号P1_0 来表示P1.0 引脚。 在C 语言里,如果直接写P1.0,C 编译器并不能识别,而且P1.0 也不是一个合法的C 语言变量名,所以得给它另起一个名字,这里起的名为P1_0,可是P1_0 是不是就是P1.0 呢?你这么认为,C 编译器可不这么认为,所以必须给它们建立联系,这里使用了Keil C 的关键字sbit 来定义,sbit 的用法有三种: 第一种方法:sbit 位变量名=地址值 第二种方法:sbit 位变量名=SFR 名称^变量位地址值 第三种方法:sbit 位变量名=SFR 地址值^变量位地址值 如定义PSW 中的OV 可以用以下三种方法: sbit OV=0xd2 (1)说明:0xd2 是OV 的位地址值 sbit OV=PSW^2 (2)说明:其中PSW 必须先用sfr 定义好 sbit OV=0xD0^2 (3)说明:0xD0 就是PSW 的地址值 因此这里用sfr P1_0=P1^0;就是定义用符号P1_0 来表示P1.0 引脚,如果你愿意也可以 起P10 一类的名字,只要下面程序中也随之更改就行了。 3)main 称为“主函数”。 每一个C 语言程序有且只有一个主函数,函数后面一定有一对大括号“{}”,在大括号 里面书写其它程序。 从上面的分析我们了解了部分C 语言的特性,下面再看一个稍复杂一点的例子。 例1-2 让接在P1.0 引脚上的LED 闪烁发光
#include "reg51.h" #define uchar unsigned char #define uint unsigned int sbit P10=P1^0; /*延时程序 由Delay 参数确定延迟时间 */ void mDelay(unsigned int Delay) { unsigned int i; for(;Delay>0;Delay--) { for(i=0;i<124;i++) {;} } } void main() { for(;;) { P10=!P10; //取反P1.0 引脚 mDelay(1000); } } 程序分析:主程序main 中的第一行暂且不看,第二行是“P1_0=!P1_0;”,在P1_0 前有 一个符号“!”,符号“!”是C 语言的一个运算符,就像数学中的“+”、“-”一样,是一种 运算任号,意义是“取反”,即将该符号后面的那个变量的值取反。 注意:取反运算只是对变量的值而言的,并不会自动改变变量本身。可以认为C 编译 器在处理“!P1_0”时,将P1_0 的值给了一个临时变量,然后对这个临时变量取反,而不 是直接对P1_0 取反,因此取反完毕后还要使用赋值符号(“=”)将取反后的值再赋给P1_0, 这样,如果原来P1.0 是低电平(LED 亮),那么取反后,P1.0 就是高电平(LED 灭),反之, 如果P1.0 是高电平,取反后,P1.0 就是低电平,这条指令被反复地执行,接在P1.0 上灯就 会不断“亮”、“灭”。 该条指令会被反复执行的关键就在于main 中的第一行程序:for(;;),这里不对此作详细 的介绍,读者暂时只要知道,这行程序连同其后的一对大括号“{}”构成了一个无限循环语 句,该大括号内的语句会被反复执行。 第三行程序是:“mDelay(1000);”,这行程序的用途是延时1s 时间,由于单片机执行指 令的速度很快,如果不进行延时,灯亮之后马上就灭,灭了之后马上就亮,速度太快,人眼 根本无法分辨。 这里mDelay(1000)并不是由Keil C 提供的库函数,即你不能在任何情况下写这样一行 程序以实现延时。如果在编写其它程序时写上这么一行,会发现编译通不过。那么这里为什 么又是正确的呢?注意观察,可以发现这个程序中有void mDelay(…)这样一行,可见, mDelay 这个词是我们自己起的名字,并且为此编写了一些程序行,如果你的程序中没有这 么一段程序行,那就不能使用mDelay(1000)了。有人脑子快,可能马上想到,我可不可 以把这段程序也复制到我其它程序中,然后就可以用mDelay(1000)了呢?回答是,那当然 就可以了。还有一点需要说明,mDelay 这个名称是由编程者自己命名的,可自行更改,但 一旦更改了名称,main()函数中的名字也要作相应的更改。 mDelay 后面有一个小括号,小括号里有数据(1000),这个1000 被称之“参数”,用它 可以在一定范围内调整延时时间的长短,这里用1000 来要求延时时间为1000 毫秒,要做到 这一点,必须由我们自己编写的mDelay 那段程序决定的,详细情况在后面循环程序中再作 分析,这里就不介绍了。 1.2 Keil 工程的建立 要使用Keil 软件,首先要正确安装Keil 软件,该软件的Eval 版本可以直接去 http://www.keil.com 下载,安装时选择Eval Vision,其它步骤与一般Windows 程序安装类似, 这里就不再赘述了。安装完成后,将Ledkey.dll 文件复制到Keil 安装目录下的C51\BIN 文 件夹下,这是作者提供的键盘与LED 实验仿真板,可与Keil 软件配合,在计算机上模拟LED 和按键的功能。 启动μVison,点击“File??New…”在工程管理器的右侧打开一个新的文件输入窗口, 在这个窗口里输入例1-2 中的源程序,注意大小写及每行后的分号,不要错输及漏输。 输入完毕之后,选择“File??Save”,给这个文件取名保存,取名字的时候必须要加上扩 展名,一般C 语言程序均以“.C”为扩展名,这里将其命名为exam2.c,保存完毕后可以将 该文件关闭。 Keil 不能直接对单个的C 语言源程序进行处理,还必须选择单片机型号;确定编译、汇 编、连接的参数;指定调试的方式;而且一些项目中往往有多个文件,为管理和使用方便, Keil 使用工程(Project)这一概念,将这些参数设置和所需的所有文件都加在一个工程中, 只能对工程而不能对单一的源程序进行编译和连接等操作。 点击“Project->New Project…”菜单,出现对话框,要求给将要建立的工程起一个名字, 这里起名为exam2,不需要输入扩展名。点击“保存”按钮,出现第二个对话框,如图1-2 所示,这个对话框要求选择目标CPU(即你所用芯片的型号),Keil 支持的CPU 很多,这 里选择Atmel 公司的89S52 芯片。点击ATMEL 前面的“+”号,展开该层,点击其中的89S52, 然后再点击“确定”按钮,回到主窗口,此时,在工程窗口的文件页中,出现了“Target 1”, 前面有“+”号,点击“+”号展开,可以看到下一层的“Source Group1”,这时的工程还是 一个空的工程,里面什么文件也没有,需要手动把刚才编写好的源程序加入,点击“Source Group1”使其反白显示,然后,点击鼠标右键,出现一个下拉菜单,如图1-3 所示,选中其 中的“Add file to Group”Source Group1”,出现一个对话框,要求寻找源文件。 双击exam2.c 文件,将文件加入项目,注意,在文件加入项目后,该对话框并不消失, 等待继续加入其它文件,但初学时常会误认为操作没有成功而再次双击同一文件,这时会出 现如图1-4 所示的对话框,提示你所选文件已在列表中,此时应点击“确定”,返回前一对 话框,然后点击“Close”即可返回主接口,返回后,点击“Source Group 1”前的加号,exam3.c 文件已在其中。双击文件名,即打开该源程序。
1.3 工程的详细设置 工程建立好以后,还要对工程进行进一步的设置,以满足要求。 首先点击左边Project 窗口的Target 1,然后使用菜单“Project->Option for target ‘target1’” 即出现对工程设置的对话框,这个对话框共有8 个页面,大部份设置项取默认值就行了。
如图1-5 所示,Xtal 后面的数值是晶振频率值,默认值是所选目标CPU 的最高可用频 率值,该值与最终产生的目标代码无关,仅用于软件模拟调试时显示程序执行时间。正确设 置该数值可使显示时间与实际所用时间一致,一般将其设置成与你的硬件所用晶振频率相 同,如果没必要了解程序执行的时间,也可以不设。
Memory Model 用于设置RAM 使用情况,有三个选择项: Small: 所有变量都在单片机的内部RAM 中; Compact:可以使用一页(256 字节)外部扩展RAM; Larget: 可以使用全部外部的扩展RAM。 Code Model 用于设置ROM 空间的使用,同样也有三个选择项: Small:只用低于2K 的程序空间; Compact:单个函数的代码量不能超过2K,整个程序可以使用64K 程序空间; Larget:可用全部64K 空间; 这些选择项必须根据所用硬件来决定,由于本例是单片应用,所以均不重新选择,按默 认值设置。 Operating:选择是否使用操作系统,可以选择Keil 提供了两种操作系统:Rtx tiny 和
Rtx full,也可以不用操作系统(None),这里使用默认项None,即不用操作系统。
OutPut 页 如图1-6 所示,这里面也有多个选择项,其中Creat Hex file 用于生成可执行代码文件, 该文件可以用编程器写入单片机芯片,其格式为intelHEX 格式,文件的扩展名为.HEX,默 认情况下该项未被选中,如果要写片做硬件实验,就必须选中该项。 工程设置对话框中的其它各页面与C51 编译选项、A51 的汇编选项、BL51 连接器的连 接选项等用法有关,这里均取默认值,不作任何修改。以下仅对一些有关页面中常用的选项 作一个简单介绍。
Listing 页 该页用于调整生成的列表文件选项。在汇编或编译完成后将产生(*.lst)的列表文件, 在连接完成后也将产生(*.m51)的列表文件,该页用于对列表文件的内容和形式进行细致 的调节,其中比较常用的选项是“C Compile Listing”下的“Assamble Code”项,选中该项 可以在列表文件中生成C 语言源程序所对应的汇编代码,建议会使用汇编语言的C 初学者 选中该项,在编译完成后多观察相应的List 文件,查看C 源代码与对应汇编代码,对于提 高C 语言编程能力大有好处。 C51 页 该页用于对Keil 的C51 编译器的编译过程进行控制,其中比较常用的是“Code Optimization”组,如图1.7 所示,该组中Level 是优化等级,C51 在对源程序进行编译时, 可以对代码多至9 级优化,默认使用第8 级,一般不必修改,如果在编译中出现一些问题, 可以降低优化级别试一试。Emphasis 是选择编译优先方式,第一项是代码量优化(最终生成的代码量小);第二项是速度优先(最终生成的代码速度快);第三项是缺省。默认采用速 度优先,可根据需要更改。
Debug 页 该页用于设置调试器,Keil 提供了仿真器和一些硬件调试方法,如果没有相应的硬件调 试器,应选择Use Simulator,其余设置一般不必更改,有关该页的详细情况将在程序调试部分再详细介绍。 至此,设置完成,下面介绍如何编译、连接程序以获得目标代码,以及如何进行程序的 调试工作。
1.4 编译、连接 下面我们通过一个例子来介绍C 程序编译、连接的过程。这个例子使P1 口所接LED 以流水灯状态显示。 将下面的源程序输入,命名为exam3.c,并建立名为exam3 的工程文件,将exam3.c 文 件加入该工程中,设置工程,在Target 页将Xtal 后的值由24.0 改为12.0,以便后面调试时 观察延时时间是否正确,本项目中还要用到我们所提供的实验仿真板,为此需在Debug 页 对Dialog DLL 对话框作一个设置,在进行项目设置时点击Debug,打开Debug 页,可以看 到Dialog DLL 对话框后的Parmeter:输入框中已有默认值-pAT52,在其后键入空格后再输入 -dledkey,如图1-8 所示。 例1-3 使P1 口所接LED 以流水灯状态显示
;流水灯程序 **************************************************/ #include "reg51.h" #include "intrins.h" #define uchar unsigned char #define uint unsigned int /*延时程序 由Delay 参数确定延迟时间 */ void mDelay(unsigned int Delay) { unsigned int i; for(;Delay>0;Delay--) { for(i=0;i<124;i++) {;} } } void main() { unsigned char OutData=0xfe; for(;;) { 图1-8 Debug 选项设置 P1=OutData; OutData=_crol_(OutData,1); //循环左移 mDelay(1000); /*延时1000 毫秒*/ } } 设置好工程后,即可进行编译、连接。选择菜单Project->Build target,对当前工程进行 连接,如果当前文件已修改,将先对该文件进行编译,然后再连接以产生目标代码;如果选 择Rebuild All target files 将会对当前工程中的所有文件重新进行编译然后再连接,确保最终 生产的目标代码是最新的,而Translate ….项则仅对当前文件进行编译,不进行连接。以上 操作也可以通过工具栏按钮直接进行。图1-9 是有关编译、设置的工具栏按钮,从左到右分 别是:编译、编译连接、全部重建、停止编译和对工程进行设置。 编译过程中的信息将出现在输出窗口中的Build 页中,如果源程序中有语法错误,会有 错误报告出现,双击该行,可以定位到出错的位置,对源程序修改之后再次编译,最终要得 到如图1-10 所示的结果,提示获得了名为exam3.hex 的文件,该文件即可被编程器读入并 写到芯片中,同时还可看到,该程序的代码量(code=63),内部RAM 的使用量(data=9), 外部RAM 的使用量(xdata=0)等一些信息。除此之外,编译、连接还产生了一些其它相关 的文件,可被用于Keil 的仿真与调试,到了这一步后即进行调试。
1.5 程序的调试 在对工程成功地进行汇编、连接以后,按Ctrl+F5 或者使用菜单Debug->Start/Stop Debug Session 即可进入调试状态,Keil 内建了一个仿真CPU 用来模拟执行程序,该仿真CPU 功 能强大,可以在没有硬件和仿真机的情况下进行程序的调试。 进入调试状态后,Debug 菜单项中原来不能用的命令现在已可以使用了,多出一个用于 运行和调试的工具条,如图1-11 所示,Debug 菜单上的大部份命令可以在此找到对应的快 捷按钮,从左到右依次是复位、运行、暂停、单步、过程单步、执行完当前子程序、运行到当前行、下一状态、打开跟踪、观察跟踪、反汇编窗口、观察窗口、代码作用范围分析、1 #串行窗口、内存窗口、性能分析、工具按钮等命令。 点击菜单Peripherals,即会多出一项“键盘LED 仿真板(K)”,选中该项,即会出现如图1-12 所示界面。 使用菜单STEP 或相应的命令按钮或使用快捷键F11 可以单步执行程序,使用菜单STEP OVER 或功能键F10 可以以过程单步形式执行命令,所谓过程单步,是指把C 语言中的一 个函数作为一条语句来全速执行。 按下F11 键,可以看到源程序窗口的左边出现了一个黄色调试箭头,指向源程序的第一 行。每按一次F11,即执行该箭头所指程序行,然后箭头指向下一行,当箭头指向 “mDelay(1000);”行时,再次按下F11,会发现,箭头指向了延时子程序mDelay 的第一行。 不断按F11 键,即可逐步执行延时子程序。 如果mDelay 程序有错误,可以通过单步执行来查找错误,但是如果mDelay 程序已正 确,每次进行程序调试都要反复执行这些程序行,会使得调试效率很低,为此可以在调试时 使用F10 来替代F11,在main 函数中执行到mDelay(1000)时将该行作为一条语句快速执行 完毕。
Keil 软件还提供了一些窗口,用以观察一些系统中重要的寄存器或变量的值,这也是很 重要的调试方法。 以下通过一个对延时程序的延迟时间的调整来对这些调试方法作一个简单的介绍。 这个程序中用到了延时程序mDelay,如果使用汇编语言编程,每段程序的延迟时间可 以非常精确地计算出来,而使用C 语言编程,就没有办法事先计算了。为此,可以使用观 察程序执行时间的方法了来解。进入调试状态后,窗口左侧是寄存器和一些重要的系统变量 的窗口,其中有一项是sec,即统计从开始执行到目前为止用去的时间。按F10,以过程单 步的形式执行程序,在执行到mDelay(1000)这一行之前停下,查看sec 的值(把鼠标停在sec 后的数值上即可看到完整的数值),记下该数值,然后按下F10,执行完mDelay(1000)后再 次观察sec 值,如图1-13 所示,这里前后两次观察到的值分别是:0.00040400 和1.01442600, 其差值为1.014022s,如果将该值改为124 可获得更接近于1s 的数值,而当该值取123 时所 获得的延时值将小于1s,因此,最佳的取值应该是124。
1.6 C 语言的一些特点 通过上述的几个例子,可以得出一些结论: 1、C 程序是由函数构成的,一个C 源程序至少包括一个函数,一个C 源程序有且只有 一个名为main()的函数,也可能包含其它函数,因此,函数是C 程序的基本单位。主程序 通过直接书写语句和调用其它函数来实现有关功能,这些其它函数可以是由C 语言本身提 供给我们的(如例3 中的_crol_(…)函数),这样的函数称之为库函数,也可以是用户自 己编写的(如例2、3 中用的mDelay(…)函数),这样的函数称之为用户自定义函数。那 么库函数和用户自定义函数有什么区别呢?简单地说,任何使用Keil C 语言的人,都可以 直接调用C 的库函数而不需要为这个函数写任何代码,只需要包含具有该函数说明的相应 的头文件即可;而自定义函数则是完全个性化的,是用户根据自己需要而编写的。Keil C 提 供了100 多个库函数供我们直接使用。 2、一个函数由两部份组成: (1)函数的首部、即函数的第一行。包括函数名、函数类型、函数属性、函数参数(形 参)名、参数类型。 例如:void mDelay (unsigned int DelayTime) 一个函数名后面必须跟一对圆括号,即便没有任何参数也是如此。 (2)函数体,即函数首部下面的大括号“{}”内的部份。如果一个函数内有多个大括 号,则最外层的一对“{}”为函数体的范围。 函数体一般包括: 声明部份:在这部份中定义所用到的变量,例1.2 中unsigned char j。 执行部份:由若干个语句组成。 在某此情况下也可以没有声明部份,甚至即没有声明部份,也没有执行部份,如: void mDelay() {} 这是一个空函数,什么也不干,但它是合法的。 在编写程序时,可以利用空函数,比如主程序需要调用一个延时函数,可具体延时多少, 怎么个延时法,暂时还不清楚,我们可以主程序的框架结构弄清,先编译通过,把架子搭起 来再说,至于里面的细节,可以在以后慢慢地填,这时利用空函数,先写这么一个函数,这 样在主程序中就可以调用它了。 3、一个C 语言程序,总是从main 函数开始执行的,而不管物理位置上这个main()放 在什么地方。例1.2 中就是放在了最后,事实上这往往是最常用的一种方式。
4、主程序中的mDelay 如果写成mdelay 就会编译出错,即C 语言区分大小写,这一点 往往让初学者非常困惑,尤其是学过一门其它语言的人,有人喜欢,有人不喜欢,但不管怎 样,你得遵守这一规定。 5、C 语言书写的格式自由,可以在一行写多个语句,也可以把一个语句写在多行。没 有行号(但可以有标号),书写的缩进没有要求。但是建议读者自己按一定的规范来写,可 以给自己带来方便。 6、每个语句和资料定义的最后必须有一个分号,分号是C 语句的必要组成部份。 7、可以用/*…..*/的形式为C 程序的任何一部份作注释,在“/*”开始后,一直到“*/” 为止的中间的任何内容都被认为是注释,所以在书写特别是修改源程序时特别要注意,有时 无意之中删掉一个“*/”,结果,从这里开始一直要遇到下一个“*/”中的全部内容都被认 为是注释了。原本好好的一个程序,编译已过通过了,稍作修改,一下出现了几十甚至上百 个错误,初学C 的人往往对此深感头痛,这时就要检查一下,是不是有这样的情况,如果 有的话,赶紧把这个“*/”补上。 特别地,Keil C 也支持C++风格的注释,就是用“//”引导的后面的语句是注释,例: P1_0=!P1_0; //取反P1.0 这种风格的注释,只对本行有效,所以不会出现上面的问题,而且书写比较方便,所以 在只需要一行注释的时候,我们往往采用这种格式。但要注意,只有Keil C 支持这种格式, 早期的Franklin C 以及PC 机上用的TC
第2 章 分支程序设计 第一部分课程学习了如何建立Keil C 的编程环境,并了解了一些C 语言的基础知识, 这一部分将通过一个键控流水灯程序的分析来学习分支程序设计。 2.1 程序功能与实现 硬件电路描述如下:89S52 单片机的P1 口接有8 个LED,当某一端口输出为“0”时, 相应的LED 点亮,P3.2、P3.3、P3.4、P3.5 分别接有四个按钮K1~K4,按下按钮时,相应 引脚被接地。现要求编写可键控的流水灯程序,当K1 按下时,开始流动,K2 按下时停止 流动,全部灯灭,K3 使灯由上往下流动,K4 使灯由下往上流动。 下面首先给出程序,然后再进行分析。 例2-1:键控流水灯的程序 #include "reg51.h" #include "intrins.h" #define uchar unsigned char void mDelay(unsigned int DelayTime) { unsigned int j=0; for(;DelayTime>0;DelayTime--) { for(j=0;j<125;j++) {;} }} uchar Key() { uchar KeyV; uchar tmp; P3=P3|0x3c; //四个按键所接位置 KeyV=P3; if((KeyV|0xc3)==0xff) //无键按下 return(0); mDelay(10); //延时,去键抖 KeyV=P3; if((KeyV|0xc3)==0xff) return(0); else { for(;;){ tmp=P3; if((tmp|0xc3)==0xff) break;} return(KeyV);}} void main() { unsigned char OutData=0xfe; bit UpDown=0; bit Start=0; uchar KValue; for(;;) { KValue=Key(); switch (KValue) { case 0xfb: //P3.2=0,Start { Start=1; break; } case 0xf7: //P3.3=0,Stop { Start=0; break; } case 0xef: //P3.4=0 Up { UpDown=1; break; } case 0xdf: //P3.5=0 Down { UpDown=0; break; } } if(Start) { if(UpDown) OutData=_crol_(OutData,1); else OutData=_cror_(OutData,1); P1=OutData; } else P1=0xff; //否则灯全灭 mDelay(1000); } }
输入源程序,保存为exam21.c,建立名为exam21 的工程文件,选择的CPU 型号为 AT89S52,在Debug 页加入-ddpj6,以便使用单片机实验仿真板,其他按默认设置。正确编 译、链接后进入调试模式,点击Peripherals??51 实验仿真板,打开实验仿真板,选择Run (全速运行),此时实验仿真板没有变化,用鼠标点击上方的K1 按钮,松开后即可看到Led “流动”起来,初始状态是由下往上流动,点击K3 按钮,可改变LED 的流动方向,改为 由上往下流动,点击K4 按钮,又可将流动方向变换回来。点击K2 按钮,可使流动停止, 所有LED“熄灭”。
|
|