【006】多位数码管动态显示 [51]
点击数:2102 发布日期:2006-4-16 16:04:00 【收藏】 【评论】 【打印】 【编程爱好者论坛】 【关闭】
实验目的:数码管动态显示多位数字。
实验参考:笨笨工作室 实验五、多位数码动态显示。(查看原文)
实验板: FB51A(查看)。
该实验用到实验板的资源电路图如下:
其中P0口是段码,低电平有效。P2口是位码,高电平有效。P2.0口控制第1个数码管,一直到P2.7口控制第8个。该板的段码表如下:
各个数码管的段码都是p0口的输出, 即各个数码管输入的段码都是一样的, 为了使其分别显示不同的数字, 可采用动态显示的方式, 即先只让最低位显示0(含点) ,经过一段延时,再只让次低位显示1,如此类推。由视觉暂留, 只要我们的延时时间足够短,就能够使得数码的显示看起来非常的稳定清楚。过程如下图。
采用上述方法思路编写如下:
org 0000h
start: mov a,#08h ;0 ;段码
mov p0,a
mov p2,#01h ;位码
lcall delay_1ms
mov a,#0abh ;1
mov p0,a
mov p2,#02h
lcall delay_1ms
mov a,#12h ;2
mov p0,a
mov p2,#04h
lcall delay_1ms
mov a,#22h ;3
mov p0,a
mov p2,#08h
lcall delay_1ms
mov a,#0a1h ;4
mov p0,a
mov p2,#10h
lcall delay_1ms
mov a,#24h ;5
mov p0,a
mov p2,#20h
lcall delay_1ms
mov a,#04h ;6
mov p0,a
mov p2,#40h
lcall delay_1ms
; mov a,#0aah ;7
; mov p0,a
mov p0,#0aah ;
以后会有用吧
mov p2,#80h
lcall delay_1ms
ljmp start 感觉用这句和上面两句实现一样,可能这种习惯
delay_1ms: mov r6,#2
temp: mov r5,#0ffh
djnz r5,$
djnz r6,temp
ret
end
下载到板上得到测结果为从低到高八位分别显示0到7(含点)。
★上述方法逐次给P0或者P2赋值,一方面程序的复杂程度增加,另外一方面会使得程序的灵活性降低。如果要改变显示的数字,程序改动起来很麻烦。 所以要用51单片机中常用的一种方法:查表法。例如P0口输出段码时,我们可以把要显示的段码放在一个表格中,然后每次从这个表格里面取数,送到P0口即可。P2口输出位码时,可以把要用的位码放在另一个表格里,每次从此表中取数,送入P2口。这样,如果要改变显示的数字,只需要改变表格里面的数。
org 0000h
start: mov r7,#0ffh ;r7,r6查表时送入变址寄存器a (因自加1后为0, 所以预置ffh)
mov r6,#0ffh
loop: lcall play1 ;调用显示段码子程序
lcall play2 ;调用显示位码子程序
lcall delay_1ms
cjne a,#80h,loop ;判断是否到了最左边的数, 即第8个位码 ajmp start
play1: ;查表求段码子程序
; mov a,r7
; inc a
; mov r7,a
inc r7 ;这2句和上面三条语句实现功能相同
mov a,r7 ;a在这里做变址寄存器
mov dptr,#table1 ;表首址送dptr ,dptr 做基址寄存器
movc a,@a+dptr ;基址寄存器加变址寄存器寻址
mov p0,a
ret
play2: ;查表求位码子程序(原理同play1)
mov a,r6
inc a
mov r6,a
mov dptr,#table2
movc a,@a+dptr
mov p2,a
ret
table1: db 08h,0abh,12h,22h,0a1h,24h,04h,0aah ;段码表
table2: db 01h,02h,04h,08h,10h,20h,40h,80h ;位码表
delay_1ms: mov r5,#02h ;延时1ms 子程序 temp: mov r4,#0ffh
djnz r4,$
djnz r5,temp
ret
end
下载到板上验证得到预想结果。
C51实现如下(参考了AS 的例程):
#include
#include // 包含了左移函数_crol_()
void delayms(unsigned char ms); // 延时子程序
unsigned char data dis_digit; // 位选通值, 传送到P2口用于选通当前数码管的数值,
// 如等于0x01时, 选通P2.0口数码管 unsigned char code dis_code[11]={0x08,0xab,0x12,0x22,0xa1, // 0,1,2,3, 4
0x24,0x04,0xaa,0x00,0x20, 0xff}; // 5,6,7,8,9, off
unsigned char data dis_buf[8]; // dis_buf 显于缓冲区基地址
unsigned char data dis_index; // 显示索引, 用于标识当前显示的数码管和缓冲区的偏移量
void main()
{
P0 = 0xff; // 关闭所有数码管
P2 = 0x00;
dis_buf[0] = dis_code[0];
dis_buf[1] = dis_code[1];
dis_buf[2] = dis_code[2];
dis_buf[3] = dis_code[3];
dis_buf[4] = dis_code[4];
dis_buf[5] = dis_code[5];
dis_buf[6] = dis_code[6];
dis_buf[7] = dis_code[7];
dis_digit = 0x01; // 首先选通P2.0
dis_index = 0; // 当前偏移量为0
while(1)
{
P0 = dis_buf[dis_index]; // 段码送P0口
P2 = dis_digit; // 选能位(即位码) delayms(1); // 延时
dis_digit = _crol_(dis_digit, 1); // 位选通左移, 下次选通下一位
dis_index++; // 下一个段码
dis_index &= 0x07; // 见注释
}
}
void delayms(unsigned char ms) // 延时子程序(晶振12M) {
unsigned char i;
while(ms--)
{
for(i = 0; i
}
}
★注释: 此句作用是8个数码管全部扫描完一遍之后,再回到第一个开始下一次扫描。写回一般形式:dis_index = dis_index & 0x07 。这种方法挺新,第一次见到,十六进制的07就是二进制的00000111,这样通过与操作可以控制循环了。比如dis_index 经第一次循环后值为00000001,和0x07与操作后值不变仍为0x01,第二次循环时,其值为0为0x02,与0x07后仍为0x02,一直到其值增为0x07时还是不变的,但再次循环后其值为0x80,再与0x07后就变成0x00了,这样又从初始循环了。此句可用 if (dis_index == 8) dis_index = 0 代替,效果一样。
★通过C51用上述方法实现时,其段码放在了数组dis_code[11]中,再通过缓冲区数组dis_buf[]将程序中要调用的值装入,这样就可以用下标(偏移量)访问了。这样看上去有些繁锁,但其思路比较清楚,结构上也很明了,具有通用性,
便于扩展。
★另外只要把程序中的延时加长,如delayms(250),下载到板上就可以看到实际上数码管是由低位到高位逐位显示的。
若单单就实现这个功能而言,可以直接调入段码数组dis_code[11]中下标从0到7的值,而不必再设置缓冲数组dis_buf[],实现如下:
#include
#include //_crol_()用
void delayms(unsigned char ms); //延时子程序
unsigned char data dis_digit; //位选通值, 传送到P2口用于选通当前数码管的数值,
//如等于0x01时, 选通P2.0口数码管 unsigned char code dis_code[11]={0x08,0xab,0x12,0x22,0xa1, // 0,1,2,3,4
0x24,0x04,0xaa,0x00,0x20, 0xff}; // 5,6,7,8,9,off
unsigned char data dis_index; //显示索引, 用于标识当前显示的数码管和缓冲区的偏移量
void main()
{
P0 = 0xff; // 关闭所有数码管
P2 = 0x00;
dis_index = 0; // 当前偏移量为0
dis_digit = 0x01; // 选通P2.0
while(1)
{
P0 = dis_code[dis_index]; // 段码送P0口
P2 = dis_digit; // 位码送P2口
delayms(1);
dis_digit = _crol_(dis_digit, 1); // 位选通左移, 下次选通下一位
dis_index++;
dis_index &= 0x07;
}
}
void delayms(unsigned char ms) // 延时子程序(晶振12M)
{
unsigned char i;
while(ms--)
{
for(i = 0; i
}
}
★本来是想通过以下方式实现一次循环的:
for (dis_index = 0; dis_index
{
P0 = dis_code[dis_index]; // 段码送P0口
P2 = dis_index+1; // 位码送P2口
delayms(1);
}
可得到的总是错误的结果:第0位到第2位这三位显示的是三个8,第3位显示的是7,高四位没有显示。加长延时逐位观察也没有发现错误的规律,对Keil 的调试也不熟悉,先把问题留到这,待找出原因后再补上。
[2006.5.2] 找出原因啦,补上:
今天又看了一下,找到上面的错误出在哪了。当时是想用dis_index的值做为位码的, 即第一位显示0时, 段码为dis_code[0], 即dis_index值为0, 此时位码值为1。第二位显示1时, 段码为dis_code[1],即dis_index值为1, 此时位码值为2。所以就简单用了个加1运算,将P0口的偏移值与P2口的位码联系起来。但仔细想一下位码的原理,上述方法显然是错的,只要再验证一步就明白了,即当第3位显示2时,段码为dis_code[2], dis_index值为2,加1后为3,按上述方法时就将这个3作为了位码,而正确的位码应该是4 (00000100B)。所以出错。实际上这个对应关系是有的,但不是简简单单的加1,位码应该是2的dis _index次幂。即:
0--1
1--2
2--4
3--8
4--16 „„
幂次运算函数flaot pow(float x, float y)包含在math.h 中, 返回值为x y (float 型):
for (dis_index = 0; dis_index
{
P0 = dis_code[dis_index]; // 段码送P0口
P2 = (char) pow(2, dis_index); // 位码送P2口 delayms(255);
}
再次下载到板上发现仍有问题, 即延时很小的时候显示混乱, 但加大延时时间(如程序中的值) 可以观查到数码管是按位正确显示的。另外用这种方法产生的代码量也很大(从写入速度看, 很明显) 。这里仅提出了一个思路,只在此实验中适用,意义不大,到此为止。
[补充结束]
AS 中绐出的例程是利用定时中断做的延时,参考修改到我的板上,程序如下:
#include
#include // 包含了左移函数_crol_()
unsigned char data dis_digit; // 位选通值, 传送到P2口用于选通当前数码管的数值,
// 如等于0x01时, 选通P2.0口数码管 unsigned char code dis_code[11]={0x08,0xab,0x12,0x22,0xa1, // 0,1,2,3,4
0x24,0x04,0xaa,0x00,0x20, 0xff}; // 5,6,7,8,9,off
unsigned char data dis_buf[8]; // dis_buf 显于缓冲区基地址
unsigned char data dis_index; // 显示索引, 用于标识当前显示的数码管和缓冲区的偏移量
void main()
{
P0 = 0xff; //关闭所有数码管
P2 = 0x00;
TMOD = 0x01; // 00000001B 定时计数器0工作在方式1,16位定时器/计数器
TH0 = 0xFC;
TL0 = 0x17; // 预置初值 FC17H=64535D, 216-64535=1001us=1ms IE = 0x82; // 10000010B T0溢出中断允许
dis_buf[0] = dis_code[0x0];
dis_buf[1] = dis_code[0x1];
dis_buf[2] = dis_code[0x2];
dis_buf[3] = dis_code[0x3];
dis_buf[4] = dis_code[0x4];
dis_buf[5] = dis_code[0x5];
dis_buf[6] = dis_code[0x6];
dis_buf[7] = dis_code[0x7];
dis_digit = 0x01; // 选通第0位数码管
dis_index = 0; // 偏移初值为0
TR0 = 1; // 启动T0
while(1); // 循环等待中断
}
void timer0() interrupt 1 // 定时器0中断服务程序, 用于数码管的动态扫描
{
TH0 = 0xFC; // 发生中断定时/计数器重装初值 TL0 = 0x17; // 感觉此处(及上) 应该是0x18, 而不是17,分析如下
P2 = 0x00; // 先关闭所有数码管
P0 = dis_buf[dis_index]; // 段码送P0口
P2 = dis_digit; // 位码送P2口
dis_digit = _crol_(dis_digit,1); // 位选通值左移, 下次中断时选通下一位数码管
dis_index++;
dis_index &= 0x07; // 8个数码管全部扫描完一遍之后,再回到第一个开始下一次扫描
}
★定时器/计数器的输入脉冲周期与机器周期一样, 为时钟振荡频率的1/12。晶振用12M 时,输入脉冲周期间隔为1us 。机器周期为 1us。设T0的初值为X ,计算初值的方法:本例中定时器用方式1,是16位的定时器,即最大值为216=65536,超过此值将发生溢出,引起中断,进入中断处理程序。这里要让其延时1m s ,即1000us, 则有式216-X =1000,可得X =64536,换算为16进制为FC18,即初值TH0=0xFC,TL0=0x18。即定时器由64536开始计数,经1000次计数后值为65536,将发生定时中断,再进入中断处理子程序后,重新装和初值,如此循环下去。
而在上例中其装入的初值并非FC18(64536),而是FC17(64535)。我想大概认为其计数范围在0~65565的原因吧,我也想过这个问题,是用216-计数初值=中断间隔 呢, 还是用(216-1)-计数初值=中断间隔呢? 随手查了几本书, 说法不一, 不过用前者的较多, 我自己也认为前者比较合理, 因为在计算机中16位的二进制不能表示65536, 在各位均为1时表示的值为65535, 即65535H=[**************]1B, 也可以说65536是溢出得到的。而何时响应中断就成了关键,拿上例来说,如设初值为64535(FC17),则计数到65535时,已经计数为1000个,即1ms ,但此时并未发生溢出,因此也没有触发中断。而是在下一个计数后才发生。确切值应为1001us 。若初值为64536(FC18),则恰好为所需值,所以上例中的初值应该用FC18而不是FC17。这仅仅是我自己的一点看法,至于是不是这样,还有待进一步考证。
最终下载到实验板上结果:
######################################补充########################################
用Proteus 仿真结果如下(某一状态的截图):
★该电路段码是按与板上接法对应的, 即按前面的段码表次序连接。另外这个八位的仿真数码管最左端是第一位,最右端是第八位,与板上的顺序相反,所以接为了统一,该图以板为准连接。上图不加上拉电阻也可仿真出结果,只是P0口高电平显示为灰,即高阻。
引用地址:http://blog.programfan.com/trackback.asp?id=12535
多功能数字钟的设计方案
1、蜂鸣器的设计
2、温度传感器(DB18B20)设计
3、液晶(1620)模块
4、正弦波转换为方波电路图
5、+12V 交流电压转换为+5V 直流电压
6、 红外发射模块
7、红外接收模块
8、综合电路图
方案报告:
校电子设计大赛设计与总结报告
(题目:多功能数字钟)
一、 任务与要求
任务:设计制作一个24小时制多功能数字钟。
要求: a.基本要求
1、具有时间设置(小时和分钟)、闹钟时间设置、闹钟开、闹钟关功
能。
2、数字显示小时、分钟,有AM 、PM 指示器,闹钟就绪灯,蜂鸣器。
3、220V 供电。
b .发挥部分
1、键盘切换现场环境温度显示。(0~60℃ 1℃)
2、键盘切换电网频率、电压显示。
3、电压欠压、过压报警(~220V +/—10%)功-能。
4、非接触止闹功能。
二、方案比较及作品模块介绍
多功能数字钟,具有时钟时间设置、闹钟时间设置、闹钟开、闹钟关等功能,数字显示日期、小时、分钟、秒,有AM 、PM 指示器,闹钟就绪灯(由指示灯指示是否设有闹钟)、蜂鸣器。可用键盘切换现场温度、电网频率、电压等,能动态刷新显示以上各测量参数。还可以进行电压欠压、过压报警(220V±10%)。当闹钟启动后,可以通过遥控器遥控止闹。
电子钟的设计包括以下几部分:时钟模块、键盘输入模块、电网频率测量模块、电网电压测量模块、电源模块、环境温度测量模块、红外遥控关闹钟模块、单片机控制模块、1602液晶显示模块等。
2.1 时钟模块
时钟模块主要由单片机AT89S52内部的定时器/计数器来实现,他的处理过程主要是先设定单片机内部的一个定时器/计数器工作于定时方式,对机器周期计数形成基准时间,然后用另外一个定时器软件计数的方法对基准时间形成秒,秒计60次形成分,分计60次形成小时。然后通过液晶把他们的内容显示在相应位置出来即可。在具体处理时,定时器/计数器采用中断方式工作,对时钟的形成在中断服务程序中实现。在主程序中只需要对定时器/计数器初始化、调用显示子程序和控制子程序。另外,为了使用方便,设计了简单的按键,可以通过按键实现时、分的调整,这样在主程序中就加入了键盘设置子程序。
在更新周期内,芯片内部时标寄存器数据处于更新阶段,故在该周期内,微处理器不能读芯片时标寄存器的内容,否则将得到不确定数据。更新周期的基本功能主要是刷新各个时标寄存器中的内容,同时秒时标寄存器内容加1,并检查其他时标寄存器内容是否有溢出。如果有溢出则相应进位日、月、年。另外一个功能是检查三个时、分、秒报警时标寄存器的内容是否与对应时标寄存器的内容相符,如果相符则寄存器C 中的AF 位置“1”。如果报警时标寄存器的内容为C0H 到FFH 之间的数据,则为不关心状态。
2.2 键盘输入模块
2.3 数据采集电路
数据采集测量电路包括温度、电压、电流、频率、功率和功率因数。
(1) 温度采集
DS18B20中的温度传感器可完成对温度的测量,测得的数据转换为数字值存放在内部存储器中,温度传感器的内部存储器包括一个高速暂存RAM 和一个非易失性的可电除的E2RAM, 后者存放高温度和低温度触发器TH 、TL 和结构寄存器。根据DS18B20的通信协议,主机控制DS18B20完成温度转换必须经过3个步骤:每一次读写之前都要对DS18B20进行复位,复位成功后发送一条ROM 指令,最后发送RAM 指令,这样才能对DS18B20进行预定的操作。复位要求主CPU 将数据线下拉500μs ,然后释放,DS18B20收到信号后等待16~60μs 左右,后发出60~240μs 的存在低脉冲,主 CPU收到此信号表示复位成功。在电路中数据输出端直接与P1.1相连即可。
(2) 电网频率测量
电网的正弦频率信号经以上电路处理后,形成方波信号,高电平为+5V,将方波信号接到89C52的P3.6(理论脚)脚上,通过单片机计一次高电平即半个周期的时间T 程序计算频率 F=1/2T
2.4 液晶显示模块
显示电路采用液晶1602实现。液晶显示器具有微功耗、体积小、显示内
容丰富、超薄轻巧的诸多优点,在本题的制作中,用液晶来实现数字信息的显示时比较合适的一种选择。
2.5 非接触止闹功能模块
本部分电路由红外发射模块和接受模块构成。
红外线的特点是不干扰其他电器设备工作,也不会影响周边环境。电路调试简单,若对发射信号进行编码,可实现多路红外遥控功能。
(1) 模块自身辐射极小,加上电路模块背面网状接地铜箔的屏蔽作用,可以减少自身振荡的泄漏和外界干扰信号的侵入。
(2) 接收电路的红外接收管是一种光敏二极管,使用时要给红外接收二极管加反向偏压,它才能正常工作而获得高的灵敏度。
(3)LED指示灯和蜂鸣器与单片机I/O口相接,这样只需在程序当中判断这些状态,然后改变I/O口高低电平,使其亮灭。
2.6 电源模块
电压模块由220V 交流供电,通过7805使电压将为+5V 直流电压
2.7软件设计
采用单片机AT89S52控制整个系统工作,实现各个功能。程序结构图如上图
三、调试结果
经过两天时间的制作,电子钟的制作已经完成,基本符合题目的要求,具有时间设置(小时和分钟)、闹钟时间设置、闹钟开、闹钟关功能。能够由液晶动态显示小时、分钟,设有闹钟就绪灯,蜂鸣器。可以由220V 供电。可以用键盘切换现场环境温度显示(0~60℃ 1℃)。自制的红外遥控可以实现非接触止闹功能。
本系统基本实现了题目的要求,但还有很多不足需要改进。如频率采集还有待改进。
四、经验及教训
通过此次多功能数字钟设计制作,我们将从书本上学到的知识应用于实践,学会了一些电子电路仿真设计能力, 虽然过程中遇到了一些困难,但是在解决这
些问题的过程无疑也是对自己自身专业素质的一种提高。当最终调试成功的时候也是对自己的一种肯定。此次的设计作业不仅增强了自己在专业设计方面的信心,鼓舞了自己,更是一次兴趣的培养,为自己以后的学习方向的明确了重点。 另外在这次实验中我们遇到了不少的问题针对不同的问题我们采取不同的解决方法,最终一一解决设计中遇到的问题。在我们遇到不懂的问题时,利用网上的资源,搜索查找得到需要的信息。和队友之间相互讨论,明白了团队合作的重要性。这次的制作也让我们感受到,我们在电子方面学到的只是很小的一部分知识,我们需要更多的时间来学习知识,学习技术。
【006】多位数码管动态显示 [51]
点击数:2102 发布日期:2006-4-16 16:04:00 【收藏】 【评论】 【打印】 【编程爱好者论坛】 【关闭】
实验目的:数码管动态显示多位数字。
实验参考:笨笨工作室 实验五、多位数码动态显示。(查看原文)
实验板: FB51A(查看)。
该实验用到实验板的资源电路图如下:
其中P0口是段码,低电平有效。P2口是位码,高电平有效。P2.0口控制第1个数码管,一直到P2.7口控制第8个。该板的段码表如下:
各个数码管的段码都是p0口的输出, 即各个数码管输入的段码都是一样的, 为了使其分别显示不同的数字, 可采用动态显示的方式, 即先只让最低位显示0(含点) ,经过一段延时,再只让次低位显示1,如此类推。由视觉暂留, 只要我们的延时时间足够短,就能够使得数码的显示看起来非常的稳定清楚。过程如下图。
采用上述方法思路编写如下:
org 0000h
start: mov a,#08h ;0 ;段码
mov p0,a
mov p2,#01h ;位码
lcall delay_1ms
mov a,#0abh ;1
mov p0,a
mov p2,#02h
lcall delay_1ms
mov a,#12h ;2
mov p0,a
mov p2,#04h
lcall delay_1ms
mov a,#22h ;3
mov p0,a
mov p2,#08h
lcall delay_1ms
mov a,#0a1h ;4
mov p0,a
mov p2,#10h
lcall delay_1ms
mov a,#24h ;5
mov p0,a
mov p2,#20h
lcall delay_1ms
mov a,#04h ;6
mov p0,a
mov p2,#40h
lcall delay_1ms
; mov a,#0aah ;7
; mov p0,a
mov p0,#0aah ;
以后会有用吧
mov p2,#80h
lcall delay_1ms
ljmp start 感觉用这句和上面两句实现一样,可能这种习惯
delay_1ms: mov r6,#2
temp: mov r5,#0ffh
djnz r5,$
djnz r6,temp
ret
end
下载到板上得到测结果为从低到高八位分别显示0到7(含点)。
★上述方法逐次给P0或者P2赋值,一方面程序的复杂程度增加,另外一方面会使得程序的灵活性降低。如果要改变显示的数字,程序改动起来很麻烦。 所以要用51单片机中常用的一种方法:查表法。例如P0口输出段码时,我们可以把要显示的段码放在一个表格中,然后每次从这个表格里面取数,送到P0口即可。P2口输出位码时,可以把要用的位码放在另一个表格里,每次从此表中取数,送入P2口。这样,如果要改变显示的数字,只需要改变表格里面的数。
org 0000h
start: mov r7,#0ffh ;r7,r6查表时送入变址寄存器a (因自加1后为0, 所以预置ffh)
mov r6,#0ffh
loop: lcall play1 ;调用显示段码子程序
lcall play2 ;调用显示位码子程序
lcall delay_1ms
cjne a,#80h,loop ;判断是否到了最左边的数, 即第8个位码 ajmp start
play1: ;查表求段码子程序
; mov a,r7
; inc a
; mov r7,a
inc r7 ;这2句和上面三条语句实现功能相同
mov a,r7 ;a在这里做变址寄存器
mov dptr,#table1 ;表首址送dptr ,dptr 做基址寄存器
movc a,@a+dptr ;基址寄存器加变址寄存器寻址
mov p0,a
ret
play2: ;查表求位码子程序(原理同play1)
mov a,r6
inc a
mov r6,a
mov dptr,#table2
movc a,@a+dptr
mov p2,a
ret
table1: db 08h,0abh,12h,22h,0a1h,24h,04h,0aah ;段码表
table2: db 01h,02h,04h,08h,10h,20h,40h,80h ;位码表
delay_1ms: mov r5,#02h ;延时1ms 子程序 temp: mov r4,#0ffh
djnz r4,$
djnz r5,temp
ret
end
下载到板上验证得到预想结果。
C51实现如下(参考了AS 的例程):
#include
#include // 包含了左移函数_crol_()
void delayms(unsigned char ms); // 延时子程序
unsigned char data dis_digit; // 位选通值, 传送到P2口用于选通当前数码管的数值,
// 如等于0x01时, 选通P2.0口数码管 unsigned char code dis_code[11]={0x08,0xab,0x12,0x22,0xa1, // 0,1,2,3, 4
0x24,0x04,0xaa,0x00,0x20, 0xff}; // 5,6,7,8,9, off
unsigned char data dis_buf[8]; // dis_buf 显于缓冲区基地址
unsigned char data dis_index; // 显示索引, 用于标识当前显示的数码管和缓冲区的偏移量
void main()
{
P0 = 0xff; // 关闭所有数码管
P2 = 0x00;
dis_buf[0] = dis_code[0];
dis_buf[1] = dis_code[1];
dis_buf[2] = dis_code[2];
dis_buf[3] = dis_code[3];
dis_buf[4] = dis_code[4];
dis_buf[5] = dis_code[5];
dis_buf[6] = dis_code[6];
dis_buf[7] = dis_code[7];
dis_digit = 0x01; // 首先选通P2.0
dis_index = 0; // 当前偏移量为0
while(1)
{
P0 = dis_buf[dis_index]; // 段码送P0口
P2 = dis_digit; // 选能位(即位码) delayms(1); // 延时
dis_digit = _crol_(dis_digit, 1); // 位选通左移, 下次选通下一位
dis_index++; // 下一个段码
dis_index &= 0x07; // 见注释
}
}
void delayms(unsigned char ms) // 延时子程序(晶振12M) {
unsigned char i;
while(ms--)
{
for(i = 0; i
}
}
★注释: 此句作用是8个数码管全部扫描完一遍之后,再回到第一个开始下一次扫描。写回一般形式:dis_index = dis_index & 0x07 。这种方法挺新,第一次见到,十六进制的07就是二进制的00000111,这样通过与操作可以控制循环了。比如dis_index 经第一次循环后值为00000001,和0x07与操作后值不变仍为0x01,第二次循环时,其值为0为0x02,与0x07后仍为0x02,一直到其值增为0x07时还是不变的,但再次循环后其值为0x80,再与0x07后就变成0x00了,这样又从初始循环了。此句可用 if (dis_index == 8) dis_index = 0 代替,效果一样。
★通过C51用上述方法实现时,其段码放在了数组dis_code[11]中,再通过缓冲区数组dis_buf[]将程序中要调用的值装入,这样就可以用下标(偏移量)访问了。这样看上去有些繁锁,但其思路比较清楚,结构上也很明了,具有通用性,
便于扩展。
★另外只要把程序中的延时加长,如delayms(250),下载到板上就可以看到实际上数码管是由低位到高位逐位显示的。
若单单就实现这个功能而言,可以直接调入段码数组dis_code[11]中下标从0到7的值,而不必再设置缓冲数组dis_buf[],实现如下:
#include
#include //_crol_()用
void delayms(unsigned char ms); //延时子程序
unsigned char data dis_digit; //位选通值, 传送到P2口用于选通当前数码管的数值,
//如等于0x01时, 选通P2.0口数码管 unsigned char code dis_code[11]={0x08,0xab,0x12,0x22,0xa1, // 0,1,2,3,4
0x24,0x04,0xaa,0x00,0x20, 0xff}; // 5,6,7,8,9,off
unsigned char data dis_index; //显示索引, 用于标识当前显示的数码管和缓冲区的偏移量
void main()
{
P0 = 0xff; // 关闭所有数码管
P2 = 0x00;
dis_index = 0; // 当前偏移量为0
dis_digit = 0x01; // 选通P2.0
while(1)
{
P0 = dis_code[dis_index]; // 段码送P0口
P2 = dis_digit; // 位码送P2口
delayms(1);
dis_digit = _crol_(dis_digit, 1); // 位选通左移, 下次选通下一位
dis_index++;
dis_index &= 0x07;
}
}
void delayms(unsigned char ms) // 延时子程序(晶振12M)
{
unsigned char i;
while(ms--)
{
for(i = 0; i
}
}
★本来是想通过以下方式实现一次循环的:
for (dis_index = 0; dis_index
{
P0 = dis_code[dis_index]; // 段码送P0口
P2 = dis_index+1; // 位码送P2口
delayms(1);
}
可得到的总是错误的结果:第0位到第2位这三位显示的是三个8,第3位显示的是7,高四位没有显示。加长延时逐位观察也没有发现错误的规律,对Keil 的调试也不熟悉,先把问题留到这,待找出原因后再补上。
[2006.5.2] 找出原因啦,补上:
今天又看了一下,找到上面的错误出在哪了。当时是想用dis_index的值做为位码的, 即第一位显示0时, 段码为dis_code[0], 即dis_index值为0, 此时位码值为1。第二位显示1时, 段码为dis_code[1],即dis_index值为1, 此时位码值为2。所以就简单用了个加1运算,将P0口的偏移值与P2口的位码联系起来。但仔细想一下位码的原理,上述方法显然是错的,只要再验证一步就明白了,即当第3位显示2时,段码为dis_code[2], dis_index值为2,加1后为3,按上述方法时就将这个3作为了位码,而正确的位码应该是4 (00000100B)。所以出错。实际上这个对应关系是有的,但不是简简单单的加1,位码应该是2的dis _index次幂。即:
0--1
1--2
2--4
3--8
4--16 „„
幂次运算函数flaot pow(float x, float y)包含在math.h 中, 返回值为x y (float 型):
for (dis_index = 0; dis_index
{
P0 = dis_code[dis_index]; // 段码送P0口
P2 = (char) pow(2, dis_index); // 位码送P2口 delayms(255);
}
再次下载到板上发现仍有问题, 即延时很小的时候显示混乱, 但加大延时时间(如程序中的值) 可以观查到数码管是按位正确显示的。另外用这种方法产生的代码量也很大(从写入速度看, 很明显) 。这里仅提出了一个思路,只在此实验中适用,意义不大,到此为止。
[补充结束]
AS 中绐出的例程是利用定时中断做的延时,参考修改到我的板上,程序如下:
#include
#include // 包含了左移函数_crol_()
unsigned char data dis_digit; // 位选通值, 传送到P2口用于选通当前数码管的数值,
// 如等于0x01时, 选通P2.0口数码管 unsigned char code dis_code[11]={0x08,0xab,0x12,0x22,0xa1, // 0,1,2,3,4
0x24,0x04,0xaa,0x00,0x20, 0xff}; // 5,6,7,8,9,off
unsigned char data dis_buf[8]; // dis_buf 显于缓冲区基地址
unsigned char data dis_index; // 显示索引, 用于标识当前显示的数码管和缓冲区的偏移量
void main()
{
P0 = 0xff; //关闭所有数码管
P2 = 0x00;
TMOD = 0x01; // 00000001B 定时计数器0工作在方式1,16位定时器/计数器
TH0 = 0xFC;
TL0 = 0x17; // 预置初值 FC17H=64535D, 216-64535=1001us=1ms IE = 0x82; // 10000010B T0溢出中断允许
dis_buf[0] = dis_code[0x0];
dis_buf[1] = dis_code[0x1];
dis_buf[2] = dis_code[0x2];
dis_buf[3] = dis_code[0x3];
dis_buf[4] = dis_code[0x4];
dis_buf[5] = dis_code[0x5];
dis_buf[6] = dis_code[0x6];
dis_buf[7] = dis_code[0x7];
dis_digit = 0x01; // 选通第0位数码管
dis_index = 0; // 偏移初值为0
TR0 = 1; // 启动T0
while(1); // 循环等待中断
}
void timer0() interrupt 1 // 定时器0中断服务程序, 用于数码管的动态扫描
{
TH0 = 0xFC; // 发生中断定时/计数器重装初值 TL0 = 0x17; // 感觉此处(及上) 应该是0x18, 而不是17,分析如下
P2 = 0x00; // 先关闭所有数码管
P0 = dis_buf[dis_index]; // 段码送P0口
P2 = dis_digit; // 位码送P2口
dis_digit = _crol_(dis_digit,1); // 位选通值左移, 下次中断时选通下一位数码管
dis_index++;
dis_index &= 0x07; // 8个数码管全部扫描完一遍之后,再回到第一个开始下一次扫描
}
★定时器/计数器的输入脉冲周期与机器周期一样, 为时钟振荡频率的1/12。晶振用12M 时,输入脉冲周期间隔为1us 。机器周期为 1us。设T0的初值为X ,计算初值的方法:本例中定时器用方式1,是16位的定时器,即最大值为216=65536,超过此值将发生溢出,引起中断,进入中断处理程序。这里要让其延时1m s ,即1000us, 则有式216-X =1000,可得X =64536,换算为16进制为FC18,即初值TH0=0xFC,TL0=0x18。即定时器由64536开始计数,经1000次计数后值为65536,将发生定时中断,再进入中断处理子程序后,重新装和初值,如此循环下去。
而在上例中其装入的初值并非FC18(64536),而是FC17(64535)。我想大概认为其计数范围在0~65565的原因吧,我也想过这个问题,是用216-计数初值=中断间隔 呢, 还是用(216-1)-计数初值=中断间隔呢? 随手查了几本书, 说法不一, 不过用前者的较多, 我自己也认为前者比较合理, 因为在计算机中16位的二进制不能表示65536, 在各位均为1时表示的值为65535, 即65535H=[**************]1B, 也可以说65536是溢出得到的。而何时响应中断就成了关键,拿上例来说,如设初值为64535(FC17),则计数到65535时,已经计数为1000个,即1ms ,但此时并未发生溢出,因此也没有触发中断。而是在下一个计数后才发生。确切值应为1001us 。若初值为64536(FC18),则恰好为所需值,所以上例中的初值应该用FC18而不是FC17。这仅仅是我自己的一点看法,至于是不是这样,还有待进一步考证。
最终下载到实验板上结果:
######################################补充########################################
用Proteus 仿真结果如下(某一状态的截图):
★该电路段码是按与板上接法对应的, 即按前面的段码表次序连接。另外这个八位的仿真数码管最左端是第一位,最右端是第八位,与板上的顺序相反,所以接为了统一,该图以板为准连接。上图不加上拉电阻也可仿真出结果,只是P0口高电平显示为灰,即高阻。
引用地址:http://blog.programfan.com/trackback.asp?id=12535
多功能数字钟的设计方案
1、蜂鸣器的设计
2、温度传感器(DB18B20)设计
3、液晶(1620)模块
4、正弦波转换为方波电路图
5、+12V 交流电压转换为+5V 直流电压
6、 红外发射模块
7、红外接收模块
8、综合电路图
方案报告:
校电子设计大赛设计与总结报告
(题目:多功能数字钟)
一、 任务与要求
任务:设计制作一个24小时制多功能数字钟。
要求: a.基本要求
1、具有时间设置(小时和分钟)、闹钟时间设置、闹钟开、闹钟关功
能。
2、数字显示小时、分钟,有AM 、PM 指示器,闹钟就绪灯,蜂鸣器。
3、220V 供电。
b .发挥部分
1、键盘切换现场环境温度显示。(0~60℃ 1℃)
2、键盘切换电网频率、电压显示。
3、电压欠压、过压报警(~220V +/—10%)功-能。
4、非接触止闹功能。
二、方案比较及作品模块介绍
多功能数字钟,具有时钟时间设置、闹钟时间设置、闹钟开、闹钟关等功能,数字显示日期、小时、分钟、秒,有AM 、PM 指示器,闹钟就绪灯(由指示灯指示是否设有闹钟)、蜂鸣器。可用键盘切换现场温度、电网频率、电压等,能动态刷新显示以上各测量参数。还可以进行电压欠压、过压报警(220V±10%)。当闹钟启动后,可以通过遥控器遥控止闹。
电子钟的设计包括以下几部分:时钟模块、键盘输入模块、电网频率测量模块、电网电压测量模块、电源模块、环境温度测量模块、红外遥控关闹钟模块、单片机控制模块、1602液晶显示模块等。
2.1 时钟模块
时钟模块主要由单片机AT89S52内部的定时器/计数器来实现,他的处理过程主要是先设定单片机内部的一个定时器/计数器工作于定时方式,对机器周期计数形成基准时间,然后用另外一个定时器软件计数的方法对基准时间形成秒,秒计60次形成分,分计60次形成小时。然后通过液晶把他们的内容显示在相应位置出来即可。在具体处理时,定时器/计数器采用中断方式工作,对时钟的形成在中断服务程序中实现。在主程序中只需要对定时器/计数器初始化、调用显示子程序和控制子程序。另外,为了使用方便,设计了简单的按键,可以通过按键实现时、分的调整,这样在主程序中就加入了键盘设置子程序。
在更新周期内,芯片内部时标寄存器数据处于更新阶段,故在该周期内,微处理器不能读芯片时标寄存器的内容,否则将得到不确定数据。更新周期的基本功能主要是刷新各个时标寄存器中的内容,同时秒时标寄存器内容加1,并检查其他时标寄存器内容是否有溢出。如果有溢出则相应进位日、月、年。另外一个功能是检查三个时、分、秒报警时标寄存器的内容是否与对应时标寄存器的内容相符,如果相符则寄存器C 中的AF 位置“1”。如果报警时标寄存器的内容为C0H 到FFH 之间的数据,则为不关心状态。
2.2 键盘输入模块
2.3 数据采集电路
数据采集测量电路包括温度、电压、电流、频率、功率和功率因数。
(1) 温度采集
DS18B20中的温度传感器可完成对温度的测量,测得的数据转换为数字值存放在内部存储器中,温度传感器的内部存储器包括一个高速暂存RAM 和一个非易失性的可电除的E2RAM, 后者存放高温度和低温度触发器TH 、TL 和结构寄存器。根据DS18B20的通信协议,主机控制DS18B20完成温度转换必须经过3个步骤:每一次读写之前都要对DS18B20进行复位,复位成功后发送一条ROM 指令,最后发送RAM 指令,这样才能对DS18B20进行预定的操作。复位要求主CPU 将数据线下拉500μs ,然后释放,DS18B20收到信号后等待16~60μs 左右,后发出60~240μs 的存在低脉冲,主 CPU收到此信号表示复位成功。在电路中数据输出端直接与P1.1相连即可。
(2) 电网频率测量
电网的正弦频率信号经以上电路处理后,形成方波信号,高电平为+5V,将方波信号接到89C52的P3.6(理论脚)脚上,通过单片机计一次高电平即半个周期的时间T 程序计算频率 F=1/2T
2.4 液晶显示模块
显示电路采用液晶1602实现。液晶显示器具有微功耗、体积小、显示内
容丰富、超薄轻巧的诸多优点,在本题的制作中,用液晶来实现数字信息的显示时比较合适的一种选择。
2.5 非接触止闹功能模块
本部分电路由红外发射模块和接受模块构成。
红外线的特点是不干扰其他电器设备工作,也不会影响周边环境。电路调试简单,若对发射信号进行编码,可实现多路红外遥控功能。
(1) 模块自身辐射极小,加上电路模块背面网状接地铜箔的屏蔽作用,可以减少自身振荡的泄漏和外界干扰信号的侵入。
(2) 接收电路的红外接收管是一种光敏二极管,使用时要给红外接收二极管加反向偏压,它才能正常工作而获得高的灵敏度。
(3)LED指示灯和蜂鸣器与单片机I/O口相接,这样只需在程序当中判断这些状态,然后改变I/O口高低电平,使其亮灭。
2.6 电源模块
电压模块由220V 交流供电,通过7805使电压将为+5V 直流电压
2.7软件设计
采用单片机AT89S52控制整个系统工作,实现各个功能。程序结构图如上图
三、调试结果
经过两天时间的制作,电子钟的制作已经完成,基本符合题目的要求,具有时间设置(小时和分钟)、闹钟时间设置、闹钟开、闹钟关功能。能够由液晶动态显示小时、分钟,设有闹钟就绪灯,蜂鸣器。可以由220V 供电。可以用键盘切换现场环境温度显示(0~60℃ 1℃)。自制的红外遥控可以实现非接触止闹功能。
本系统基本实现了题目的要求,但还有很多不足需要改进。如频率采集还有待改进。
四、经验及教训
通过此次多功能数字钟设计制作,我们将从书本上学到的知识应用于实践,学会了一些电子电路仿真设计能力, 虽然过程中遇到了一些困难,但是在解决这
些问题的过程无疑也是对自己自身专业素质的一种提高。当最终调试成功的时候也是对自己的一种肯定。此次的设计作业不仅增强了自己在专业设计方面的信心,鼓舞了自己,更是一次兴趣的培养,为自己以后的学习方向的明确了重点。 另外在这次实验中我们遇到了不少的问题针对不同的问题我们采取不同的解决方法,最终一一解决设计中遇到的问题。在我们遇到不懂的问题时,利用网上的资源,搜索查找得到需要的信息。和队友之间相互讨论,明白了团队合作的重要性。这次的制作也让我们感受到,我们在电子方面学到的只是很小的一部分知识,我们需要更多的时间来学习知识,学习技术。