51单片机C语言教程,郭天祥,PDF转word版第二章 - 图文

更新时间:2023-12-05 18:48:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第2章Keil软件使用及流水灯设计

本章详细介绍单片机程序常用编译软件Keil的用法,包括用Kei建立工程、工程配置、C51单片机程序软件仿真、单步、全速、断点设置、变量查看等。同时还介绍如何使用SST89E516RD单片机进行计算机与TX-IC单片机学习板之间的硬件仿真。用一个完整的C51程序来操作发光二极管的点亮与熄灭,然后调用C51库函数来方便地实现流水灯,最后为大家补充蜂鸣器与继电器的操作方法及集电极开路与漏极开路的概念。从这一章开始我们将手把手地讲解单片机C语言编程。认真学好本章,对于初学者来说将会是一个非常好的开头。

2.1 Kell工程建立及常用按钮介绍

在使用Keil软件之前,要保证在用户的计算机上装有一套稳定可靠的软件。本教材中讲解的Keil版本为V6.12,为了能让大家更方便地学习本软件的用法,建议大家在学习本教材时尽量选择该版本。

在本书中,我们强烈推荐的学习方法是边学边用,所以在这里我们不会像传统专业书籍那样,将某个软件的所有功能事先都讲解得非常仔细,很多不用的地方我们不做说明,需要用到什么,我们就学习什么,这样才能有效地理解它、记忆它,最终达到学以致用的目的。

2.1.1 Keil工程的建立

进入Keil后,屏幕知图2.1.1所示,紧接着出现编辑界面,如图2.1.2所示。

图2.1.1 启动Keil软件时的屏幕

(1) 建立一个新工程单击菜单中的选项,如图2.1.3所示。

图2.1.2进入Keil软件后的编辑界面

图2.1.3新建工程

(2)选择工程要保存的路径,输入工程文件名。Keil的一个工程里通常含有很多小文件, 为了方便管理,通常我们将一个工程放在一个独立文件夹下,比如保存到part2_l文件夹,工 程文件的名字为part2_l,如图2.1.4所示,然后单击<保存>按钮。工程建立后,此工程名变为 part2_l.uv2。

图2.1.4保存工程

(3)这时会弹出一个对话框,要求用户选择单片机的型号,可以根据用户使用的单片机来选择。Keil C51几乎支持所有的51内核的单片机,TX-1C实验板上用的是STC89C52,我

们在对话框中找不到这个型号的单片机。因为51内核单片机具有通用性,所以我们在这里可 以任选一款89C52就行,Keil软件的关键是程序代码的编写,而非用户选择什么硬件,在这 里我们选择Atmel的89C52来说明,如图2.1.5所示。选择89C52之后,右边 栏里是对该型号单片机的基本说明,我们可以单击其他型号单片机浏览一下其功能特点,然 后单击<确定>按钮。

图2.1.5选择单片机型号

(4)完成上一步骤后,窗口界面如图2.1.6所示。

图2.1.6添加完单片机后的窗口界面

到此为止,我们还没有建立好一个完整的工程,虽然工程名有了,但工程当中还没有任 何文件及代码,接下来我们添加文件及代码。

(5)如图2.1.7所示,单击菜单中的菜单项,或单击界面上的快捷图标。

新建文件后窗口界面如图2.1.8所示。

图2.1.7添加文件

图2.1.8添加完文件后的窗口界面

此时光标在编辑窗口中闪烁,可以输入用户的应用程序,但此时这个新建文件与我们刚 才建立的工程还没有直接的联系,单击图标H,窗口界面如图2.1.9所示,在<文件名(N)>编辑框中,输入要保存的文件名,同时必须输入正确的扩展名。注意,如果用c语言编写程 序,则扩展名必须为.c;如果用汇编语言编写程序,则扩展名必须为.asm。这里的文件名不一 定要和工程名相同,用户可以随意填写文件名,然后单击<保存>按钮。

图2.1.9保存文件

(6)回到编辑界面,单击前面的“+”号,然后在选项上

(7)单击右键,弹出如图2.1.10所示菜单。然后选择KAdd Files to Group 'Source Group F3菜单项, 对话框如图2.1.11所示。

图2.1.10将文件加入工程的菜单

图2.1.11选中文件后的对话框

选中,单击,再单击按钮,然后我们再单击左侧前面的“ + ”号,屏幕窗口如图2.1.12所示。

图2.1.12将文件加入工程后的屏幕窗口

这时我们注意到文件夹中多了一个子项,当一个工程中有多个代码文件时,都要加在这个文件夹下,这时源代码文件就与工程关联起来了。

通过以上(1)?(6)步我们学习了如何在Keil编译环境下建立一个工程,在开始编写 程序之前,我们有必要先学习编辑界面上一些常用的按钮功能与用法。

2.1.2常用按钮介绍

按钮用于显示或隐藏项目窗口,我们可单击该按钮观察其现象,项目窗口如图2.1.13所示。

按钮一用于显示或隐藏输出信息窗口,当我们进行程序编译时可查看输出信息窗口, 查看程序代码是否有错误,是否成功编译,是否生成单片机程序文件等。我们可单击该按钮 观察其现象,输出信息窗口如图2.1.14所示。

按钮用于编译我们正在操作的文件。

按钮用于编译修改过的文件,并生成应用程序供单片机直接下载。

按钮用于重新编译当前工程中的所有文件,并生成应用程序供单片机直接下载。因 为很多工程有不止一个文件,当有多个文件时,我们可使用此按钮进行编译。

按钮用于打开《OprionsforTarget》对话框,也就是为当前工程设置选项。使用该对话框可以对当前工程进行详细设置,关于该对话框的设置方法将在使用时再做详细讲解。

以上是使用频率最多的几个按钮的功能,大家千万不要被一打开软件时呈现在眼前令人 的眼花缭乱的众多按钮所吓着哟。其他一些调试时用到的按钮等我们具体用到时再做介绍。

2.2点亮第一个发光二极管

大家是不是已经迫不及待地想编写程序了,接下来我们就用C语言编写一个点亮TX-1C 实验板上第一个发光二极管的程序。由于这是本书的第一个程序,看懂了它,也就意味着你 已经踏入了单片机C语言编程的第一道门槛,因此我们在这里要花些时间讲解它,大家一定 要有耐心,认真地弄明白它。

我们先回到2.1节最后的编辑界面“part2一l.c”下,在当前编辑框中输入如下的C语言源程序,注意:在输入源代码时务必将输入法切换成英文半角状态。

---------------------------------------------------------------------------------------------------------------------- 例2.2.1 编写程序,点亮第一个发光二极管(part2_1.c P27 ) #include //52系列单片机头文件

sbit led1=P1^0; //声明单片机P1口的第一位 void main() //主函数 { led1=0; /*点亮第一个发光二极管*/ }

----------------------------------------------------------------------------------------------------------------------

在输入上述程序时,Keil会自动识别关键字,并以不同的颜色提示用户加以注意,这样 会使用户少犯错误,有利于提高编程效率。若新建立的文件没有事先保存的话,Keil是不会 自动识别关键字的,也不会有不同颜色出现。程序输入完毕后,如图2.2.1所示。

我们暂且不要管这几句程序表示什么意思,先学会编译及错误处理,然后我再详细介绍 代码的含义。接下来我们编译此工程,看看程序代码是否有错误。先保存文件,再单击K全 部编译〗快捷图标圖。建议大家每次在执行编译之前都先保存一次文件,从一开始就养成良 好的习惯对你将来写程序有很大好处,因为进行编译时,Keil软件有时会导致计算机死机, 使你不得不重启计算机,若你在编写一个很大的工程文件时没有及时保存,那么重启后你将 找不到它的任何踪影,只得重写。虽然这种情况极少发生,但出于安全考虑,建议大家及时

保存。编译后的屏幕如图2.2.2所示,我们重点观察信息输出窗口。

在图2.2.2中,我们看到信息输出窗口中显示的是编译过程及编译结果。其过程含义如下: 创建目标“Target 1”

以上信息表示此工程成功编译通过。

当然,并不是每个用户第一次都能很顺利地编译成功,下面我们再故意改错一处,然后 再编译一次,来观察它的编译错误信息,并教大家如何查找错误。

我们将程序中“ledl=0;/*点亮第一个发光二极管*/” 一行中的“;”删掉,然后将输入法 切换成中文输入,在中文输入状态下重新输入一个“;”,保存它,然后编译,如图2.2.3所示。

从图2.2.3看出,编译过程出现了错误,错误信息有三处,分别为part2一l.c的第5, 5, 6 行,在一个比较大的程序中,如果某处出现了错误,编译后会发现有很多个错误信息,其实 这些错误并非真正的错误,而是当编译器发现有一个错误时,编译器自身已经无法完整编译

完后续的代码而引发出更多的错误。解决办法如下:我们须将错误信息窗口右侧的滚动条拖 到最上面,双击第一条错误信息,可以看到Keil软件自动将错误定位,并且在代码行前面出 现一个蓝色的箭头。需要说明的是,有些错误连Keil软件自身也不能准确显示错误信息,更 不能准确定位,它只能定位到错误出现的大概位置,我们根据这个大概位置和错误提示信息 自己再查找和修改错误。双击图2.2.3中第一条错误信息后,显示如图2.2.4所示。

可见在中文状态下,Keil软件代码区输入符号会出现错误,我们改正错误后再编译一次, 成功通过。

现在我们回到Keil编辑界面,开始分析代码含义。

知识点:reg52.h头文件的作用

在代码中引用头文件,其实际意义就是将这个头文件中的全部内容放到引用头文件的位 置处,免去我们每次编写同类程序都要将头文件中的语句重复编写。

在代码中加入头文件有两种书写方法,分别#include 和#includen”reg52.h”,包 含头文件时都不需要在后面加分号。两种写法区别如下:

当使用?包含头文件时,编译器先进入到软件安装文件夹处开始搜索这个头文件,也就 是Keil\\C51\\INC这个文件夹下,如果这个文件夹下没有引用的头文件,编译器将会报错。

当使用双撇号” ”包含头文件时,编译器先进入到当前工程所在文件夹处开始搜索该头文 件,如果当前工程所在文件夹下没有该头文件,编译器将继续回到软件安装文件夹处搜索这 个头文件,若找不到该头文件,编译器将报错。reg52.h在软件安装文件夹处存在,所以我们 一般写成#include

打开该头文件查看其内容,将鼠标移动到reg52.h上,单击右键,选择HOpen document 3,即可打开该头文件,如图2.2.5所示。以后若需打开工程中的其他头文件,也可采用这种方式。或者手动定位到头文件所在的文件夹也可。

其全部内容如下:/*--------------------------------------------------------------------------

REG52.H

Header file for generic 80C52 and 80C32 microcontroller.

Copyright (c) 1988-2001 Keil Elektronik GmbH and Keil Software, Inc. All rights reserved. --------------------------------------------------------------------------*/ /* BYTE Registers */ sfr P0 = 0x80; sfr PI = 0x90; sfr P2 = OxAO;

sfr P3 = OxBO; sfr PSW = OxDO; sfr ACC = OxEO; sfr B = OxFO; sfr SP =0x81; sfr DPL =0x82; sfr DPH =0x83; sfr PCON =0x87; sfr TCON =0x88; sfr TMOD =0x89; sfr TLO =0x8A; sfrTLl = 0x8B; sfr THO =0x8C; sfrTHl =0x8D; sfr IE =0xA8; sfr IP = =0xB8; sfr SCON =0x98;

sfrSBUF =0x99;

/* 8052 Extensions */ sfrT2CON = 0xC8; sfr RCAP2L = OxCA; sfr RCAP2H = OxCB; sfrTL2 =OxCC; sfr TH2 =OxCD;

/* BIT Registers */ /* PSW */

sbit CY =PSW ^7; sbit AC =PSW ^6; sbit FO =PSW^5; sbit RSI =PSW^4; sbit RSO =PSW ^3; sbit OV =PSW^2; sbitP =PSWA0; \

/* TCON */

sbit TF1 =TCON^7; sbit TR1 =TCON^6; sbit TFO =TCON^5; sbit TRO =TCON^4; sbit IE1 =TCON^3; sbit IT1 =TCON^2; sbit IEO = TCON^1; sbit ITO =TCON^0;

/* IE */

sbit EA =IE^7;

sbit ET2 =IE^5; //8052 only sbit ES = IE^4; sbit ET1 =IE ^ 3; sbit EX1 =IE^2; sbit ETO = IE^1; sbit EXO =IE^0;

/* IP */

sbit PT2 =IP^5; sbit PS =IP^4; sbit PT1 =IP ^3; sbit PX1 =IP ^; sbit PTO = IP^1; sbit PXO =IP^0;

/* P3 */

sbit RD =P3^7; sbit WR =P3 ^6; sbitTI =P3 ^5; sbit TO =P3^4; sbit INTI =P3^3; sbit INTO=P3^2; sbit TXD = P3^1; sbit RXD =P3 ^0;

/* SCON */

sbit SMO = SCON^7; sbit SMI = SCON^6; sbit SM2 = SCON^5; sbit REN = SCON^4; sbit TB8 = SCON ^3; sbit RB8 = SCON^2; sbitTI =SCON^ 1; sbit RI = SCON^0;

/* PI */

sbit T2EX =P1^1; // 8052 only sbit T2 =P1^0; // 8052 only

/* T2CON */

sbit TF2 = T2CON^7; sbit EXF2 = T2CON^6; sbit RCLK = T2CON^5; sbit TCLK = T2CON^4; sbit EXEN2 = T2CON^3; sbit TR2 = T2CON^2; sbit C_T2 =T2CON^1; sbit CP_RL2 = T2CON^0;

---------------------------------------------------------------------------------------------------------------- 从上面代码中可以看到,该头文件中定义了 52系列单片机内部所有的功能寄存器,用到 了前面讲到的sfr和sbit这两个关键字,“sfrP0=0x80;”语句的意义是,把单片机内部地址0x80 处的这个寄存器重新起名叫P0,以后我们在程序中可直接操作P0,就相当于直接对单片机内 部的0x80地址处的寄存器进行操作。说通俗点,也就是说,通过sfr这个关键字,让Keil编 译器在单片机与人之间搭建一条可以进行沟通的桥梁,我们操作的是POP,而单片机本身并 不知道什么是P0 口,但是它知道它的内部地址0x80是什么东西。说到这里我想大家应该 已经明白了,以后凡是编写51内核单片机程序时,我们在源代码的第一行就可直接包含该头 文件。

在上面我们还看到,“sbit CY=PSW^7;”语句的意思是,将PSW这个寄存器的最高位,

重新命名为CY,以后我们要单独操作PSW寄存器的最高位时,便可直接操作CY,其他类同。

讲完了头文件,接下来我们再回到编辑界面,紧接着头文件后面有“//.....”,请看知识点。

知识点:C语言中注释的写法

在C语言中,注释有两种写法:

(1) //......,两个斜杠后面跟着的为注释语句。这种写法只能注释一行,当换行时,又必须在新行上重新写两个斜扛。

(2) /*?*/,斜扛与星号结合使用,这种写法可以注释任意行,即斜扛星号与星号斜扛 之间的所有文字都作为注释。 .

所有注释都不参与程序编译,编译器在编译过程会自动删去注释,注释的目的是为了我 们读程序方便,一般在编写较大的程序时,分段加入注释,这样当我们回过头来再次读程序 时,因为有了注释,其代码的意义便一目了然了。若无注释,我们不得不特别费力地将程序 重新阅读一遍方可知道代码含义。养成良好的书写代码格式的习惯,经常为自己编写的代码 加入注释,以后定能方便许多。

例2.2.1程序中接着往下看,“sbit ledl=Pl^0;”语句的含义是,将单片机P0 口的最低位 定义为ledl。在TX-1C实验板上,8个发光二极管的阴极通过一个74HC573锁存器分别连接 至单片机的POD,若要控制某一个发光二极管,也就是要控制单片机P0 口的某一位,必定 要声明这一位,否则单片机肯定不知道我们要操作的是什么东东。需要注意的是,这里的P1 不可随意写,P是大写,若写成p,编译程序时将报错,因为编译器并不认识pl,而它只认 识P1,这是因为我们在头文件中定义的是“sfr Pl= 0x90;”。这也是大多初学者编写第一个程序时常犯的错误。

例2.2.1程序中再往下就到了主函数mainO,无论一个单片机程序有多大,或多小,所有的单片机在运行程序时,总是从主函数开始运行,关于主函数的写法,我们看下一个知识点。

知识点:main()主函数的写法

格式:void main()注意:后面没有分号。 特点:无返回值,无参数。

无返回值表示该函数执行冗后不返回任何值,上面main前面的void表示“空”,即不返 回值的意思,后面我们会讲到有返回值的函数,到时大家一对比便会更加明白。

无参数表示该函数不带任何参数,即main后面的括号中没有任何参数,我们只写“()” 就可以了,也可以在括号里写上void,表示“空”的意思,如void main(void)。

任何一个单片机C程序有且仅有一个main函数,它是整个程序开始执行的入口。大家注 意看,在写完main()之后,在下面有两个花括号,这是C语言中函数写法的基本要求之一, 即在一个函数中,所有的代码都写在这个函数的两个大括号内,每条语句结束后都要加上分 号,语句与语言之间可以用空格或回车隔开。

例如:

void main() {

总程序从这里开始执行; 其他语句; ?? }

例2.2.1程序中接下来我们看“ledl=0;”语句,也就是该程序中最核心的语句。在数字电 路中,电平只有两种状态:高电平,1;低电平,0。显然,该语句的意思是,让P1 口的最低 位清0。由于没有操作其他口,所以其余口均保持原来状态不变。那么为什么P1 口的最低位 清0,板上的第一个发光二极管就会亮呢?接下来我们再来讲解电路知识,TX-1C单片机实 验板上流水灯与单片机连接方法如图2.2.6所示。

图2.2.6电路中,除单片机外,主要元件有三类:P2 (lkQ排阻)、D (1?8)(发光二极管)、U3 (74HC573锁存器),下面分别介绍。

(1)排阻。通俗地讲,它就是一排电阻,图2.2.6中一共有8个发光二极管,每个管子上 串连一个电阻,然后在电阻的另一端接电源,因为8个管子接法相同,所以我们把8个电阻 的另一端全部连接在一起,这样一来,便共有9个引脚,其中一个称为公共端。图2.2.7和 图2.2.8是直插式和贴片式排阻的实物图。

知识点:由电阻标号认知阻值

一般在排阻上都标有阻值号,其公共端附近也有明显标记。如图2.2.7和图2.2.8中分别 为103和150,103表示其阻值大小为10 x 103Q,即10k欧,若是102其阻值大小为10 x 102Q, 即1 k欧,150为15 X 10°欧,即15欧,其他读法都相同。

我们有时也会看到标号为1002,1001等。1002表示100 x 102欧,即10k欧, 1001表示100 x lO1欧,即 lk欧。

3位数表示与4位数表示的阻值读法我们都要会,标号位数不同,其电阻的精度不同, 一般地,3位数表示5%精度,4位数表示1%精度。TX-1C实验板上与发光二极管连接的是 102阻值的9引脚直插排阻。

还有的标号如3R0,表示阻值为3欧, 4K7表示阻值为4.7k欧, R002表示阻值为0.002欧。

(2)发光二极管。它具有单向导电性,通过5mA左右电流即可发光,电流越大,其亮度 越强,但若电流过大,会烧毁二极管,一般我们控制在3?20mA之间。在这里,给发光二极 管串联一个电阻的目的就是为了限制通过发光二极管的电流不要太大,因此这个电阻又称为“限流电阻”。当发光二极管发光时,测量它两端电压约为1.7V,这个电压又叫做发光二极管的“导通压降”。图2.2.9和图2.2.10分别为直插式发光二极管和贴片式发光二极管实物图。 发光二极管正极又称阳极,负极又称阴极,电流只能从阳极流向阴极。直插式发光二极管长脚为阳极,短脚为阴极。仔细观察贴片式发光二极管正面的一端有彩色标记,通常有标记的 一端为阴极。大家可观察TX-1C实验板上贴片发光二极管有一端有绿色标记,此标记即标识它是管子的阴极。

关于排阻大小的选择:欧姆定律想必大家都清楚,U=IR,当发光二极管正常导通时,其 两端电压约为1.7V,发光管的阴极为低电平,即0V,阳极串接一电阻,电阻的另一端为Vcc,

为5V,因此加在电阻两端的电压为5V-1.7V=3.3V,计算穿过电阻的电流,3.3V/1000Q=3.3mA。即穿过发光管的电流也为3.3mA,若想让发光管再亮一些,我们可以适当减小该电阻。

(3) 74HC573锁存器。它是一种数字芯片,由于数字芯片种类成千上万,我们不可能将 其全部记住,所以只能用一个学一个,然后弄明白它,日积月累,大家必将能灵活地设计出 各种电路。关于锁存器我们作为一个知识点来讲解。其直插式和贴片式实物图分别如图2.2.11 和2.2.12所示。

知识点?.锁存器

图2.2.13为74HC573的引脚分布图,先对照引脚图分别介绍各个引脚的作用,

专业术语为三态允许控制端(低电平有效),通常叫做输出使能端,或输出允许端都可以;1D? 8D为数据输入端;1Q?8Q为数据输出端;LE为锁存允许端,或叫锁存控制端。

图2.2.14为74HC573的真值表。真值表用来表示数字电路或数字芯片工作状态的直观特 性,大家务必要看明白。图2.2.14真值表中字母代码含义如下:H—高电平;L—低电平;X 一任意电平;Z—高阻态,也就是既不是高电平也不是低电平,而它的电平状态由与它相连接 的其他电气状态决定;Qo—上次的电平状态。

由真值表可以看出,当

为高电平时,无论LE与D端为何种电平状态,其输出都为

高 阻态。很明显,此时该芯片处于不可控状态,而我们将74HC573接入电路是必须要控制它的, 因此在设计电路时也就必须将面接低电平,所以TX-1C实验板上使用的三个锁存器的

端 全部接地。

为4氐电平时,我们再看LE,当LE为H时,D与Q同时为H或L;而当LE为L

时,无论D为何种电平状态,Q都保持上一次的数据状态。这也就是说,当LE为高电平时, Q端数据状态紧随D端数据状态变化;而当LE为低电平时,Q端数据将保持住LE端变化为 低电平之前Q端的数据状态。因此我们将锁存器的LE端与单片机的某一引脚相连,再将锁 存器的数据输入端与单片机的某组I/O 口相连,便可通过控制锁存器的锁存端与锁存器的数 据输入端的数据状态来改变销存器的数据输出端的数据状态。

TX-1C实验板上发光二极管处连接锁存器的目的是,因为发光二极管通过锁存器连接到 单片机的P1 口,而板上A/D芯片的数据输出端也连接到单片机的P1 口,当我们在做A/D实 验时,A/D芯片的数据输出端的数据就会实时发生变化,而若不加锁存器,那么发光二极管 的阴极电平也跟随A/D的数据输出的变化而变化,这样就会看见发光管无规则闪动,为了在 做A/D实验时,不影响发光二极管,我们在发光二极管与单片机之间加入一个锁存器用以隔 离,当做A/D实验时,我们可通过单片机将此锁存器的锁存端关闭,而此时无论单片机P1 口数据怎么变化,发光二极管也不会闪动。当我们做发光二极管的实验时,可将锁存端始终 打开,也就是让锁存器的锁存端处于高电平状态,而此时发光二极管就会跟随单片机的P1口状态而变化。

可能看到这里大家会有疑问了,为什么我们刚才在写程序的时候,并没有写一句控制锁 存器的锁存端置高的语句呢?原因是这样的,大家一定要牢记,51单片机在一上电时,如果 我们没有人为地控制其I/O 口的状态,它所有未控制的I/O 口都将默认为高电平,因此我们并 不需要写一句让锁存端置高的语句。

讲到这里,我们基本上讲完了与点亮第一个发光管有关的内容。大家可以看一看,虽然 仅仅只是一个简单的发光二极管,可这里面融合了多少知识啊!知识在于一点一滴的积累, 大家继续往下看,后面的内容更精彩。接下来我们就把前面编写的这段程序生成可以下载到 单片机的代码,然后亲自下载到实验板上,看看其效果究竟如何?

回到Keil编辑界面,单击菜单,然后在下拉菜单中单击项,或直接单击界面上的工程设置选项快捷图标,弹出如图2.2.15所示画面。单击KOutputH, 然后选中【Create HEX File〗项,使程序编译后产生HEX代码,供下载器软件下载到单片机 中。这里简单补充一点,单片机只能下载HEX文件或BIN文件,HEX文件是十六进制文件, 英文全称为hexadecimal, BIN文件是二进葡丨文件,英文全称为binary,这两种文件可以通过 软件相互转换,其实际内容都是一样的。我们也可同时将选项KBroise Information2选中, 选中这个选项后,我们在程序中某处调用函数的地方单击右健选择打开函数后,可直接跳转 到该函数体中,这个功能在编写比较大的程序中会经常用到。

确定后,我们再将工程编译一次,信息输出窗口如图2.2.16所示。

观察信息窗口可以看到多了一行“creating hex file from npart2_1.. ”。再补充一点,当创 建一个工程并编译这个工程时,生成的HEX文件名与工程文件名是相同的,添加的源代码名 可以有很多,但HEX文件名只跟随工程文件名。

然后,我们将此HEX文件下载到TX-1C单片机实验板上(关于下载过程请大家查看视 频教程或实验板配套光盘资料),实际现象效果图如图2.2.17所示。

在图2.2.17中,右侧8个发光二极管中,最上面的这个发光管点亮了,其余的没有亮, 这说明,程序按照我们编写的意图工作了。

这种控制I/O 口的方法是一条语句只能控制一个I/O 口,也就是通常所说的位操作法,如 果我们要同时让1, 3, 5,7这4个发光二极管亮,就要声明4个I/O 口,然后在主程序中再 写4句分别点亮4个发光管的程序。显然,这种写法比较麻烦。接下来为大家讲解一种总线操作法。

【例2.2.2】请大家按以下方法操作:在原来工程下新建立一个文件,保存,修改名称为 part2_2.c,将此文件添加到工程中,在项目窗口中用鼠标选中,按下键盘上的 删除此文件,这时工程中的文件就只有part2_2.c。注意:必须删除part2_l.c才可正常编译新文件,因为一个工程中只能有一个主函数。我们在新文件中输入以下语句:

#include //52系列单片机头文件 void main() //主函数 { P1=0xaa; }

这里的“Pl=0xaa;”就是对单片机P1 口的8个I/O 口同时进行操作,“0x”表示后面的数 据是以十六进制形式表示的,十六进制的aa,转换成二进制是10101010,那么对应的发光二 极管便是1,3,5,7亮,2,4,6,8灭。我们将0xaa转换成十进制后为170,也可直接对P1口进行十进制数的赋值,如“Pl=170;”,其效果是一样的,只是麻烦了许多。因为无论是 几进制的数,在单片机内部都是以二进制数形式保存的,只要是同一个数值的数,在单片机 内部占据的空间就是固定的,在这里还是用十六进制比较直观。编译后下载,实际观察现象 效果图如图2.2.18所示.

2.3while语句

通过上面一节的学习,想必大家已经对点亮实验板上的任意发光二极管轻车熟路了,但 是,先不要高兴得太早,上面的程序并不完善,任何一个程序都要有头有尾才对,而上面我 们写的程序似乎只有头而无尾。我们分析一下看,当程序运行时,首先进入主函数,顺序执 行里面的所有语句,因为主函数中只有一条语句,当执行完这条语句后,该执行什么了?因 为我们没有给单片机明确指示下一步该做什么,所以单片机在运行时就很有可能会出错。根

据经验(并没有详细记录可查),当Keil编译器遇到这种情况时,它会自动从主函数开始处重 新执行语句,所以单片机在运行上面两个程序时,实际上是在不断地重复点亮发光二极管的 操作,而我们的意图是让单片机点亮二极管后就结束,也就是让程序停止在某处,这样一个 有头有尾的程序才完整。

那么如何让程序停止在某处呢?我们用while语句就可以实现。

知识点: while()语句

格式:while (表达式) {内部语句(内部可为空)}

特点:先判断表达式,后执行内部语句。

原则:若表达式不是0,即为真,那么执行语句。否则跳出while语句,执行后面 的语句。 需要注意的三点: 所以1,2,3等都是真。

(2)内部语句可为空,就是说while后面的大括号里什么都不写也是可以的,如 “while(l){};”既然大括号里什么也没有,那么我们就可以直接将大括号也不写,再如 “while(l);”中“;” 一定不能少,否则while()会^1巴跟在它后面第一个分号前的语句认为是它的内部语句。

例如:

while(l) Pl=123; P2=121; ? ? ? ?

上面这个例子中,while()会把“Pl=123;”当做它的语句,即使这条语句并没有加大括号。 既然如此,那么我们以后在写程序时,如果while()内部只有一条语句,我们就可以省去大括 号,而直接将这条语句跟在它的后面。

例如:

while(l) Pl=123;

(3)表达式可以是一个常数、一个运算或一个带返回值的函数。

(1)在C语言中我们一般把“0”认为是“假”,“非0”认为是“真”,也就是说,只要 不是0就是真,

有了上面的介绍,我们在程序最后加上“while(l);”这样一条语句就可以让程序停止。因 为该语句表达式值为1,内部语句为空,执行时先判断表达式值,因为为真,所以什么也不执 行,然后再判断表达式,仍然为真,又不执行,因为只有当表达式值为0时才可跳出while() 语句,所以程序将不停地执行这条语句,也就是说单片机点亮发光管后将永远重复执行这条 语句。

初学者可能会这样想,我让单片机把发光二极管点亮后,就让它停止工作,不再执行别 的指令,这样不是更好吗?请大家注意,单片机是不能停止工作的,只要它有电,有晶振在 起振,它就不会停止工作,每过一个机器周期,它内部的程序指针就要加1,程序指针就指向 下一条要执行的指令。想让它停止工作的办法就是把电断掉,不过这样发光二极管也就不会 亮了。不过我们可以将单片机设置为休眠状态或掉电模式,这样可以最大限度地降低它的功

耗。关于这些内容我们在后面会讲到。

【例2.3.1】编写一个完整的点亮第一个发光二极管的程序。

#include I 152系列单片机头文件 void main() //主函数 {

Pl=Oxfe; while(l); }

2.4 for语句及简单延时语句

知识点:for语句

格式:for (表达式1;表达式2;表达式3)

{语句(内部可为空)}

执行过程:

第1步,求解一次表达式1。

第2步,求解表达式2,若其值为真(非0即为真),则执行for中语句,然后执行第3 步;否则结束for语句,直接跳出,不再执行第3步。

第3步,求解表达式3。 第4步,跳到第2步重复执行。

需要注意的是,三个表达式之间必须用分号隔开。

利用for语句和while语句可以写出简单的延时语句,下面就用for语句来写一个简单的 延时语句,并进一步讲解for语句的用法。

unsigned char i; for(i=2;i>0;i—);

看上面这两句,首先定义一个无符号字符型变量i,然后执行for语句,表达式1是给i 赋一个初值2,表达式2是判断i大于0是真还是假,表达式3是i自减1,我们分析执行过 程:

第1步,给i赋初值2,此时i=2.

第2步,因为2>0条件成立,所以其值为真,那么执行一次for中的语句,因为for内部 语句为空,即什么也不执行。

第3步,i自减1,即i=2-l=l。

第4步,跳到第2步,因为1>0条件成立,所以其值为真,那么执行一次for中的语句, 因为for内部语句为空,即什么也不执行。

第5步,i自减1,即i=l-l=0。

第6步,跳到第2步,因为0〉0条件不成立,所以其值为假,那么结束for语句,直接跳出

通过以上6步,这个for语句就执行完了,单片机在执行这个for语句的时候是需要时间 的,上面i的初值较小,所以执行的步数就少,当我们给i赋的初值越大,它执行所需的时间 就越长,因此我们就可以利用单片机执行这个for语句的时间来作为一个简单延时语句。

很多初学者容易犯的错误是,想用for语句写一个延时比较长的语句,那么他可能会这样写:

unsigned char i; for(i=3000;i>0;i-);

但是结果却发现这样写并不能达到延长时间的效果,因为在这里i是一个字符型变量, 它的最大值为255,当你给它赋一个比最大值都大的数时,编译器自然就出错误了,因此我们 尤其要注意,每次给变量赋初值时,都要首先考虑变量类型,然后根据变量类型赋一个合理 的值。

那么怎样才能写出长时间的延时语句呢?我们下面讲解for语句的嵌套。 unsigned char i, j; for(i=100;i>0;i-) for(j=200;j>0;j-);

上面这个例子是for语句的两层嵌套,大家注意看,第一个for后面没有分号,那么编译 器默认第二个for语句就是第一个for语句的内部语句,而第二个for语句内部语句为空,程 序在执行时,第一个for语句中的i每减一次,第二个for语句便执行200次,因此上面这个 例子便相当于共执行了 100X200次for语句。通过这种嵌套我们便可以写出比较长时间的延 时语句,我们还可以进行3层、4层嵌套来增加时间,或是改变变量类型,将变量初值再增大 也可以增加执行时间。

这种for语句的延时时间到底有没有精确的算法呢?在C语言中这种延时语句不好算出 它的精确时间,如果需要非常精确的延时时间,我们在后面会讲到利用单片机内部的定时器 来延时,它的精度非常高,可以精确到微秒级。而一般的简单延时语句实际上我们并不需要 太精确,不过我们也是有办法知道它大概延时多长时间的,请看下一节讲解。

2.5 Keil仿真及延时语句的精确计算

【例2.5.1】利用for语句的延时特性,编写一个让实验板上第一个发光二极管以间隔Is 亮灭闪动的程序。我们新建一个文件part2_3x,添加到工程中,删去原来的文件,在新文件 中输入以下代码:

#include //52系列单片机头文件 #define uint unsigned int //宏定义

sbit led1=P1^0; //声明单片机P1口的第一位 uint i,j;

void main() //主函数 { while(1) //大循环

{ }

} led1=0; for(i=1;i>0;i--) for(j=110;j>0;j--); led1=1; for(i=1000;i>0;i--) for(j=110;j>0;j--);

/*点亮第一个发光二极管*/ //延时

/*关闭第一个发光二极管*/ //延时

观察上面代码,与part2_l.c相比,关键部分多了#define语句、while(l){}、还有两个for

语句。

知识点:#define宏定义

格式:#define新名称原内容 .

注意后面没有分号,#define命令用它后面的第一个字母组合代替该字母组合后面的所有 内容,也就是相当于我们给“原内容”重新起一个比较简单的“新名称”,方便以后在程序中 直接写简短的新名称,而不必每次都写烦琐的原内容。

上例中我们使用宏定义的目的就是将unsigned int用uint代替,在上面的程序中可以看到, 当我们需要定义unsigned int型变量时,并没有写“unsigned int i,j;”,取而代之的是“uint i, j;”,在一个程序代码中,只要宏定义过一次,那么在整个代码中都可以直接使用它的“新 名称”。注意,对同一个内容,宏定义只能定义一次,若定义两次,将会出现重复定义的错误提示。

while语句和for语句在前面都已经讲过,这里使用while(l){}语句,因为while里的表达 式是1,永远为真,所以程序将永远循环执行这个大括号中的所有语句。单片机在执行指令的 时候是按代码从上向下顺序执行的,我们分析while大括号里的语句含义是:“点亮灯一延时 一会儿一关闭灯一再延时一会儿一点亮灯一延时一会儿?”如此循环下去,当我们把程序下 载到实验板上便可看到小灯亮灭闪动的效果。

我们如何用软件来模拟出这个延时语句究竟是延长多少时间呢?回到Keil编辑界面,打 开工程设置对话框,在KTarget3标签下的KXtal(MHz): 3后面将原来的默认值修改为TX-1C 单片机实验板上晶振频率值11.0592MHz,如图2.5.1所示。

Keil编译器在编译程序时,计算代码执行时间与该数值有关,既然我们要模拟真实时间,

那么软件模拟运行速度就要与实际硬件一一对应,TX-1C实验板上使用的外部晶振频率是 11.0592MHz,在实验板上单片机的右下角大家可以看到实物,如图2.5.2所示。

单击〖确定〗按钮后,再单击窗口上的调试按钮快捷图标进入到软件模拟调试模式, 如图2.5.3所示

在软件调试模式下,我们可以设置断点、单步、全速、进入某个函数内部运行程序,同 时还可以查看变量变化过程、模拟硬件I/O 口电平状态变化、查看代码执行时间等。在开始 调试之前我们先熟悉一下调试按钮的功能。调试状态下多了图2.5.4所示的几个调试按钮

对常用的几个按钮介绍如下:

一将程序复位到主函数的最开始处,准备重新运行程序。

—全速运行,运行程序时中间不停止。

—停止全速运行,全速运行程序时激活该按钮,用来停止正全速运行的程序 一进入子函数内部。

一单步执行代码,它不会进入子函数内部,可直接跳过函数。 一跳出当前进入的函数,只有进入子函数内部该按钮才被激活 一程序直接运行至当前光标所在行。

-显示/隐藏编译窗口,可以查看每句C语言编译后所对应的汇编代码。 一显示/隐藏变量观察窗口,可以查看各个变量值的变化状态。

大家不妨把这些按钮一个个都单击试试看,只有亲自操作过了记忆才会深刻。我们先来 看如何在单步执行代码时,查看硬件I/O 口电平变化和变量值的变化。先将硬件I/O 口模拟器 打开,在图2.5.5中单击《Portl》项,弹出图2.5.6所示对话框。

图2.5.6显示的是软件模拟出的单片机PI 口 8位口线的状态,单片机上电后I/O 口全为1, 即十六进制的OxFF。

我们再单击图2.5.3中右下角变量观察窗口的KWatch#l3标签,窗口变成图2.5.7所示, 可以看到上面显示出“type F2 to edit (按F2进行编辑)”的字样,接下来我们分别按两次F2, 输入本程序中用到的两个变量i和j。在右面立即显示出变量的值0x0000,如图2.5.8所示, 因为i和j在最开始定义的时候并没有给它们赋初值,编译器默认给它们赋的初值是0,而当 进入for语句后,我们才为i和j分别赋了 1000和110的值。

同时,在图2.5.3左侧的寄存器窗口中可以看到一些寄存器名称及它们的值,如图2.5.9 所示。

图2.5.9中我们最关心的只有一个,也就是本小节的核心部分“sec”,它后面显示的数

据 就是程序代码执行所用的时间,单位是秒,可以看到上面显示的是422.09us,这是程序启动到目前停止位置所花的所有时间。注意:这个时间是累计时间。

我们回到代码编辑框,在图2.5.3中看到主函数

“ledl=0;”前面有一个黄色的小箭头,这里需要注意,这个小箭头指向的代码是下一步将要执行的代码,我们单击单步运行快捷图标

,这时看到黄色小箭头向下移动了

一行,在P1 口软件模拟窗口中,P1的最低位对应的对号没有了,这说明“ledl=0;”这条语句执行结束了,在实际硬件中也就点亮了 P1 口最低位

所对应的发光二极管。同时sec后面变成为423.18us,因为我们可以计算出执行这条指令实际花去了 423.18-422.09= 1.09us的时间,这个时间恰好就是51单片

机在11.0592晶振频率下,一个机器周期(知识点)所花费的时间。

知识点:单片机的几个周期介绍

(1)时钟周期。也称振荡周期,定义为时钟频率的倒数 (可以这样来理解,时钟周期就是单片机外接晶振的倒数,如12MHz的晶振,它的时钟周期就是1/12us),它是单片机中最基本的、最小的时间单位。在一个时钟周期内,CPU仅完成一 个最基本的动作。对于某个单片机来讲,若采用了 1MHz的时钟频率,则时钟周期就是1us; 若采用4MHz的时钟频率,则时钟周期就是250us。由于时钟脉冲是CPU的基本工作脉冲, 它控制着CPU的工作节奏(使CPU的每一步都统一到它的步调上来)。显然,对同一种单片 机,时钟频率越高,单片机的工作速度就越快。但是,由于不同的单片机其内部硬件电路和 电气结构不完全相同,所以其所需要的时钟频率范围也不一定相同。我们使用的STC89C系列单片机的时钟范围约在1MHz?40MHz。

(2)状态周期。它是时钟周期的两倍。

(3)机器周期。单片机的基本操作周期,在一个操作周期内,单片机完成一项基本操作, 如取指令、存储器读/写等。它由12个时钟周期(6个状态周期)组成。

(4)指令周期。它是指CPU执行一条指令所需要的时间。一般一个指令周期含有1?4 个机器周期。

接着再单击单步运行按钮,这时右下角变量查看窗口中的i被赋值0x03E8,在这个值上 单击鼠标右键选择项,将数值显示方式改成十进制显示,我们看到i的值即为1000,实际上就是刚才上一步运行第一个for语句时给i赋的值。继续单步运行 可以看到i的值从1000开始往下递减,同时左侧的sec在一次次增加,但j的值始终为0,因 为每执行一次外层for语句,内层for语句将执行110次,即j已经由110递减到0 了,所以 我们看上去j的值始终都是0。那如果我们要看这个for嵌套语句到底执行了多长时间的话, 是不是就要单击1000次呢?其实不用这么麻烦,设置断点可以方便地解决这个问题。

设置断点有很多好处,在软件模拟调试状态下,当程序全速运行时,每遇到断点,程序 会自动停止在断点处,即下一步将要执行断点处所在的这条指令。这样,我们只需在延时语 句的两端各设置一个断点,然后通过全速运行,便可方便地计算出所求延时代码的执行时间。 设置方式如下:单击复位钮,然后在第一个for所在行前面空白处双击鼠标,前面出现一个红

色方框,表示本行设置了一个断点,然后在下面“ledl=l;”所在行以同样方式插入另一个断 点,这两个断点之间的代码就是这个两级for嵌套语句,如图2.5.10所示。

单击全速运行按钮程序会自动停止在第一个for语句所在行,查看时间显示为 423.18us,再单击一次全速运行按钮,程序停止在第二个for语句下面一行处,查看时间显示 为968.31272ms,我们忽略微秒,此时间约为1s,由于无须精确时间,所以这个精度已经足够,我们的for语句延时时间便计算出来了。

大家可以改变for语句中两个变量的初值来重新测试时间,这里给大家讲讲我曾用过的时 间经验:for语句中两个变量类型都为unsigned hit型时(注意,若变量为其他类型则时间不 遵循以下规律,因为变量类型不同,单片机运行时所需时间就不同),内层for语句中变量恒 定值为110时,外层for中变量为多少,这个for嵌套语句就延时约多少毫秒,大家可自行测 试验证,也可自己测试出更精确的延时语句。

2.6 不带参数函数的写法及调用

我们先来观察2.5节中的例2.5.1,可以看到在打开和关闭发光二极管的两条语句之后, 是两个完全相同的for嵌套语句:

for(i= 1000;i>0;i—) //延时

for(j=110;j>0;j-);

在C语言代码中,如果有一些语句不止一次用到,而且语句内容都相同,我们就可以把 这样的一些语句写成一个不带参数的子函数,当在主函数中需要用到这些语句时,直接

调用 这个子函数就可以了。我们以上面这个for嵌套语句为例,其写法如下:

void delay ls() {

for(i=1000;i〉0;i--) for(j=110;j>0;j--); }

其中,void表示这个函数执行完后不返回任何数据,即它是一个无返回值的函数。delayls 是函数名,这个名字我们可以随便起,但是注意不要和C语言中的关键字相同。大家写成 delay_ls, delaylmiao等都是可以的,一般我们写成方便记忆或读懂的名字,也就是一看到函 数名就知道此函数实现的内容是什么。我在这里写成delayls是因为这个函数是一个延时Is 的函数。紧跟函数名后面的是一个括号,这个括号里没有任何数据或符号(即C语言当中的

“参数”),因此这个函数是一个无参数的函数。接下来两个大括号中包含着其他要实现的语 句。以上讲解的是一个无返回值、不带参数的函数的写法。

需要注意的是,子函数可以写在主函数的前面或是后面,但是不可以写在主函数里面。 当写在后面时,必须要在主函数之前声明子函数,声明方法如下:将返回值特性、函数名及 后面的小括号完全复制,若是无参函数,则小括号内为空;若是有参函数,则需要在小括号 里依次写上参数类型,只写参数类型,无须写参数,参数类型之间用逗号隔开。最后在小括 号的后面必须加上分号“;”。当子函数写在主函数前面时,不需要声明,因为写函数体的同时 就已经相当于声明了函数本身。通俗地讲,声明子函数的目的是为了编译器在编译主程序的 时候,当它遇到一个子函数时知道有这样一个子函数存在,并且知道它的类型和带参情况等 信息,以方便为这个子函数分配必要的存储空间。

【例2.6.1】写出一个完整的调用子函数的例子,让实验板上第一个发光二极管以间隔 500ms亮灭闪动。新建一个文件part2_4.c,添加到工程中,删去原来的文件,在新文件中输 入以下代码:

#include //52系列单片机头文件 #define uint unsigned int //宏定义 sbit led1=P1^0; //声明单片机P1口的第一位 void delay1s(); //声明子函数 void main() //主函数 { while(1) //大循环 { led1=0; /*点亮第一个发光二极管*/ delay1s(); //调用延时子函数 led1=1; /*关闭第一个发光二极管*/ delay1s(); //调用延时子函数 } }

void delay1s() //子函数体 { uint i,j; for(i=500;i>0;i--) for(j=110;j>0;j--); }

在例2.6.1中,我们注意到“uint i,j;”语句,i和j两个变量的定义放到了子函数里,而 没有写在主函数的最外面。在主函数外面定义的变量叫做全局变量;像这种定义在某个子函 数内部的变量被叫做局部变量,这里i和j就是局部变量。注意:局部变量只在当前函数中有 效,程序一旦执行完当前子函数,在它内部定义的所有变量都将自动销毁,当下次再调用该 函数时,编译器重新为其分配内存空间。我们要知道,在一个程序中,每个全局变量都占据 着单片机内固定的RAM,局部变量是用时随时分配,不用时立即销毁。一个单片机的RAM

是有限的,如AT89C52只有256B的RAM,如果要定义unsigned int型变量的话,我们最多 只能定义128个;STC单片机内部比较多,有512B的,也有1280B的。很多时候,当写一 个比较大的程序时,经常会遇到内存不够用的情况,因此我们从一开始写程序时就要坚持能 节省RAM空间就要节省,能用局部变量就不用全局变量的原则。

将程序下载到实验板,可看见小灯先亮500ms,再灭500ms, —直闪烁。

2.7 带参数函数的写法及调用

有了 2.6节的知识,本节学起来便容易多了。我们来看2.6节中的delayls()子函数,i=500 时延时500ms,那么如果我们要延时300ms,就需要在子函数里把i再赋值为300,要延时100ms 就得改i为100,这样岂不是很麻烦?有了带参数的子函数就好办多了,写法如下:

Void delay ms(unsigned int xms) {

uinti, j;

for(i=xms;i>0;i~)

for(j=110;j>0;j-_); }

上面代码中delayms后面的括号中多了一句“unsigned int xms”,这就是这个函数所带的 一个参数,xms是一个unsigned int型变量,又叫这个函数的形参,在调用此函数时我们用一 个具体真实的数据代替此形参,这个真实数据被称为实参,形参被实参代替之后,在子函数 内部所有和形参名相同的变量将都被实参代替。声明方法在2.6节已经讲过,这里再强调一下, 声明时必须将参数类型带上,如果有多个参数,多个参数类型都要写上,类型后面可以不跟 变量名,也可以写上变量名,具体使用过程请看例2.7.1。有了这种带参函数,我们要调用一 个延时300ms的函数就可以写成“delayms(300);”,要延时200ms可以写成“delayms(200);”, 这样就方便多了。如下:

【例2.7.1】写一个完整的程序,还是让一个小灯闪动,不过这次我们让它以亮200ms、 灭800ms的方式闪动,完整程序代码如下: #include #define uint unsigned int sbit led1=P1^0; void delayms(uint); void main() { while(1) { led1=0; delayms(200); led1=1; delayms(800); } }

void delayms(uint xms) {

//52系列单片机头文件 //宏定义 //声明单片机P1口的第一位 //声明子函数 //主函数

//大循环

/*点亮第一个发光二极管*/ //延时200毫秒

/*关闭第一个发光二极管*/ //延时800毫秒

}

uint i,j;

for(i=xms;i>0;i--) //i=xms即延时约xms毫秒 for(j=110;j>0;j--);

将程序下载到实验板,可看见小灯先亮200sm,再灭800ms,一直闪烁。

2.8利用C51库函数实现流水灯

实现流水灯的办法有多种,可以用逻辑运算来实现,也可以用C51库自带的函数来实现, 本节中我们就调用现成的库函数来实现流水灯,大家打开Keil软件安装文件夹,定位到 Keil\\C51\\HLP文件夹,打开此文件夹下的C511ib文件,这是C51自带库函数帮助文件。在索 引栏我们找到_crol_函数,双击打开它的介绍,内容如下:

这个函数包含在intrins.h头文件中,也就是说,如果在程序中要用到这个函数,那么必 须在程序的开头处包含intrins.h这个头文件。再来看函数特性“unsigned char _crol_ (unsigned char c, unsigned char b);”这个函数不像前几节我们讲过的函数,它前面没有void,取而代之 的是unsigned char;小括号里有两个形参,unsigned char c,unsigned char b,这种函数叫做有 返回值、带参数的函数。有返回值的意思是说,程序执行完这个函数后,通过函数内部的某 些运算而得出一个新值,该函数最终将这个新值返回给调用它的语句。_cml_是函数名,不再 多讲。我们再来看看函数实现了什么功能。

上面英文的大意是,Description (描述):_crol_这个函数的意思是将字符c循环左移b 位,这是C51库自带的内部函数,在使用这个函数之前,需要在文件中包含它所在的头文件。 再看后面的Return Value (返回值):_crol_这个函数返回的是将c循环左移之后的值。关于移 位操作,我们看下一个知识点。

知识点:移位操作

(1)左移。C51中操作符为<<”,每执行一次左移指令,被操作的数将最高位移入单 片机PSW寄存器的CY位,CY位中原来的数丟弃,最低位补0,其他位依次向左移动一位, 如图2.8.1所示。

本文来源:https://www.bwwdw.com/article/nx8t.html

Top