飞道的博客

8051单片机实战分析(以STC89C52RC为例) | 06 - 动态数码管驱动

302人阅读  评论(0)

从上一博文《8051单片机实战分析(以STC89C52RC为例) | 05 - 静态数码管驱动》,我们可以了解到数码管一些驱动原理,实际上静态扫描在项目中基本不会用到,在项目中我们经常用动态扫描,所以便有了本文。如果不懂数码管的段选与位选请先看这篇:《3分钟带你彻底弄懂数码管的段选与位选

1 动态扫描

那什么是动态扫描呢?

举个例子:有 2 个数码管,我们要显示“12”这个数字,可以先让高位的位选三极管导通,然后控制段选让其显示“1”,延时一定时间后再让低位的位选三极管导通,然后控制段选让其显示“2”。把这个流程以一定的速度循环运行就可以让数码管显示出“12”,由于交替速度非常快,人眼识别到的就是“12”这两位数字同时亮了。

在多个数码管显示数字的时候,我们可以轮流点亮数码管(一个时刻内只有一个数码管是亮的),利用人眼的视觉暂留现象(也叫余辉效应),就可以做到看起来是所有数码管都同时亮了,这就是动态显示,也叫做动态扫描

那么一个数码管需要点亮多长时间呢?也就是说要多长时间完成一次全部数码管的扫描呢(很明显:整体扫描时间=单个数码管点亮时间*数码管个数)?答案是:10ms 以内

当电视机和显示器还处在 CRT(电子显像管)时代的时候,有一句很流行的广告语——“100Hz无闪烁”,没错,只要刷新率大于 100Hz,即刷新时间小于 10ms,就可以做到无闪烁,这也就是我们的动态扫描的硬性指标。那么你也许会问,有最小值的限制吗?理论上没有,但实际上做到更快的刷新却没有任何进步的意义了,因为已经无闪烁了,再快也还是无闪烁,只是徒然增加 CPU 的负荷而已(因为 1 秒内要执行更多次的扫描程序)。

所以,通常我们设计程序的时候,都是取一个接近 10ms,又比较规整的值就行了。

2 原理图

① 数码管原理图:

② 数码管的位选使用138译码器进行解析,关于这块我们可以参考这篇文章:《数字器件认识 | 74HC138三八译码器的应用》。

③ MCU原理图:

3 代码

了解原理之后,我们就可以写个Demo让数码管从右至左显示0-7:

#include "reg52.h"			 //此文件中定义了单片机的一些特殊功能寄存器

typedef unsigned int u16;	  //对数据类型进行声明定义
typedef unsigned char u8;

sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;

u8 code LedChar[]={
   
	0x3F,  //"0"
    0x06,  //"1"
    0x5B,  //"2"
    0x4F,  //"3"
    0x66,  //"4"
    0x6D,  //"5"
    0x7D,  //"6"
    0x07,  //"7"
    0x7F,  //"8"
    0x6F,  //"9"
    0x77,  //"A"
    0x7C,  //"B"
    0x39,  //"C"
    0x5E,  //"D"
    0x79,  //"E"
    0x71,  //"F"
    0xff, //全亮
    0x00  //熄灭
};


/*******************************************************************************
* 函 数 名         : delay
* 函数功能		   : 延时函数,i=1时,大约延时10us
*******************************************************************************/
void delay(u16 i)
{
   
	while(i--);	
}

/*******************************************************************************
* 函 数 名         : DigDisplay
* 函数功能		   : 数码管动态扫描函数,循环扫描8个数码管显示
*******************************************************************************/
void DigitalDisplay()
{
   
	u8 i;
	for(i=0;i<8;i++)
	{
   
		switch(i)	 //位选,选择点亮的数码管,
		{
   
			case(0):
				LSA=0;LSB=0;LSC=0; break;//显示第0位
			case(1):
				LSA=1;LSB=0;LSC=0; break;//显示第1位
			case(2):
				LSA=0;LSB=1;LSC=0; break;//显示第2位
			case(3):
				LSA=1;LSB=1;LSC=0; break;//显示第3位
			case(4):
				LSA=0;LSB=0;LSC=1; break;//显示第4位
			case(5):
				LSA=1;LSB=0;LSC=1; break;//显示第5位
			case(6):
				LSA=0;LSB=1;LSC=1; break;//显示第6位
			case(7):
				LSA=1;LSB=1;LSC=1; break;//显示第7位	
		}
		P0=LedChar[i];//发送段码
		delay(100); //间隔一段时间扫描:大约1ms
		P0=0x00;//消隐
	}
}

/*******************************************************************************
* 函 数 名       : main
* 函数功能		 : 主函数
* 输    入       : 无
* 输    出    	 : 无
*******************************************************************************/
void main()
{
   	
	while(1)
	{
   	
		DigitalDisplay();  //数码管显示函数	
	}		
}

简要分析:

main函数单纯给DigitalDisplay子函数进行无限循环。

DigitalDisplay子函数通过for循环语句在每一次循环指定一个位选与段选。

例如i=0;时,通过LSA=0;LSB=0;LSC=0;语句控制138译码器选择Y0端口输出0,从而选定LED1网络的位选。

然后通过P0=LedChar[i];给P0端口发送段选值,i=0的时候发送的是'0'字符的段选值,每次for循环中间只延时大约1ms。

③ 注意这句话:P0=0x00;//消隐。如果没有这句话就会产生“鬼影”,这个动态扫描的一个bug

鬼影”的出现,主要是在数码管位选和段选产生的瞬态造成的。

举个简单例子,我们在数码管动态显示的那部分程序中,实际上每一个数码管点亮的持续时间是1ms的时间,1ms后进行下个数码管的切换。

在进行数码管切换的时候,比如我们从 case 7 要切换到 case 0 的时候,case 7的位选用的是LSA=1;LSB=1;LSC=1;

==》假如此刻case 7也就是最高位数码管对应的值是0,我们要切换成的 case 0 的数码管位选是LSA=0;LSB=0;LSC=0;,而对应的数码管的值假如是1

==》又因为C语言程序是一句一句顺序往下执行的,每一条语句的执行都会占用一定的时间,即使这个时间非常非常短暂。

==》但是当我们把“LSA=1”改变成“LSA=0”的时候,这个瞬间存在了一个中间状态 LSA=0; LSB=1; LSC=1;在这个瞬间上,我们就给 case 6 对应的数码管LED7网络位选瞬间赋值了0

==》当我们全部写完了LSA=0;LSB=1; LSC=0;后,这个时候,我们的 P0 还没有正式赋值,而 P0此刻却保持了前一次的值,也就是在这个瞬间,我们又给case 4对应的数码管 LED5 网络位选赋值了一个 0。。。

==》直到我们把 case 0后边的语句全部完成后,我们的刷新才正式完成。而在这个刷新过程中,有 3个瞬间我们给错误的数码管赋了值,虽然很弱(因为亮的时间很短),但是我们还是能够发现。

搞明白了原理后,我们只要避开这个瞬间错误就可以了。不产生瞬间错误的方法是,在进行位选切换期间,避免一切数码管的赋值即可。方法有两个,一个方法是刷新之前关闭所有的段选,改变好了位选后,再打开段选即可;第二个方法是关闭数码管的位选,赋值过程都做好后,再重新打开即可。这里用到了第一种方法!!


转载:https://blog.csdn.net/Neutionwei/article/details/117405211
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场