第6章 中断与数码管动态显示
数码管的动态显示
多个数码管快速(10ms 以内)依次轮流点亮,利用人类视觉暂留现象,可以做到看起来是所有数码管都同时亮,也叫做动态扫描。
六个数码管全部点亮,从0开始增加显示,间隔时间为1秒钟。代码如下:(存在数码管鬼影、抖动问题)
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //数码管显示字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = { //数码管显示缓冲区,初值0xFF确保启动时都不亮
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
void main()
{
unsigned char i = 0; //动态扫描的索引
unsigned int cnt = 0; //记录T0中断次数
unsigned long sec = 0; //记录经过的秒数
ENLED = 0; //使能U3,选择控制数码管
ADDR3 = 1; //因为需要动态改变ADDR0-2的值,所以不需要再初始化了
TMOD = 0x01; //设置T0为模式1
TH0 = 0xFC; //为T0赋初值0xFC67,定时1ms
TL0 = 0x67;
TR0 = 1; //启动T0
while (1)
{
if (TF0 == 1) //判断T0是否溢出
{
TF0 = 0; //T0溢出后,清零中断标志
TH0 = 0xFC; //并重新赋初值
TL0 = 0x67;
cnt++; //计数值自加1
if (cnt >= 1000) //判断T0溢出是否达到1000次
{
cnt = 0; //达到1000次后计数值清零
sec++; //秒计数自加1
//以下代码将sec按十进制位从低到高依次提取并转为数码管显示字符
LedBuff[0] = LedChar[sec%10];
LedBuff[1] = LedChar[sec/10%10];
LedBuff[2] = LedChar[sec/100%10];
LedBuff[3] = LedChar[sec/1000%10];
LedBuff[4] = LedChar[sec/10000%10];
LedBuff[5] = LedChar[sec/100000%10];
}
//以下代码完成数码管动态扫描刷新
switch (i)
{
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
default: break;
}
}
}
}
鬼影是不该亮的数码管位有微微发亮。鬼影的出现,主要是在数码管位选和段选产生的瞬态造成的。比如进行数码管切换时候,由于代码是一句一句执行的,会造成中间存在其他瞬时状态,导致某一瞬间其他不想要的数码管被点亮。解决办法是关闭段,执行switch语句之前执行一次P0=0xFF,这样切换数码管过程中哪怕有其他瞬时态产生,也不会点亮数码管;另一个办法是关闭位,在执行switch语句之前,加一句 ENLED=1,关闭三八译码器,等数码管切换完成再执行 ENLED=0,启动三八译码器即可。
每一秒钟过后,不发生变化的数码管段可能会产生抖动。产生抖动的原因是每一秒钟更新缓存区数字内容所花费时间较多,导致每个数码管(总共六个)依次点亮时间变成5ms+缓冲区更新时间。因为没有在很短时间内完成数码管点亮刷新,导致数码管会变暗,然后点亮时候突然变量,人类视觉能够察觉这一亮度变化。
上述问题中第一个可以使用关闭段或者关闭位方法解决,第二个问题需要使用新知识中断方法进行解决。
IE——中断使能寄存器的位分配(地址 0xA8、可位寻址)
IE——中断使能寄存器的位描述
使用中断处理上述问题之后得到完整代码如下:
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //数码管显示字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = { //数码管显示缓冲区,初值0xFF确保启动时都不亮
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char i = 0; //动态扫描的索引
unsigned int cnt = 0; //记录T0中断次数
void main()
{
unsigned long sec = 0; //记录经过的秒数
EA = 1; //使能总中断
ENLED = 0; //使能U3,选择控制数码管
ADDR3 = 1; //因为需要动态改变ADDR0-2的值,所以不需要再初始化了
TMOD = 0x01; //设置T0为模式1
TH0 = 0xFC; //为T0赋初值0xFC67,定时1ms
TL0 = 0x67;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
while (1)
{
if (cnt >= 1000) //判断T0溢出是否达到1000次
{
cnt = 0; //达到1000次后计数值清零
sec++; //秒计数自加1
//以下代码将sec按十进制位从低到高依次提取并转为数码管显示字符
LedBuff[0] = LedChar[sec%10];
LedBuff[1] = LedChar[sec/10%10];
LedBuff[2] = LedChar[sec/100%10];
LedBuff[3] = LedChar[sec/1000%10];
LedBuff[4] = LedChar[sec/10000%10];
LedBuff[5] = LedChar[sec/100000%10];
}
}
}
/* 定时器0中断服务函数 */
void InterruptTimer0() interrupt 1
{
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
cnt++; //中断次数计数值加1
//以下代码完成数码管动态扫描刷新
P0 = 0xFF; //显示消隐
switch (i)
{
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
default: break;
}
}
中断优先级:
中断优先级有两种,一种是抢占优先级,一种是固有优先级。
IP——中断优先级寄存器的位分配(地址 0xB8、可位寻址)
IP——中断优先级寄存器的位描述
抢占优先级中只有对IP寄存器中优先级置1,才能达到高优先级抢占低优先级的情况。在固定优先级中(表6-3),哪怕优先级更高的事件也不能抢占执行中的低优先级事件;固定优先级应用场景是当打开总中断时,同时有多个中断发生,在没有设置优先级的情况下,就按照固定优先级顺序依次执行。
使用六个数码管,实现从 999999 开始倒计时的程序,使用定时器 T1 中断完成。代码如下:
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[] = { //数码管显示字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = { //数码管显示缓冲区,初值0xFF确保启动时都不亮
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char i = 0; //动态扫描的索引
unsigned int cnt = 0; //记录T1中断次数
void main()
{
char j; //循环变量
unsigned long sec = 1000000; //记录经过的秒数,倒计时初值为999999+1
unsigned char buf[6]; //中间转换缓冲区
EA = 1; //使能总中断
ENLED = 0; //使能U3
ADDR3 = 1; //因为需要动态改变ADDR0-2的值,所以不需要再初始化了
TMOD = 0x10; //设置T1为模式1
TH1 = 0xFC; //为T1赋初值0xFC67,定时1ms
TL1 = 0x67;
ET1 = 1; //使能T1中断
TR1 = 1; //启动T1
while (1)
{
if (cnt >= 1000) //判断T1溢出是否达到1000次
{
cnt = 0; //达到1000次后计数值清零
sec--; //秒计数自减1
//将sec按十进制位从低到高依次提取到buf数组中
buf[0] = sec%10;
buf[1] = sec/10%10;
buf[2] = sec/100%10;
buf[3] = sec/1000%10;
buf[4] = sec/10000%10;
buf[5] = sec/100000%10;
//从最高为开始,遇到0不显示,遇到非0退出循环
for (j=5; j>=1; j--)
{
if (buf[j] == 0)
LedBuff[j] = 0xFF;
else
break;
}
//将剩余的有效数字位如实转换
for ( ; j>=0; j--) //for()起始未对j操作,j即保持上个循环结束时的值
{
LedBuff[j] = LedChar[buf[j]];
}
}
}
}
/* 定时器1中断服务函数 */
void InterruptTimer1() interrupt 3
{
TH1 = 0xFC; //重新加载初值
TL1 = 0x67;
cnt++; //中断次数计数值加1
//以下代码完成数码管动态扫描刷新
P0 = 0xFF; //显示消隐
switch (i)
{
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
default: break;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET10 - 预览版1新功能体验(一)