您好,欢迎来到五一七教育网。
搜索
您的当前位置:首页c51教程

c51教程

来源:五一七教育网
 作为一个初学者,如何单片机入门?我需要那些知识和设备?

知识上,其实不需要多少东西,会简单的C语言,知道51单片机的基本结构就可以了。一般的大学毕业生都可以了,自学过这2门课程的高中生也够条件。

设备上,一般是建议购买一个仿真器,这样才可以进行实际的,全面的学习。日后在工作上,仿真器也大有用处

还有,一般光有仿真器是不行,还得有一个实际的电路,即学习板。学习板一般价格都比较贵,而且许多学习板配套程序和讲解不够完善。 这里介绍的是最简单的学习板,4个按键加4个LED发光管,一个蜂鸣器,一个24c02即可。

通过30个教程,初学者可以学到:单片机控制外部设备,读取外部设备状态,外部中断的应用,中断的深入理解,变量和标记的灵活应用,定时器的灵活应用,可编程自动控制的方法,按键控制设备动作的方法,PWM输出的设计,存储器的读写,延时报警器的设计,各种报警音的设计,音乐播放的设计,程序模块化的设计等等知识。

虽然,这些知识的覆盖面有限,但是,当你学习并掌握了这30个试验之后,您就会豁然开朗,单片机的编程控制如此简单!学习完后,您就已经完全地入门了,并可以自主地对其它的单片机知识进行学习、试验,甚至进行项目开发!

第一课 了解单片机及单片机的控制原理,控制一个LED 灯的亮和灭

本章学习内容: 单片机基本原理,如何仿真器,如何编程点亮和灭掉一个LED 灯,如何进入KEILC51uV调试环境,如何使用单步,断点,全速,停止的调试方法

单片机现在是越来越普及了,学习单片机的热潮也一阵阵赶来,许多人因为工作需要或者个人兴趣需要学习单片机。可以说,掌握了单片机开发,就多了一个饭碗。 51 单片机已经有30 多年的历史了,在中国,高校的单片机课程大多数都是51,而51 经过这么多年的发展,也增长了许多的系列,功能上有了许多改进,也扩展出了不少分支。而国内书店的单片机专架上,也大多数都是51 系列。可以预见,51 单片机在市场上只会越来越多,功能只会越来越丰富,在可以预见的数十年内是不可能会消失的。

下面以51 为例来了解一下单片机是什么东西,控制原理又是什么?

在数字电路中,电压信号只有两种情况,高电平和低电平,用数字来记录就是1 和0。单片机内部的CPU,寄存器,总线等等结构都是通过1 和0 两种信号来运作的,数据也是以1 或者0 来保存的。单片机的输入输出管脚,也就是IO 口,也是只输出或识别1 和0 两种信号,也就是高电平和低电平。当单片机输出一个或一组电平信号到IO 口后,外部的设备就可以读到这些信号,并进行相应操作,这就是单片机对外部的控制。当外部一个或一组电平信号送到单片机的IO 口时,单片机也可以读到这些信号,并进行分析操作,这就是单片机对外部设备信号的读取。当然实际的操作中,这些信号可能十分复杂,必须严格地按照规定的时间顺序(时序)输入输出。每种设备也都规定了自己的时序,只要都严格遵守,就可以控制任何设备,做出只要你想象得出的任何事情。

您可能会再问,我如何让单片机去控制和分析外部设备呢?答案是程序,您可以编写相关的程序,并且把他们烧写到单片机内部的程序空间,单片机在上电时,就会一步一步按照您写的程序去执行指令,做您想做的事情。

在51 标准芯片中,有32 个输入输出IO,分为4 组,每组8 个,分别为P0 口,P1 口,P2 口,P3 口。P1 口的8 条脚就用P1.0 至P1.7 表示,其余类似。51 就是用这32 个口来完成所有外部操作的。对于51 的内部结构,如果您已经了解,那是最好;如果不懂,也可以先放下,在完成了本教程开始的几个章节之后,您就会大有兴趣,自己去寻找资料阅读了。当然,如果您希望成为一个优秀的单片机开发程序员,还是必须熟悉单片机的内部结构及工作原理,切不可偷懒!

在这一章,您将用程序去控制一个LED 发光管的亮和灭。你应该知道,LED 发光管在通过一定电流时亮,不通电就灭。为了不让LED 通过太大的电流把它烧坏,我们还要串上限流电阻。51 的IO 是弱上拉的方式,在输出高电平时,只能输出几十微安的电流到地,而在输出低电平时,VCC 电源可以输入几十毫安的电流到IO。一般LED 需要10 毫安左右电流点亮,我们就将LED 接在电源VCC 和IO 口之间,中间串上电阻,当IO 输出低电平时,灯就亮了,反之,灯就灭了。我们在这个程序里要控制的是P1.0。请参考一下我们将要使用的试验板的电路图。

在实际的单片机学习和开发中,你可以用仿真器模拟一个CPU 芯片,让它按照您编写的程序工作,并且进行调试,一步步排除程序的bug,使程序正常工作。程序工作正常后,您就可以用烧写器将您编写的程序烧入购买来的单片机芯片中,让它自己去运行了。

要使用仿真器,还得有一个编译调试的环境,这个环境是在计算机上运行的,我们就在计算机上编写和调试程序,计算机和仿真器有连接,仿真器中的各种数据和程序,都可以从计算机上观察到,并可以观察变量,写入变量的值,单步调试程序,在程序中设置断点调试,全速运行,停止程序运行,等等操作。我们使用keilC51 编译调试环境,仿真器的选择太多了,你可以根据自己的实际情况来选择。

随后我将给大家提供keilc51 相关的中文说明资料,这些资料详细地说明了如何使用C51 编程和如何使用keil uV2 现在可以开始做试验了,我们打开已经建立好的工程和编写好的程序试验。顺便还会学习一下程序调试的技巧。至于如何建立一个新工程,请参考C51 的帮助文件。

请双击lessoncode01 目录下的lesson1.uv2,打开后界面如下:

点一下上图第三排第2 或者第3 个按钮(您的编译器按钮位置不一定在那个位置,自己找找),就可以看到编译结果了。上面显示是0errrs,0warnings,这是最佳的编译结果,如果有error,则无法进行下一步仿真,如果有warning,一定要尽量消除,确实无法消除的,也要确认不会对程序造成影响,才进行下一步的仿真。在编译结果中,我们还可以看到有data,xdata,code 等用了多少字节的报告,要注意您的单片机中是否有这么多的资源,如果不够,将来烧片运行时就可能出现问题。比如ATC51 的程序空间是4K,xdata 如果没有外扩就是0 个,data 是128 个。超出这些范围,程序就不能在ATc51 中运行。不同的芯片有不同的容量,如SSTE516RD 就有K 程序,内部768 字节XDATA,还有256 个字节的data。我们的例程中肯定都考虑了这些了,肯定不会超出,将来自己开发时就要注意了。

下面我们故意把第9 行的P10 写成P11,点编译,因为没有预先定义P11,所以就报告错误了,如下图:

双击一下错误报告的那一行,窗口就也会跳到这一行,方便您进行修改。好了,现在请把错误改回去,再编译一次,出现报告正确了以后,下面开始仿真了。点一下第二行第5 个一个放大镜里面一个d 字母的按钮,就可以进入仿真了,仿真器要事先连接好哟。进入仿真后要退出仿真环境也是点这个按钮。注意,等会如果程序在正在全速运行时,仿真环境是不能直接退出的,得先点停止运行后,再点仿真按钮才可以退出。点进入仿真按钮,程序开始装载,PC 自动运行到了main()停下,并指向了main()函数的第一行。 进入仿真窗口后,如果出现的不是前面的源代码窗口,而是夹有反汇编代码的窗口,直接关掉这个窗口就会恢复到代码窗口。下次进入也会直接进入到源代码窗口。 现在先试验单步,点单步(两个单步都可以,一般点单步跨过)。可以看到灯亮了。PC 指针也指向了下一个

程序行。再点一下单步,PC 又走下一步,灯灭了。再点一次,PC 走到挂起的程序行了,继续点仍然在这一行。这句指令其实就是使程序不断地跳到自己这一行,别的什么也不做。一般称作程序挂起。

一般的实际应用中的程序是不会挂起的,一般是在main 函数里做一个大循环,程序如下:

void main(void) // 主程序 {

while(1) {

P11=0;//亮灯 P10=1;//灭灯 } }

请将main 函数程序改为上面的代码,我们下一步将试验断点的操作。

在第15 行双击一下,可以看到程序行左边出现了一个红方块,这就是设置断点,再双击一次,断点就取消了。如果程序在全速运行的过程中遇到断点,就会自动停下来给你分析。注意在进入仿真后,并且程序是停止状态时,才可以设置或者取消断点。

现在点全速运行,可以看到程序在断点处停了下来,并且由于前一句指令刚刚执行了点灯,所以这时灯是亮着的。

现在在第14 行设置断点,并且取消上一个断点。

现在点全速运行,可以看到程序在断点处停了下来,并且由于刚刚执行了灭灯,灯是灭着的。好,现在试验全速运行和停止。把断点取消,再点全速运行,可以看到灯是亮着的,但是不是很亮,这是由于程序是循环的,亮灭交替进行,亮的时间并不是全部的时间。现在点停止,可以看到程序停止了,重复几次进行全速和停止,可以发现每次停止的地方不一定是同一位置。

环境调试。

第二课 用指令方式延时闪烁LED 灯

本章将学习如何使LED 闪烁,和如何查看变量的值。单片机内部的CPU 工作都是要靠时钟驱动的。在标准51 芯片中,每个指令周期是12 个时钟。所以只要外部时钟固定,某一条指令运行的时间也是固定的。比如本试验中的单片机晶振振荡输出的时钟是22118400HZ,一条单周期指令执行的时间就是12/22118400秒=5.425347×10-7 秒,这样如果你想在程序里延迟一段时间,就可以用循环执行多少条指令来实现。这是一个最简单的延时方法,优点是不占用其他的单片机资源,缺点是不容易计算准确延时时间,而且延时过程中CPU 无法做其他工作。指令延时方法一般用在一些不用精确计时的场合。在需要精确计时的场合,需要使用定时器,在之后的课程中将会学到。

程序由一个循环组成,在点亮P10 口的LED 之后,延时一段时间,再灭掉LED,又延时一段时间,之后循环到前面。for()循环后面直接一个分号,表示这个循环里面什么事情也不做,就等循环完成指定的次数就退出来。这也是指令循环延时的最常见的C 写法。编译后,按进入仿真。

按全速运行,可以看到P1.0 的LED 灯不断地闪烁。下面我们用另一个更简单的方式点灯,就是取反IO 口的状态。取反指令将当前bit 变量的状态反转,当前是1,取反后就是0,当前是0,取反后就是1。IO 口相当于一个bit 变量,也可以这样取反。请修改程序如下:

编译成功后,再点全速运行。同样可以看到LED 闪烁的现象。可以看到,这种方法,我们只需要一次延时,就可以实现闪烁了。下面我们再来学习如何查看变量n 在运行中的值。注意,要查看变量的值,只能在程序停下来的状态下查看。在程序运行的过程中,程序不断地运行,变量也在不断地变化,一般是无法查看的。点停止程序,将鼠标放在程序中的“n”上面。

可以看到旁边出现了一个小框框,上面显示了n=0x47D3,这就是变量此时的值。如果

觉得这样可能会点不准确,可以选中你要看的变量,同样会显示变量的值,个人感觉这种

操作更为方便。如图:

在命令行输入的方法也可以看变量,在命令行输入n,回车,就看到结果了。请注意

看下图的命令行窗口的结果。

这里再教一招,如果我想让n 现在就变成我想要的值怎么办?这也是调试常见的手段,设置一个变量的值,比如,让n =0x1234,只要在命令框里输入“n=0x1234”就行了,

几乎所有变量都可以这样直接设置,包括IO 口,比如你输入“P1.1=0”, 结果第二个灯就亮了。还有一招常用的,就是在watch 窗口看变量。点watch 图标,就是那个有个眼镜

的图标,打开watch 窗口。如图:

这个窗口里有locals 页就是当前函数使用的变量的列表,还有有watch 1 和2 两个窗口,就是自定义要看的变量的值,可以手工输入,也可以选中某个变量,按右键,将出现一个菜单。选择add 到watch 窗口即可,在程序停止时随时看到此变量的值。注意要看某个变量,如果这个变量是某个函数私有的,必须是程序停止时并且PC 已经停止在了这个函数中才可以看到,各种看变量的情况都是这样。还有一种直接看存储器的方法,可以看到所有存储器的值,但是和变量名称就不是那么好对应起来了。点memory 窗口图标,

打开memory 窗口,如图:

在Address 窗口输入:“d:0x00”就可以看到data 空间的从0x00 开始的所有内存。

如上图。

输入“i:0x00”,就可以看到idata 空间的所有内存的值。 输入“x:0x00”,就可以看到xdata 空间的所有内存的值。 输入“c:0x00”,就可以看到code 空间的所有程序。

在实际的硬件调试方式中,如果不用看memery 窗口,就建议不用打开它。因为保持它

的打开会增加仿真时通讯的时间,特别是单步运行的时间。

这一章就完成了,我们学会了,指令延时,取反的用法,还有更重要的就是如何在keil

调试环境中查看变量。

第三课 跑马灯试验

在本课中,你可以学习到几乎所有单片机试验课程都会介绍到跑马灯试验。 打开工程文件,如图:

这里实现跑马灯的方法是,依次灭掉前一个灯和点亮后一个灯,再延时一会,不断循环,就可以看到跑马灯的效果了。

请在编译后,进入仿真,点全速运行看结果。 好好研究这段代码,可以自己试着自己修改代码:

例程中的跑马灯在同一时刻只显示1个灯,现在改为同时亮着2个灯的跑马灯。 4课 读IO,用按钮控制点灯

请看一下电路,今天我们要学习用单片机读取按键的值,并且使用一个按键K1去控制点亮P1.0控制的LED,用另一个按键K2去控制P1.1控制的LED。看电路图,K1是接在P32上的,K2是接在P35上的。

下面讲述一下识别按键的原理。在单片机中,我们可以读取某个IO的值。在51的IO口,如果处于输出1的状态(51上电后IO就默认为1),这时IO内部可以简化为有一个几十K的电阻上拉到电源VCC(P0除外),这时这个IO就可以作为输入脚用。P0是没有上拉的,相当于一个悬空的引脚,就是高阻状态,如果用P0,必须在外部接上拉电阻。我们这里用的是P3口的IO,内部有上拉。

如果直接读一个没有按下按键的IO,就会读到1。如果这个按键按下了,这个IO就通过按键短路到了地。这是就会读到0。这就是读按键的原理。 下面看程序:

编译,进入仿真,开始全速运行。

这时可以实际操作一下,按下K1,灯亮;按下K2,灯灭。

第5课 标记的用法,用一个按键控制1个LED灯的亮灭,按键防抖动

这一课,我们学习怎么用一个按键K1控制1个LED灯的亮和灭两种状态。按一次K

1灯亮,再按一次K1灯灭。再按一次又亮,再按一次又灭。

我们学习一下用一个bit变量来做一个标记,然后在按键的控制下,这个标记会变化,

再根据这个标记的值,LED也输出不同的状态。

因为按键按下时可能会有抖动的情况,每次按下时,可能会发生了人难以觉察到的多次抖

动,相当于一下子按下了很多次。这会导致程序无法识别出您真正的按键意图。

但是抖动一般都是发生在刚按下键和松开键的时候,所以,我们只要避开这一段时间,

等键稳定按下或者松开时,再去读它的值,一般就可以正确读取了。

所以,当读到第一次按键的值时,要延时等待一会,再处理。在松开后,也延时一会,免得检测到松开的抖动以为又有按键。(注,更复杂的应用,需要在按下延时之后重新验证

按键,为了简化和方便理解,这个例程里没有这样做。)

另外,因为程序是循环运行的,当一次按键处理后,又会再循环回来继续检测,如果您的按键这时还没有松开,又会被读到一次新的按键,并做处理。所以我们还要做一个特殊的处理,识别到一个按键并处理完成之后,还要等待这个按键松开后,再继续循环运行。

请根据例程里的注释理解程序。 请编译,进入仿真,全速运行,看结果。

全速后,由于light变量初始化时默认为0,所以灯是亮的。按下K1,松开后,灯灭

了;再按一次K1,松开后,灯灭了。

这个例子里,我们只用一个按键就控制了灯的亮灭,这种方法可以节省了硬件资源,也就是节省了硬件成本。在实际项目设计中,有成本优势,产品就更具竞争力。所以我们

应该多学习类似的可以节省资源的方法。

proteus仿真换个帖子来讲讲了,毕竟硬件实物调程序更直观和实际呢,呵呵。

第6课 用定时器中断闪灯,定时器中断的学习

在第二课,我们学习了用指令延时闪灯,但是用指令方式闪灯有cpu不能做其他工作

的缺点。这一课,我们将学习如何使用定时器方式使灯闪烁。

中断的理解。

这里将涉及到单片机中断的应用,在cpu的一步步按照指令运行的过程中(主程序),可能会有其它的更紧急的需要做的事情(中断服务程序),需要cpu暂时停止当前的程序(主程序),做完了(中断服务程序)之后,又可以继续去运行先前的程序(主程序)。就像你正在吃饭,一边又在给水桶里放水,吃着吃着,水满了,你就得赶快去把水龙头关掉或者

换一个空的水桶,再回来吃饭。

单片机的定时器就像是一个水桶,你让它启动了,也就是水龙头打开了;开始装水了;定时在每个机器周期不断自动加1,最后溢出了;水桶的水不断增加,最也就满出来了;定时器溢出时,你就要去做处理了;水桶的水满了,你也应该处理一下了;处理完后,单片机又可以回到刚刚开停止的地方继续运行;水桶处理了,先前你在做什么也可以继续去做什

么了。

单片机的主程序是从0x0000开始运行的,单片机服务程序从哪里开始运行呢?在51里,有多个中断服务程序入口,0号入口是外中断0,地址在0x0003;1号入口是定时器0,在0x000B;2号入口是外中断1;地址在0x0013,3号入口是定时器2;地址在0x001B,等等。当中断发生时,程序就记下当前运行的位置,跳到对应的中断入口去运行中断服务

程序,运行完之后,又跳回到原来的位置继续运行。

在C51中,你不用理会中断服务程序放在哪里,会怎么跳转。你只要把某个函数标识为几

号中断服务函数就可以了。在发生了对应的中断时,就会自动的运行这个函数。 请看一下相关的51的硬件的书,对定时器工作的寄存器设置做进一步的了解。也可以

做完试验再了解,因为例程中都已经为您设置好了。

请看程序,主程序里的循环里是个死循环,什么也没有做,在实际应用中这里是放的

主程序。

在定时器服务函数里,需要重新置入定时器的值,这样才能保证每次溢出时,都是你指定的时间。这里置入的是0x0006,还需要走0x10000-0x0006个机器周期才溢出。换成10进制也就是每65530个机器周期中断一次。我们仿真的晶振是22118400HZ,每12个时钟一个机器周期。65530×12/22118400=0.036秒。也就是差不多28HZ的闪烁频率。 因为51的定时器最大只有0xffff,溢出的速度很快,无法做出更久的闪烁频率来,这一课就先观察一下这个28HZ左右频率。在下一课我们会用静态变量的办法,做一个长达1秒

钟的LED闪烁频率。

另外,由于51从中断发生到进入中断的时间不定,是3至8个机器周期,我们在进入了中断后才重新置新的定时器初始值,这样就会存在定时误差。也就是不是精确定时,如果要精确定时,需要使用定时器自动装载方式,也就是在定时器溢出的同时,硬件逻辑就自动把定时器初始值装载进去了,而不是在中断服务程序里赋初始值,这样就可以实现

精确定时,误差只出现晶振的频率上。这是下一颗的内容。

现在请仔细研究一下程序,并编译,进入仿真,全速运行,观察运行结果。我们可以看到P

10上的LED在快速闪烁。

顺便,也请再练习一下停止,单步,断点等等的调试方法。

一个特殊的地方,使用DX516在单步时运行时,可能无法进入到中断服务函数中。这是因为中断函数可能在单步处理的瞬间已经运行过去了。如果要单步调试中断服务函数,请在中断服务函数内设置断点,再点全速。稍后就会停止在断点上,就可以继续单步运行

了。 如图:

还有,在使用DX516仿真器时,你输入EA查看它的值时,会发现它等于0,而你明

明在程序中置了1。 第7课 精确定时1秒钟闪灯

这一课,我们将学习如何精确定时1秒钟闪灯。上节介绍过,要精确定时,必须使用自装载方式。这里我们使用T2定时器,让它工作在16bit自动装载方式,这时,有另一个位置专门装着16位预装载值,T2溢出时,预装载值立即被置入。这就保证了精确定时。 但是,即使是16位定时器,最长的溢出时间也就几十毫秒,要定时一秒,就需要一个变量来保存溢出的次数,积累到了多少次之后,才执行一次操作。这样就可以累加到1秒

或者更长的时间才做一次操作了。

T2定时器有个特殊的地方,它进入中断后,需要自己清除溢出标记,而51的其他定

时器是自动清除的。请参考51单片机相关书籍。

如果使用T2定时器实现1秒精确定时?

下面我们就来计算:

仿真器的晶振是22118400HZ,每秒钟可以执行1843200个机器周期。而T2每次溢出最多65536个机器周期。我们尽量应该让溢出中断的次数最少,这样对主程序的干扰也

就最小。

选择每秒中断24次 ,每次溢出1843200/24=76800个机器周期,超出65536,无效。

选择每秒中断30次 ,每次溢出1843200/30=61440个机器周期 选择每秒中断32次 ,每次溢出1843200/32=57600个机器周期 选择每秒中断36次 ,每次溢出1843200/36=51200个机器周期 选择每秒中断40次 ,每次溢出1843200/40=46080个机器周期

从上面可以看到我们可以选择方式有很多,但是最佳的是每秒中断30次,每次溢出61440个机器周期。也就是赋定时器T2初值65536-61440=4096,换成十六进制就是0x

1000。

从上面的计算也可以看出晶振2118400Hz的好处,它可以整除的倍数多,要准确定时非常方便。更常见的应用是在串口波特率上,使用22118400HZ可以输出最多准确的标准

波特率。

请打开程序,如图:

我们在定时器服务函数里,设置了一个静态变量t,静态变量的值在进入函数时是不会被初始化的,而是保持上次的值。它用来计数定时器的溢出次数,也就是T2中断函数进入的次数,每溢出30次,就控制一次LED的反转显示。这时的时间就正好是1秒,而且是

精确的1秒!只与晶振的精度有关。

请编译,进入仿真,全速运行。可以看到,LED在亮一秒,灭一秒,不断循环闪烁。

这种精确定时,可以用在时钟的计算、显示上。

第8课,定时器中断跑马灯

在第3课,我们用指令延时方式实现了跑马灯。这里我们用定时器方式再次实现,定

时器方式有效率高,定时准确等优点。

一个编程经验是,所有的中断都要尽快的运行和退出,中断服务程序越短越好,这样

才不至于干扰主程序的工作和其他中断的运行。也就是,我们应该尽量把程序代码从中断

服务函数里搬出来。

对于定时器的中断的工作方式,我们可以建立一个全局的标记,在中断里置这个标记,然后就退出。在主程序里检查到这个标记之后,就运行相关的程序。对于CPU任务比较多的项目来说,这种工作方式可以获得最佳的工作效率。当然,对于非常实时的应用要求,,比如时钟,还是建议在中断里做完,因为使用标记的方式时,主程序可能太忙而造成错过标记信号,就是这个标记还没有开始处理呢,下一个又来了。熟练的程序员还是可以避开

这些异常的情况的。

在我们的这个例程中,前一课的1秒钟输出信号,被换成了一个全局标记。在主程序

中去检查这个标记,再清0标记和处理相应的工作。

这一课的跑马灯输出方式也改变了,我们采用查表的方式,将要点亮的灯预先设置好,

到了时间,就一起送到P1口。这样,程序的执行效率会更高。

下面请认真学习和分析例程:

以下是例程,请打开lesson8目录的工程,内容是一样的。

#define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 sbit P10 = P1^0; //头文件中没有定义的IO就要自己来定义了

sbit P11 = P1^1; sbit P12 = P1^2; sbit P13 = P1^3;

bit ldelay=0; //长定时溢出标记,预置是0

char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

//定时器中断方式的跑马灯 void main(void) // 主程序

{

uchar code ledp[4]={0xfe,0xfd,0xfb,0xf7};//预定的写入P1的值

uchar ledi; //用来指示显示顺序

RCAP2H =0x10; //赋T2的预置值0x1000,溢出30次就是1秒钟

RCAP2L =0x00; TR2=1; //启动定时器 ET2=1; //打开定时器2中断

EA=1; //打开总中断 while(1) //主程序循环

{

if(ldelay) //发现有时间溢出标记,进入处理

{

ldelay=0; //清除标记

P1=ledp[ledi]; //读出一个值送到P1口

ledi++; //指向下一个

if(ledi==4)ledi=0; //到了最后一个灯就换到第一个

}

} }

//定时器2中断 timer0() interrupt 5

{

static uchar t;

TF2=0; t++;

if(t==30) //T2的预置值0x1000,溢出30次就是1秒钟,晶振22118400HZ

{ t=0;

ldelay=1;//每次长时间的溢出,就置一个标记,以便主程序处理

} }

编译,进入仿真,看结果。可以看到4个灯以精确的1秒的速度不断循环跑动。

第9课,自动变速的跑马灯试验

这一课,我们仍然使用上一个定时器跑马灯工作方式,但是我们让跑动的速度自动变

化,从慢到快。

相对于上一颗的跑马灯试验,我们新设置了一个变量speed,用来保存跑马灯的移动

速度,其实也就是定时器的累计时间溢出次数。

我们在程序中修改speed的数值,溢出的时间就会改变,跑马灯的移动速度也就改变

了。

我们是在每循环跑完一圈,就改变一次速度的。

请仔细研究代码,做到充分理解。

源代码如下:请打开对应目录里的例程,和下面的代码是一样的。

――――――――――――――――――――――― #define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 sbit P10 = P1^0; //头文件中没有定义的IO就要自己来定义了

sbit P11 = P1^1; sbit P12 = P1^2; sbit P13 = P1^3;

bit ldelay=0; //长定时溢出标记,预置是0

uchar speed=10; //设置一个变量保存跑马灯的移动速度 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

//自动变速的跑马灯试验 void main(void) // 主程序

{

uchar code ledp[4]={0xfe,0xfd,0xfb,0xf7};//预定的写入P1的值

uchar ledi; //用来指示显示顺序

RCAP2H =0x10; //赋T2的预置值0x1000,溢出30次就是1秒钟

RCAP2L =0x00; TR2=1; //启动定时器 ET2=1; //打开定时器2中断

EA=1; //打开总中断 while(1) //主程序循环

{

if(ldelay) //发现有时间溢出标记,进入处理

{

ldelay=0; //清除标记

P1=ledp[ledi]; //读出一个值送到P1口

ledi++; //指向下一个

if(ledi==4)

{

ledi=0; //到了最后一个灯就换到第一个

speed--;

if(speed==0)speed=10;//每循环显示一次,就调快一次溢出速度

} } } }

//定时器2中断 timer2() interrupt 5

{

static uchar t;

TF2=0; t++;

if(t==speed) //比较一个变化的数值,以实现变化的时间溢出

{ t=0;

ldelay=1;//每次长时间的溢出,就置一个标记,以便主程序处理

} }

―――――――――――― 请编译,运行,并查看结果。

第10课,4个按键4级变速的跑马灯试验,多任务的工作方式

这一课,我们要用4个按键,控制跑马灯的4种不同的跑动速度。

按键的控制我们也做过了,结合跑马灯,很容易程序就出来了。只是每按一个键,就赋给

一个不同的定时器2溢出次数而已。

我们设置为1秒,1/2秒,1/5秒,1/10秒四个档次,分别时K1-K4控制。

这个程序的主程序执行了2个任务。一个是跑马灯,一个是检测按键。程序的结构非常清

晰。

――――――――――――――――

程序如下:

#define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 sbit P10 = P1^0; //头文件中没有定义的IO就要自己来定义了

sbit P11 = P1^1; sbit P12 = P1^2; sbit P13 = P1^3; sbit K1= P3^2; sbit K2= P3^5; sbit K3= P2^4; sbit K4= P2^5;

bit ldelay=0; //长定时溢出标记,预置是0

uchar speed=10; //设置一个变量保存默认的跑马灯的移动速度 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

//自动变速的跑马灯试验 void main(void) // 主程序

{

uchar code ledp[4]={0xfe,0xfd,0xfb,0xf7};//预定的写入P1的值

uchar ledi; //用来指示显示顺序

RCAP2H =0x10; //赋T2的预置值0x1000,溢出30次就是1秒钟

RCAP2L =0x00; TR2=1; //启动定时器 ET2=1; //打开定时器2中断

EA=1; //打开总中断 while(1) //主程序循环

{

if(ldelay) //发现有时间溢出标记,进入处理

{

ldelay=0; //清除标记

P1=ledp[ledi]; //读出一个值送到P1口

ledi++; //指向下一个

if(ledi==4)

{

ledi=0; //到了最后一个灯就换到第一个

} }

if(!K1)speed=30; //检查到按键,设置对应的跑马速度

if(!K2)speed=15; if(!K3)speed=6; if(!K4)speed=3;

}

}

//定时器2中断 timer2() interrupt 5

{

static uchar t;

TF2=0; t++;

if((t==speed)||(t>30)) //比较一个变化的数值,以实现变化的时间溢出,同时了最

慢速度 { t=0;

ldelay=1;//每次长时间的溢出,就置一个标记,以便主程序处理

} }

―――――――――――――――― 请打开工程,编译,运行。

可以看到,启动后,以默认的速度跑马,按K1,速度是1秒一个灯,按K2,是1/2

秒一个灯,按K3是1/5秒一个灯,按K4,则最快,是1/10秒。

第11课,一个按键控制的10级变速跑马灯试验

在本课中,我们要用一个按键来实现跑马灯的10级调速。这又会涉及到键的去抖的问题。

本课的试验结果是,每按一次按键,跑马速度就降低一级,共10级。 这里我们又增加了一个变量speedlever,来保存当前的速度档次。

在按键里的处理中,多了当前档次的延时值的设置。

请看程序:

――――――――――――――――

#define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 sbit P10 = P1^0; //头文件中没有定义的IO就要自己来定义了

sbit P11 = P1^1; sbit P12 = P1^2; sbit P13 = P1^3; sbit K1= P3^2;

bit ldelay=0; //长定时溢出标记,预置是0

uchar speed=10; //设置一个变量保存默认的跑马灯的移动速度

uchar speedlever=0; //保存当前的速度档次

char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

//一个按键控制的10级变速跑马灯试验

void main(void) // 主程序

{

uchar code ledp[4]={0xfe,0xfd,0xfb,0xf7};//预定的写入P1的值

uchar ledi; //用来指示显示顺序

uint n;

RCAP2H =0x10; //赋T2的预置值0x1000,溢出30次就是1秒钟

RCAP2L =0x00; TR2=1; //启动定时器 ET2=1; //打开定时器2中断

EA=1; //打开总中断 while(1) //主程序循环

{

if(ldelay) //发现有时间溢出标记,进入处理

{

ldelay=0; //清除标记

P1=ledp[ledi]; //读出一个值送到P1口

ledi++; //指向下一个

if(ledi==4)

{

ledi=0; //到了最后一个灯就换到第一个

} }

if(!K1) //如果读到K1为0

{

for(n=0;n<1000;n++); //等待按键稳定

while(!K1); //等待按键松开

for(n=0;n<1000;n++); //等待按键稳定松开

speedlever++;

if(speedlever==10) speedlever=0;

speed=speedlever*3; //档次和延时之间的预算法则,也可以用查表方法,

做出不规则的法则

} } }

//定时器2中断 timer2() interrupt 5

{

static uchar t;

TF2=0; t++;

if((t==speed)||(t>30)) //比较一个变化的数值,以实现变化的时间溢出,同时了最

慢速度为1秒

{ t=0;

ldelay=1;//每次长时间的溢出,就置一个标记,以便主程序处理

} }

―――――――――――――――――――――― 请打开lesson11目录的工程,编译,运行,看结果:

按K1,速度则降低一次,总共10个档次。

第12 课,可编程自动控制控制跑马灯

这一颗,我们学习如何让跑马灯自动按照我们预定的顺序进行。这种控制在工控场合经常

用到。

这个程序里,我们预先定义了一个变化的顺序speedcode,每跑一圈灯就根据预定设置的表格数据来决定下一圈的跑马速度。这样我们就实现了按照预定的顺序自动变化运行。

请看代码:

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

#define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52 标准内核的头文件

sbit P10 = P1^0; //头文件中没有定义的IO 就要自己来定义了

sbit P11 = P1^1; sbit P12 = P1^2; sbit P13 = P1^3;

bit ldelay=0; //长定时溢出标记,预置是0

uchar speed=10; //设置一个变量保存跑马灯的移动速度

uchar code speedcode[10]={3,1,5,12,3,20,2,10,1,4}; //10 个预定义的速度

char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

//可编程自动控制跑马灯

void main(void) // 主程序

{

uchar code ledp[4]={0xfe,0xfd,0xfb,0xf7};// 预定的写入P1 的值

uchar ledi; //用来指示显示顺序

uchar i;

RCAP2H =0x10; // 赋T2 的预置值0x1000,溢出30 次就是1 秒钟

RCAP2L =0x00; TR2=1; //启动定时器 ET2=1; //打开定时器2 中断

EA=1; // 打开总中断 while(1) //主程序循环

{

if(ldelay) //发现有时间溢出标记,进入处理

{

ldelay=0; //清除标记

P1=ledp[ledi]; // 读出一个值送到P1 口

ledi++; //指向下一个

if(ledi==4)

{

ledi=0; //到了最后一个灯就换到第一个

// 每跑一圈灯就根据预定设置的表格来决定下一圈的跑马速度

speed=speedcode;

i++;

if(i==10)i=0;

} }

} }

//定时器2 中断 timer2() interrupt 5

{

static uchar t;

TF2=0; t++;

if(t==speed) // 比较一个变化的数值,以实现变化的时间溢出

{

t=0; ldelay=1;// 每次长时间的溢出,就置一个标记,以便主程序处理

} }

————————————————————————————————

请编译,运行,并看运行结果。

第13课 用外中断方式读按键,控制灯的亮灭

这一颗,我们学习外中断的用法。也就是外部IO的中断INT0,和INT1。对应的引脚是P32和P33。在我们的电路图中,P32也就是接在K1的引脚。所以当我们按下P32接到地

的时候,可以触发一个INT0中断,当然,必须预先初始化才会启动。

这种中断方式的按键,可以实现按键的立即响应。对于需要快速响应的场合是很有用的。

外部IO中断还常用在用IO模拟通讯的场合,可以对数据的到来立即响应。

下面请看代码:

―――――――――――――――――

#define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 sbit P10 = P1^0; //头文件中没有定义的IO就要自己来定义了

sbit P11 = P1^1; sbit P12 = P1^2; sbit P13 = P1^3; sbit K1= P3^2;

bit ldelay=0; //长定时溢出标记,预置是0

uchar speed=10; //设置一个变量保存默认的跑马灯的移动速度

uchar speedlever=0; //保存当前的速度档次

char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

//用外中断方式读按键K1,点亮一个LED

void main(void) // 主程序

{

IT0=1; //外中断跳变产生中断

EX0=1;

EA=1; //打开总中断 while(1) //主程序循环

{ } } //外中断0 int0() interrupt 0

{

P10=0; //在中断里点亮LED

}

――――――――――――――――――――――――――――――――――

这个程序里,按一下K1(P32)之后,就会触发INT0中断,在该中断里点亮LED灯。 请编译运行,并看结果。可以看到,在按下K1之后,LED1变处于亮着的状态。

第14课 模拟PWM输出控制灯的10个亮度级别

LED一般是恒流操作的,如何改变LED的亮度呢?答案就是PWM控制。在一定的频率的方波中,调整高电平和低电平的占空比,即可实现。比如我们用低电平点亮一个LED灯,我们假设把一个频率周期分为10个时间等份,如果方波中的高低电平占空比是9:1,这是就是一个比较暗的亮度,如果方波中高低电平占空比是10:0,这时,全部是高电平,灯是灭的。如果占空比是5:5,就是一个中间亮度,如果高低比是1:9,是一个比较亮的

亮度,如果高低是0:10,这时全部是低电平,就是最亮的。

实际上应用中,电视屏幕墙中的几十百万LED象素都是这样控制的,而且每一个象素都有红绿蓝3个LED,每个LED可以变化的亮度是几百到几万或者更多的级别,以实现真彩色的显示。还有在您的手机中,背光灯的亮度如果是可以变化的,也应该是这种工作方

式。目前的城市彩灯也有很多都使用了LED,需要控制亮度是也是PWM控制。 下面来分析我们的例程,在这个例程中,我们将定时器2溢出定为1/1200秒。每10次脉冲输出一个120HZ频率。这每10次脉冲再用来控制高低电平的10个比值。这样,在每个1/120秒的方波周期中,我们都可以改变方波的输出占空比,从而控制LED灯的10

个级别的亮度。

为什么输出方波的频率要120HZ这么高?因为如果频率太低,人眼就会看到闪烁感觉。一般起码要在60HZ以上才感觉好点,120HZ就基本上看不到闪烁,只能看到亮度的

变化了。

下面请看程序,程序中有比较多的注释:

――――――――――――――――――――――― #define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件

sbit P10 = P1^0; //要控制的LED灯

sbit K1= P3^2; //按键K1

uchar scale;//用于保存占空比的输出0的时间份额,总共10份 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

//模拟PWM输出控制灯的10个亮度级别

void main(void) // 主程序

{

uint n;

RCAP2H =0xF3; //赋T2的预置值,溢出1次是1/1200秒钟

RCAP2L =0x98; TR2=1; //启动定时器 ET2=1; //打开定时器2中断

EA=1; //打开总中断 while(1) //程序循环

{ ;//主程序在这里就不断自循环,实际应用中,这里是做主要工作 for(n=0;n<50000;n++); //每过一会儿就自动加一个档次的亮度

scale++;

if(scale==10)scale=0;

} }

//1/1200秒定时器2中断 timer2() interrupt 5

{

static uchar tt; //tt用来保存当前时间在一秒中的比例位置

TF2=0; tt++;

if(tt==10) //每1/120秒整开始输出低电平

{ tt=0;

if(scale!=0) //这里加这一句是为了消除灭灯状态产生的鬼影

P10=0;

}

if(scale==tt) //按照当前占空比切换输出高电平

P10=1;

}

――――――――――――――――――

在主程序中,每延时一段时间,就自动换一个占空比,以使亮度自动变化,方便观察。

编译,运行,看结果。

可以看到,LED的亮度以每种亮度1秒左右不断变化,共有10个级别。

第15课 写一个字节到24c02中

24c02是一个非挥发eeprom存储器器件,采用的IIC总线技术。24c02在许多试验中都有出现。24c02的应用,主要在存储一些掉电后还要保存数据的场合,在上次运行时,

保存的数据,在下一次运行时还能够调出。

24c02采用的IIC总线,是一种2线总线,我们在试验中用IO来模拟这种总线,至于总线的时序和原理,请参考相关资料。如果您不想研究,也没有关系,我们在程序中已经

为你写好了,现在和今后您都可以只调用就是,不必花时间和精力去研究。

一块24c02中有256个字节的存储空间。

我们将24c02的两条总线接在了P26和P27上,因此,必须先定义:

sbit SCL=P2^7; sbit SDA=P2^6;

在这个试验中,我们写入了一个字节数值0x88到24c02的0x02的位置。 写入完成后,P10灯会亮起,我们再在下一颗来读出这个字节来验证结果。

―――――――――――――

#define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件

//本课试验写入一个字节到24c02中

char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

#define WriteDeviceAddress 0xa0 //定义器件在IIC总线中的地址

#define ReadDviceAddress 0xa1

sbit SCL=P2^7; sbit SDA=P2^6; sbit P10=P1^0; //定时函数

void DelayMs(uint number)

{

uchar temp;

for(;number!=0;number--)

{

for(temp=112;temp!=0;temp--) ;

} }

//开始总线 void Start()

{

SDA=1; SCL=1; SDA=0; SCL=0;

}

//结束总线

void Stop()

{

SCL=0; SDA=0; SCL=1; SDA=1;

}

//测试ACK bit TestAck()

{

bit ErrorBit; SDA=1; SCL=1; ErrorBit=SDA;

SCL=0;

return(ErrorBit);

}

//写入8个bit到24c02 Write8Bit(uchar input)

{

uchar temp;

for(temp=8;temp!=0;temp--)

{

SDA=(bit)(input&0x80);

SCL=1; SCL=0;

input=input<<1;

} }

//写入一个字节到24c02中

void Write24c02(uchar ch,uchar address)

{

Start();

Write8Bit(WriteDeviceAddress);

TestAck();

Write8Bit(address);

TestAck(); Write8Bit(ch); TestAck(); Stop(); DelayMs(10);

}

//本课试验写入一个字节到24c02中

void main(void) // 主程序

{

Write24c02(0x88,0x02);// 将0x88写入到24c02的第2个地址空间

P10=0; //指示运行完毕 while(1); //程序挂起

}

――――――――――――――――― 编译,联机进入仿真,等待LED亮起。 第16课 写入一个字节到24c02并读出来验证

本课的程序已经包含了上一颗的内容,增加了读24c02的函数,请看程序:

―――――――――――――――――――――――――――――

#define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

#define WriteDeviceAddress 0xa0 //定义器件在IIC总线中的地址

#define ReadDviceAddress 0xa1

sbit SCL=P2^7; sbit SDA=P2^6; sbit P10=P1^0; //定时函数

void DelayMs(unsigned int number)

{

unsigned char temp; for(;number!=0;number--)

{

for(temp=112;temp!=0;temp--) ;

} }

//开始总线 void Start()

{

SDA=1; SCL=1; SDA=0; SCL=0;

}

//结束总线 void Stop()

{

SCL=0; SDA=0; SCL=1;

SDA=1;

}

//发ACK0 void NoAck()

{

SDA=1; SCL=1; SCL=0;

}

//测试ACK bit TestAck()

{

bit ErrorBit; SDA=1; SCL=1; ErrorBit=SDA;

SCL=0;

return(ErrorBit);

}

//写入8个bit到24c02

Write8Bit(unsigned char input)

{

unsigned char temp; for(temp=8;temp!=0;temp--)

{

SDA=(bit)(input&0x80);

SCL=1; SCL=0;

input=input<<1;

} }

//写入一个字节到24c02中

void Write24c02(uchar ch,uchar address)

{

Start();

Write8Bit(WriteDeviceAddress);

TestAck();

Write8Bit(address);

TestAck();

Write8Bit(ch); TestAck(); Stop(); DelayMs(10);

}

//从24c02中读出8个bit

uchar Read8Bit()

{

unsigned char temp,rbyte=0; for(temp=8;temp!=0;temp--)

{

SCL=1;

rbyte=rbyte<<1;

rbyte=rbyte|((unsigned char)(SDA));

SCL=0;

}

return(rbyte);

}

//从24c02中读出1个字节

uchar Read24c02(uchar address)

{

uchar ch; Start();

Write8Bit(WriteDeviceAddress);

TestAck();

Write8Bit(address);

TestAck(); Start();

Write8Bit(ReadDviceAddress);

TestAck(); ch=Read8Bit(); NoAck(); Stop(); return(ch);

}

//本课试验写入一个字节到24c02并读出来验证

void main(void) // 主程序

{

uchar c1,c2;

c1=Read24c02(0x02); Write24c02(0x99,0x03); c2=Read24c02(0x03);

P10=0;

while(1); //程序挂起

}

――――――――――――――――

在主程序中,我们将上一课写入的0x02位置的数据读出来放在c1中,新写了一个数据0

x99在0x03位置中,并立即将它读出来放在c2中。

编译,运行,等P10灯亮后。我们看结果。

这次的看结果,我们要在仿真环境中直接看变量。点程序停止,观察c1和c2的值,可以

看到,分别为:0x88和0x99。数据正确!

第17课 写入按键次数到24c02,并读出来显示在4个LED上

这一课我们用24c02完成一个实际应用的场合,在24c02中记录按键次数并用二机制显示在4个LED上。下次开机时,将继续显示上次的按键次数。这些工作在工控领域有

十分广泛的应用。

程序中,不断读出24c02的0x01位置的数据出来,并显示在P1口上,我们可以在4

个LED上观察到低4位的数据变化。

当检查到按键时,就将前面读出来的值加1,写入在24c02中的同一个位置中。下一

个循环中,值又被读出来并显示。

编译,联机,并运行。不断按K1,可以看到P1的4个LED不断以二机制变化显示。

注:程序代码见附件。

第18课 嘀声报警信号输出试验

这一课,我们将学习如何控制蜂鸣器的声音输出,这一课我们只输出一个频率的声音,之后几课我们将逐步输出更为复杂的音乐声,你甚至可以自己输入一个乐谱,直接播放出

来。

蜂鸣器有有源和无源的几种。也称为直流蜂鸣器和交流蜂鸣器。有源蜂鸣器只要通上直流电,就会发出预定的声音,比如,连续嘀声,或者间断嘀嘀声,这种声音无法控制,频

率也无法改变。一般用在一些简单应用场合。无源蜂鸣器相当于一个简单的喇叭,通上直流点不会发声,只有通上交流电时,才会根据交流点的频率发出相应的声音,这种蜂鸣器

可以任意控制声音输出,但是需要用户以相应的信号驱动,工作复杂一些。

我们的试验使用的是交流蜂鸣器。我们的电路中用P17来驱动。

下面看连续输出一个频率的例程:

――――――――――――

#define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

sbit P10=P1^0; //LED1 sbit K1=P3^2; //K1

sbit BEEP=P1^7; //喇叭输出脚

//嘀声报警信号输出试验 void main(void) // 主程序

{ uint n; while(1)

{

for(n=0;n<100;n++); //延时

BEEP=~BEEP; //取反输出到喇叭的信号

} }

―――――――――――――――――――

程序里,在延时一点时间之后,就将驱动蜂鸣器的引脚取反,不断循环,形成一个交流

信号,蜂鸣器也就响了。

请编译,运行。可以听到发出嘀的连续的声音。

第19课 嘀嘀嘀间断声光报警信号试验

上一课,我们试验蜂鸣器连续的嘀声输出,这一课,我们输出间断的嘀嘀声音。同时,我

们还将一个灯对应声音亮灭。 ―――――――――――――――

#define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

sbit P10=P1^0; //LED1 sbit K1=P3^2; //K1

sbit BEEP=P1^7; //喇叭输出脚

bi(ulong t)

{ ulong c; uint n;

for(c=0;c{

for(n=0;n<50;n++); //延时

BEEP=~BEEP; //取反输出到喇叭的信号

} }

//嘀嘀嘀间断声光报警信号试验 void main(void) // 主程序

{ ulong n; while(1)

{

P10=0; //灯亮 bi(1000); //嘀一阵 P10=1; //灯灭

for(n=0;n<10000;n++); //停一阵

} }

―――――――――――――

这里,将嘀声输出提出来成为了一个函数,函数的入口是,输出多久声音的嘀声。

我们调用一次函数,又延时一阵,不断循环,就形成了间断的嘀嘀声。

第20课 变频声救护车报警信号输出试验

这一课,我们做一个更复杂的声音输出,不断交替输出2个频率的声音,类似救护车的声

音。同时闪烁2个灯。

――――――――――――――――――――― #define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

sbit P10=P1^0; //LED1 sbit P11=P1^1; //LED1 sbit K1=P3^2; //K1

sbit BEEP=P1^7; //喇叭输出脚 //变频声救护车报警信号输出试验 void main(void) // 主程序

{

ulong ul; uint n;

P10=0; //先点一个灯,以便2个灯轮流闪烁

while(1)

{

//输出约1秒种一个频率的声音

for(ul=0;ul<3000;ul++)

{

for(n=0;n<80;n++); //延时

BEEP=~BEEP; //取反输出到喇叭的信号

}

P10=~P10; //闪灯 P11=~P11; //闪灯

//输出约1秒种另一个频率的声音

for(ul=0;ul<2500;ul++)

{

for(n=0;n<100;n++); //延时

BEEP=~BEEP; //取反输出到喇叭的信号

}

P10=~P10; //闪灯

P11=~P11; //闪灯

} }

―――――――――――――――――

第21课 按键音试验

你的手机里应该有这个选项,按键时发出嘀的一声,这时为了让用户知道按键已经生效的提示。我们今天也在我们的试验板上做这个试验,按下K1,就发出短暂的嘀声。

―――――――――――――――――――― #define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

sbit P10=P1^0; //LED1

sbit K1= P3^2; sbit K2= P3^5; sbit K3= P2^4; sbit K4= P2^5;

sbit BEEP=P1^7; //喇叭输出脚

bi(ulong t)

{ ulong c;

uint n;

for(c=0;c{

for(n=0;n<50;n++); //延时

BEEP=~BEEP; //取反输出到喇叭的信号

} }

//按键音试验

void main(void) // 主程序

{ uint n; while(1)

{ if(!K1) {

bi(100); //发出按键音 while(!K1); //等键松开 for(n=0;n<2000;n++); //键去抖

} } }

―――――――――――――――――――――

第22课 音阶声音输出试验

这一课,我们不再输出简单嘀声了,而是要输出各种不同频率的音乐声。先输出基本的音

阶,12345671。

为了输出准确的音阶频率,我们需要用定时器输出来控制蜂鸣器的驱动,这里用的T0。 我们再每一次定时器中断溢出时取反P17引脚,以形成频率驱动蜂鸣器,定时器0工作在16位方式,需要在中断里重新置入初始值。这个值就决定了P17输出的频率。我们在程序里先做好了一张表,预先写好了每个音阶的频率需要设置的初始值。到时调入对应的值进

去T0,不断溢出时就P17可以输出对应的频率。

在这个程序里,我们自动地输出8个音符,每个音符保持1秒钟左右。

――――――――――――――――――――――― #define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

sbit BEEP=P1^7; //喇叭输出脚

uchar th0_f; //在中断中装载的T0的值高8位 uchar tl0_f; //在中断中装载的T0的值低8位

//T0的值,及输出频率对照表 uchar code freq[36*2]={

0xA9,0xEF,//00220HZ ,1 //0 0x93,0xF0,//00233HZ ,1# 0x73,0xF1,//00247HZ ,2 0x49,0xF2,//00262HZ ,2# 0x07,0xF3,//00277HZ ,3 0xC8,0xF3,//00294HZ ,4 0x73,0xF4,//00311HZ ,4# 0x1E,0xF5,//00330HZ ,5 0xB6,0xF5,//00349HZ ,5# 0x4C,0xF6,//00370HZ ,6 0xD7,0xF6,//00392HZ ,6# 0x5A,0xF7,//00415HZ ,7 0xD8,0xF7,//00440HZ 1 //12 0x4D,0xF8,//00466HZ 1# //13 0xBD,0xF8,//00494HZ 2 //14 0x24,0xF9,//00523HZ 2# //15 0x87,0xF9,//00554HZ 3 //16 0xE4,0xF9,//00587HZ 4 //17 0x3D,0xFA,//00622HZ 4# //18 0x90,0xFA,//00659HZ 5 //19 0xDE,0xFA,//00698HZ 5# //20 0x29,0xFB,//00740HZ 6 //21 0x6F,0xFB,//00784HZ 6# //22 0xB1,0xFB,//00831HZ 7 //23 0xEF,0xFB,//00880HZ `1 0x2A,0xFC,//00932HZ `1# 0x62,0xFC,//00988HZ `2 0x95,0xFC,//01046HZ `2# 0xC7,0xFC,//01109HZ `3 0xF6,0xFC,//01175HZ `4 0x22,0xFD,//01244HZ `4# 0x4B,0xFD,//01318HZ `5 0x73,0xFD,//01397HZ `5# 0x98,0xFD,//01480HZ `6 0xBB,0xFD,//01568HZ `6# 0xDC,0xFD,//01661HZ `7 //35

};

//定时中断0,用于产生唱歌频率

timer0() interrupt 1

{

TL0=tl0_f;TH0=th0_f; //调入预定时值 BEEP=~BEEP; //取反音乐输出IO

P2=~P2;

}

//音阶声音自动输出试验 void main(void) // 主程序

{ ulong n; uchar i;

uchar code jie8[8]={12,14,16,17,19,21,23,24};//1234567`1八个音符在频率表中的位置

TMOD = 0x01; //使用定时器0的16位工作模式

TR0 = 1; ET0 = 1; EA = 1; while(1)

{

for(i=0;i<8;i++) //循环播放8个音符

{

tl0_f=freq[jie8*2]; //置一个音符的值

th0_f=freq[jie8*2+1];

for(n=0;n<50000;n++); //延时1秒左右

} } }

――――――――――――――――――――――――

第23课 按键控制音阶声音输出(电子琴)

上次我们实现了通过蜂鸣器自动输出7个音符的试验,这一课我们用按键控制音符的

输出,4个按键输出4个音符,效果就和电子琴的按键一样。

由于平时不能发声,只有按键后才发声,我们用定时器的启动TR0作为声音输出开关。在发现按键后,送入对应频率值,打开定时器,就发出了声音,延时一阵,再关闭定时器,

声音就停止了。

―――――――――――――――――――― #define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

sbit BEEP=P1^7; //喇叭输出脚

sbit K1= P3^2; sbit K2= P3^5; sbit K3= P2^4; sbit K4= P2^5;

uchar th0_f; //在中断中装载的T0的值高8位 uchar tl0_f; //在中断中装载的T0的值低8位

//T0的值,及输出频率对照表 uchar code freq[36*2]={

0xA9,0xEF,//00220HZ ,1 //0 0x93,0xF0,//00233HZ ,1# 0x73,0xF1,//00247HZ ,2 0x49,0xF2,//00262HZ ,2# 0x07,0xF3,//00277HZ ,3 0xC8,0xF3,//00294HZ ,4 0x73,0xF4,//00311HZ ,4# 0x1E,0xF5,//00330HZ ,5 0xB6,0xF5,//00349HZ ,5# 0x4C,0xF6,//00370HZ ,6 0xD7,0xF6,//00392HZ ,6# 0x5A,0xF7,//00415HZ ,7 0xD8,0xF7,//00440HZ 1 //12 0x4D,0xF8,//00466HZ 1# //13 0xBD,0xF8,//00494HZ 2 //14 0x24,0xF9,//00523HZ 2# //15 0x87,0xF9,//00554HZ 3 //16 0xE4,0xF9,//00587HZ 4 //17 0x3D,0xFA,//00622HZ 4# //18 0x90,0xFA,//00659HZ 5 //19 0xDE,0xFA,//00698HZ 5# //20 0x29,0xFB,//00740HZ 6 //21 0x6F,0xFB,//00784HZ 6# //22 0xB1,0xFB,//00831HZ 7 //23 0xEF,0xFB,//00880HZ `1 0x2A,0xFC,//00932HZ `1# 0x62,0xFC,//00988HZ `2 0x95,0xFC,//01046HZ `2# 0xC7,0xFC,//01109HZ `3 0xF6,0xFC,//01175HZ `4 0x22,0xFD,//01244HZ `4# 0x4B,0xFD,//01318HZ `5 0x73,0xFD,//01397HZ `5# 0x98,0xFD,//01480HZ `6 0xBB,0xFD,//01568HZ `6# 0xDC,0xFD,//01661HZ `7 //35

};

//定时中断0,用于产生唱歌频率

timer0() interrupt 1

{

TL0=tl0_f;TH0=th0_f; //调入预定时值 BEEP=~BEEP; //取反音乐输出IO

}

//按键控制音阶声音输出(电子琴)

void main(void) // 主程序

{ ulong n;

uchar code jie8[8]={12,14,16,17,19,21,23,24};//1234567`1八个音符在频率表中的位置

TMOD = 0x01; //使用定时器0的16位工作模式

TR0 = 0; ET0 = 1; EA = 1; while(1)

{ if(!K1) {

tl0_f=freq[jie8[0]*2]; //置一个音符的值

th0_f=freq[jie8[0]*2+1];

TR0 = 1;

for(n=0;n<10000;n++); //延时

} if(!K2) {

tl0_f=freq[jie8[1]*2]; //置一个音符的值

th0_f=freq[jie8[1]*2+1];

TR0 = 1;

for(n=0;n<10000;n++); //延时

} if(!K3) {

tl0_f=freq[jie8[2]*2]; //置一个音符的值

th0_f=freq[jie8[2]*2+1];

TR0 = 1;

for(n=0;n<10000;n++); //延时

} if(!K4) {

tl0_f=freq[jie8[3]*2]; //置一个音符的值

th0_f=freq[jie8[3]*2+1];

TR0 = 1;

for(n=0;n<10000;n++); //延时

} TR0 = 0;

} }

―――――――――――――――――――――

请仔细研读程序,编译,运行看结果。

可以看到,按K1,就发出1的音符,按K2,就发出2的音符,按K3,就发出3的音符,

按K4,就发出4的音符。如果键很多,就可以演奏音乐了!

第24课 单个按键控制多个音阶声音输出

这一课,我们用一个按键,不断按下,就更换音符输出。总共输出8个音符。

――――――――――――――――――――― #define uchar unsigned char //定义一下方便使用

#define uint unsigned int #define ulong unsigned long

#include //包括一个52标准内核的头文件 char code dx516[3] _at_ 0x003b;//这是为了仿真设置的

sbit BEEP=P1^7; //喇叭输出脚

sbit K1= P3^2; sbit K2= P3^5; sbit K3= P2^4; sbit K4= P2^5;

uchar th0_f; //在中断中装载的T0的值高8位 uchar tl0_f; //在中断中装载的T0的值低8位

//T0的值,及输出频率对照表 uchar code freq[36*2]={ 0xA9,0xEF,//00220HZ ,1 //0 0x93,0xF0,//00233HZ ,1# 0x73,0xF1,//00247HZ ,2 0x49,0xF2,//00262HZ ,2# 0x07,0xF3,//00277HZ ,3 0xC8,0xF3,//00294HZ ,4 0x73,0xF4,//00311HZ ,4# 0x1E,0xF5,//00330HZ ,5 0xB6,0xF5,//00349HZ ,5# 0x4C,0xF6,//00370HZ ,6 0xD7,0xF6,//00392HZ ,6# 0x5A,0xF7,//00415HZ ,7 0xD8,0xF7,//00440HZ 1 //12 0x4D,0xF8,//00466HZ 1# //13 0xBD,0xF8,//00494HZ 2 //14 0x24,0xF9,//00523HZ 2# //15 0x87,0xF9,//00554HZ 3 //16 0xE4,0xF9,//00587HZ 4 //17 0x3D,0xFA,//00622HZ 4# //18 0x90,0xFA,//00659HZ 5 //19 0xDE,0xFA,//00698HZ 5# //20 0x29,0xFB,//00740HZ 6 //21 0x6F,0xFB,//00784HZ 6# //22 0xB1,0xFB,//00831HZ 7 //23 0xEF,0xFB,//00880HZ `1

0x2A,0xFC,//00932HZ `1# 0x62,0xFC,//00988HZ `2 0x95,0xFC,//01046HZ `2# 0xC7,0xFC,//01109HZ `3 0xF6,0xFC,//01175HZ `4 0x22,0xFD,//01244HZ `4# 0x4B,0xFD,//01318HZ `5 0x73,0xFD,//01397HZ `5# 0x98,0xFD,//01480HZ `6 0xBB,0xFD,//01568HZ `6# 0xDC,0xFD,//01661HZ `7 //35

};

//定时中断0,用于产生唱歌频率

timer0() interrupt 1

{

TL0=tl0_f;TH0=th0_f; //调入预定时值 BEEP=~BEEP; //取反音乐输出IO

}

//单个按键控制多个音阶声音输出 void main(void) // 主程序

{ ulong n; uchar i;

uchar code jie8[8]={12,14,16,17,19,21,23,24};//1234567`1八个音符在频率表中的位置

TMOD = 0x01; //使用定时器0的16位工作模式

TR0 = 0; ET0 = 1; EA = 1; while(1)

{

if(!K1) //按键K1

{

tl0_f=freq[jie8*2]; //置一个音符的值

th0_f=freq[jie8*2+1];

TR0 = 1;

for(n=0;n<10000;n++); //声音延时

while(!K1);

for(n=0;n<1000;n++); //去抖延时

TR0 = 0;

i++; //循环下一个音符

if(i==8)i=0;

} } }

――――――――――――――――――――

程序简单,不多解释。 请编译,运行,看结果。

可以看到,我们不断按下K1,蜂鸣器就发出不同的音符。总共8个。

第25课 乐谱方式输入的音乐播放“仙剑奇侠传”

这一课开始,我们就要听到美妙的音乐了,这一课,我们可以听到演奏仙剑奇侠传的

乐谱。

这一课的程序,增加了2个比较复杂的函数,一个乐谱解释函数,一个音乐播放函数。我们音乐仙剑奇侠传的乐谱以一个我们自己定义的乐谱形式写好,作为一个预定义的字符串。再通过乐谱解释函数解释为“音符频率的序号”和“音符播放的时间”两个数组,在音乐播放函数中,就将音符频率的序号数组对应的频率送入定时器预置数中,再延时对应

音符播放的时间。这样音乐就播放出来了。

仙剑奇侠传的乐谱:

\"|3_3_3_2_3-|2_3_2_2_,6,6_,7_|12_1_,7,6_,5_|,6---|\" \"3_3_3_2_3.6_|5_6_5_5_22_3_|45_4_32_1_|3.--3_|\"

\"67_6_55_3_|5--3_5_|26_5_32_3_|3---|\" \"26_6_6-|16_6_66_7_|`17_6_76_7_|3.--3_|\" \"67_6_55_3_|5--3_5_|67_6_76_7_|3---|\" \"26_6_6-|16_6_66_7_|`17_6_7.5_|6---|\"

乐谱书写规则:

1 2 3 4 5 6 7 为7个基本音阶 前面加逗号','表示这是低音 前面加上点号'`'表示这是高音 后面加'#',表示这个音符升半个音阶

后面加'.',表示这个音符要再加长自身一半的延时

后面加一个或多个'-',每个表示延时一拍

后面加一个或多个'_',表示这个音符要缩短自身一半的时长,最多支持2个'_'。

这些规则对一般的乐谱都可以应付得来了。

程序代码参考附件内容。

这里最复杂是乐谱解释函数,是逐个字符解释的。基本上是以下过程:遇到拍子分隔符和空格跳过,判断是否高低音,读音符,调整为高低音音符,读音符后的升半个音符的“#”,读延长音“-”“.”,读缩短一半音长的“_”,字符串结束符“0x00”。请仔细领会这

个函数。

奏乐函数就比较简单,基本上就是从数组中取出音符和时长,送入定时器预置数,再延时即可。在每个音符播放前后,用TR0控制是否输出音乐,每个音符之间也有短暂静音,

以使音乐更为清晰。

在本程序中,播放音乐函数中,我们使用了xdata的空间的RAM,这是因为乐谱的数据需要比较多的内存,data和idata空间已经放不下了的原因。由于DX516内部是有768个字XRAM可以直接仿真使用的。所以我们仿真不会有任何问题。但是如果你把这个程序烧写到一片没有XRAM的芯片中,比如atc52之类,就会出现无法运行的现象。在使用没有XRAM的51芯片时,如果使用了XRAM,则要在总线上外加一个内存芯片,比如

62256之类。

完全看懂了程序之后,请编译运行,观察结果。按全速,可以听到美妙的仙剑音乐从

蜂鸣器中传出,真是太奇妙了!

大家可以编写一个其他的你熟悉的比较简单的乐谱,替换掉仙剑音乐。由于在播放函数里只定义112个音符符的空间,注意您的乐谱不要超过这个数目。如果需要调大,注意

编译后不要超过仿真器内部的768个XRAM空间。

第26课 亮灯倒计时10秒,开始播放音乐

这一课,我们用一点简单的控制之后,才开始播放音乐。这种工作方式可以用在一些

计时后报警等场合。

我们采用前面学习过的定时器精确定时,在10秒钟后给出信号,触发音乐播放。

程序代码见附件。

程序里用的是P10来触发,这和内部定义一个bit变量是一样的。只是要注意的是,

触发时用的是IO脚的引脚电平。如果这个IO脚被外部拉低,也会立即播放音乐。 请编译,运行。可以看到,点全速后,P10LED灯亮了10秒钟后,开始不停地播放音

乐。

第27课 三个按键选择三首不同的音乐播放,一个键停止播放

这一课我们用4个按键来控制播放音乐。K1-K3每个键播放一首音乐,K4按键停止

音乐的播放。

程序代码见附件。 在奏乐函数里我们加上了:

if((!K1)||(!K2)||(!K3)||(!K4))//发现按键,立即退出播放

{ TR0=0; return; }

这是为了在正在播放音乐的时候也可以检测到按键,并且停止播放音乐,立即去处理

下一步的工作。

请编译,运行。我们可以看到,按下K1,就播放仙剑,按键K2,K3分别播放其他音乐,按K4,全部音乐都停止播放了。特殊注意的是,每次按键发生时,音乐都是立即停止

了,再开始播放另一首音乐。这就是上面插入的代码的效果。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- 517ttc.cn 版权所有 赣ICP备2024042791号-8

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务