[置顶] 数码管

 
用proteus模拟多位数码管动态显示

  作者:roland1314 提交日期:2007-9-20 17:07:00  
  数码管动态显示多位数字。
  
  
  该实验用到实验板的资源电路图如下:  
  
  
  
  
  
  其中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的例程):  
  
  #i nclude  
  #i nclude  // 包含了左移函数_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  < 120; 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(1000),下载到板上就可以看到实际上数码管是由低位到高位逐位显示的。
  
  
  
  --------------------------------------------------------------------------------
   若单单就实现这个功能而言,可以直接调入段码数组dis_code[11]中下标从0到7的值,而不必再设置缓冲数组dis_buf[],实现如下:
  
  #i nclude  
  #i nclude  //_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  < 120; i++);
   }
  }
  
  ★ 通本来是想通过以下方式实现一次循环的:
  
   for (dis_index = 0; dis_index  < 8; 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中, 返回值为xy (float型):  
  
   for (dis_index = 0; dis_index  < 8; dis_index++)
   {
   P0 = dis_code[dis_index]; // 段码送P0口
   P2 = (char) pow(2, dis_index); // 位码送P2口
   delayms(255);
   }
  
  再次下载到板上发现仍有问题, 即延时很小的时候显示混乱,但加大延时时间(如程序中的值)可以观查到数码管是按位正确显示的。另外用这种方法产生的代码量也很大(从写入速度看,很明显)。这里仅提出了一个思路,只在此实验中适用,意义不大,到此为止。
  
  [补充结束]
  
  
  
  --------------------------------------------------------------------------------
  
  
  AS中绐出的例程是利用定时中断做的延时,参考修改到我的板上,程序如下:
  
  #i nclude  
  #i nclude  // 包含了左移函数_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,超过此值将发生溢出,引起中断,进入中断处理程序。这里要让其延时1ms,即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=1111111111111111B, 也可以说65536是溢出得到的。而何时响应中断就成了关键,拿上例来说,如设初值为64535(FC17),则计数到65535时,已经计数为1000个,即1ms,但此时并未发生溢出,因此也没有触发中断。而是在下一个计数后才发生。确切值应为1001us。若初值为64536(FC18),则恰好为所需值,所以上例中的初值应该用FC18而不是FC17。这仅仅是我自己的一点看法,至于是不是这样,还有待进一步考证。
  
  
  
  --------------------------------------------------------------------------------
  最终下载到实验板上结果:
  
    
  
  
  ######################################补充########################################
  
  用Proteus仿真结果如下(某一状态的截图):
  
  
  
  
  ★ 该电路段码是按与板上接法对应的,即按前面的段码表次序连接。另外这个八位的仿真数码管最左端是第一位,最右端是第八位,与板上的顺序相反,所以接为了统一,该图以板为准连接。上图不加上拉电阻也可仿真出结果,只是P0口高电平显示为灰,即高阻。
  
 
 
 
 
 
 
 
 
 
 
 
 


4位数码管动态扫描显示程序,当延时程序用,怎么后两位有重影,扫描时间间隔大小都调过,都不能解决。

    2011-10-4 19:44
提问者:819210047  |   浏览次数:290次
请大侠们指点,程序如下。看怎么能解决。
  void xs(qian,bai,shi,ge,sj)//5ms显示延时
 {	

	 while(sj--)
 
   {   P2=0xef;   //P2.4引脚输出低电平,DS4点亮
	 P0=wei[qian];  
	 delay1ms(1);
	   P2=0xdf;   //P2.3引脚输出低电平,DS3点亮
	 P0=wei[bai];  
	 delay1ms(1); 
	 	   	  P2=0xbf;   //P2.2引脚输出低电平,DS2点亮
	 P0=wei[shi]; 
	 delay1ms(1);
     P2=0x7f;   //P2.1引脚输出低电平,DS1点亮
	 P0=wei[ge];  
	 delay1ms(1);  
	 P0=0XFF;
     P2=0xff;
	  delay1ms(1); 
	 }
	   
	}
检举   |   2011-10-4 20:56
最佳答案
void xs(qian,bai,shi,ge,sj)//5ms显示延时
{	
    while(sj--)   {
      P0=wei[qian];  P2=0xef;   delay1ms(1);  P2=0xff;
      P0=wei[bai];   P2=0xdf;   delay1ms(1);  P2=0xff;
      P0=wei[shi];   P2=0xbf;   delay1ms(1);  P2=0xff;
      P0=wei[ge];    P2=0xff;   delay1ms(1);  P2=0xff;
    }
}
3
|   评论
向TA求助

回答者: 做而论道 | 十五级采纳率:43% 名人

擅长领域: 汇编语言 其他编程语言 程序设计 工程技术科学

参加的活动: 暂时没有参加的活动

提问者对于答案的评价:

懂了,就是每开段位就关闭全部显示。得到消影的作用。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

请教数码管扫描时间问题

回复数:11,点击数:1486

【楼主位】 anson
积分:616
派别:
等级:------
来自:

我需要用数码管显示数字,但单片机工作期间有25ms是连续工作的(对信号连续AD取样),也就是25ms内不能送信号给数码管,这样数码管会闪得很厉害吗?有什么其他的方法?谢谢·

 

2008-01-25,15:31:04

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【1楼】 yangsen
积分:1501
派别:
等级:------
来自:

会闪的很厉害,用专用的LED扫描芯片吧

 

2008-01-25,16:29:45

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【2楼】 vivalite

积分:1585
派别:
等级:------
来自:彼岸

硬件解决办法:在数码管上并接电容,其实就是组成积分电路
电容值可以这么算C=电流*秒/( 起始电压-终止电压)
软件解决办法:
AD取样时不要循环等待,用中断或者查询AD标志位法等待AD值
另一种方法是把数码管脚状态储存在变量里,循环等待AD值时把延时程序换成循环刷新数码管脚的程序

本贴被 vivalite 编辑过,最后修改时间:2008-01-25,16:47:07.

__________________________
千万别按Ctrl+W!!!!

2008-01-25,16:44:53

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【3楼】 ssyniuej

积分:1851
派别:
等级:------
来自:山东-》湖北武汉

连续采样是没有问题的,采样还是有时间的,在采样的时候不要等待,用中断来工作。并且可以提前计算出数码管要显示的数字,到时候几条代码就可以刷新了。

__________________________
QQ:3061876(有事您说话,请留言!)共同进步啊,感谢大家!

2008-01-25,18:33:35

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【4楼】 feiyue
积分:492
派别:
等级:------
来自:

也可以用164扩展,采用静态驱动数码管!

 

2008-01-25,19:07:09

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【5楼】 ThinkCell 思想的单元
积分:395
派别:
等级:------
来自:

我用HC595级联做显示,从来没有闪烁过。

 

2008-01-26,09:37:41

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【6楼】 skyrocket 白云
积分:27
派别:
等级:------
来自:sh

可以把扫描数码管的程序放在中断里,最多驱动8个,否则会出现闪烁等现象
多于8个的要分别驱动和扫描

 

2008-01-27,12:38:17

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【7楼】 machao
积分:6068
派别:
等级:------
来自:

不提倡把LED扫描放在中断里.LED的扫描早或晚几百个us是没有问题的.要掌握利用中断和状态机的方法,把任务分解开来,提高CPU的效率,增强系统的实时性.

假如一个LED要点亮2ms,那么8个LED需要16ms,AD转换25ms,如果使用软件延时或死等,那就成问题了.

 

2008-01-27,18:21:40

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【8楼】 vivalite

积分:1585
派别:
等级:------
来自:彼岸

优秀的程序是应该尽量避免死等的情况的

__________________________
千万别按Ctrl+W!!!!

2008-01-27,18:44:42

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【9楼】 widesoft 通关
积分:168
派别:
等级:------
来自:江苏无锡

不提倡把LED扫描放在中断里.LED的扫描早或晚几百个us是没有问题的.要掌握利用中断和状态机的方法,把任务分解开来,提高CPU的效率,增强系统的实时性. 

假如一个LED要点亮2ms,那么8个LED需要16ms,AD转换25ms,如果使用软件延时或死等,那就成问题了. 

这个问题,愿意与马老师探讨下:
如果动态扫描的数码管较多,达到16个,扫描早或晚几百个us是要出现抖动的!
通过试验觉得,整体扫描频率要达到64HZ比较平稳!
还是中断均匀!

 

2008-01-27,19:05:40

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【10楼】 zxd0225
积分:99
派别:
等级:------
来自:江苏无锡

    我觉得用定时中断的方法点亮数码管应该很常用。比如设置1到2毫秒中断一次,每次中断时候点亮一位数码管,
才用几条指令,根本不会对别的程序影响过大,显示还均匀。在8个数码管以内进行循环位扫描,在8个数码管
以上时候有两种方法:
1 循环段扫描,这时候可以扩充N个数码管,只扫描8次即可,但是每次需要耗费比较多的指令时间。
2 将多个数码管分组,同时进行位扫描。
    如果一段程序执行时候不能被中断打断,只好用静态显示或专用芯片了。
    

 

2008-01-27,19:39:13

资料邮件回复引用回复   ↑↑   ↓↓

编辑删除

【11楼】 zhonghua_li 蓝色天空

积分:1156
派别:
等级:------
来自:四川 成都

闪,应该不至于,顶多有点暗。
只要保证每个LED的 亮-暗 频率大于 50HZ,就看不出来
如果led个数太多,每个LED分配的时间就相对减少,这样平均亮时间 就低了。

 

 

 

http://wenku.baidu.com/view/7a072abcf121dd36a32d8222.html?from=related&hasrec=1

你可能感兴趣的:(工作,table,float,任务,delay,math.h)