8位8段数码管飞入显示时/分/秒(51单片机)
使用51单片机在8位数码管上以飞入效果显示动态的时/分/秒信息,每次飞入一位,不能覆盖已飞入数据,直到时/分/秒信息完整显示到8位数码管上,使编译产生的可执行文件尽量小。
效果入图所示:
问题分析
实现飞入效果并不难,难点在于如何使编译产生的可执行文件尽量小。
算法分析
动态时/分/秒信息就直接采用51单片机内部定时器实现。
typedef struct { // 时间
unsigned char hour; // 时
unsigned char miunte; // 分
unsigned char second; // 秒
unsigned char count; // 50ms
} time_t;
使用pos记录飞入数据的位置,len记录已飞入数据的数量。8位8段数码管从左至右编号7-0,使用pos记录当前正在飞入的字符位置,len的初始值为0,表示还未飞入数据,len的最大值为7,表示所有数据都已经飞入。代码篇幅比较短,就不多赘述细节了(代码脑内运行!!!),飞入效果的实现主要都在定时中断函数t0()内的LED8_FLY标签段,主函数主要负责将pos和len进行显示前的映射并显示。
本算法用C语言写编译产生的可执行文件是695字节,使用汇编能降到485字节。
欢迎感兴趣的伙伴一起思考更优秀的算法或实现方式。
typedef struct { // 控制飞入
unsigned char pos; // 飞入位置
unsigned char len; // 飞入字符数
} fly_t;
C代码
屏蔽掉的代码基本是初始化寄存器,不是废代码,只是在不影响飞入效果的情况下,使编译产生的可执行文件尽量小。
#include <reg51.h>
#define LED8_CTRL_PORT P2 // 位码端口
#define LED8_DATA_PORT P0 // 段码端口
#define CLEAR 11 // 熄灭数码管
#define CONNECT 10 // 连接符号'-'
typedef struct { // 控制飞入
unsigned char pos; // 飞入位置
unsigned char len; // 飞入字符数
} fly_t;
typedef struct { // 时间
unsigned char hour; // 时
unsigned char miunte; // 分
unsigned char second; // 秒
unsigned char count; // 50ms
} time_t;
fly_t data fly;
time_t data time;
/*******************************************************************************
* 函 数 名:main
* 函数功能:主函数
* 输 入:void
* 输 出:void
* 说 明:none
*******************************************************************************/
void main(void)
{
unsigned char data buf[8]; // 显示内容
unsigned char data i, j;
unsigned char data ctrl_code; // 位码
unsigned char code LED8_DATA_CODE[] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40, 0x00 }; // 共阴段码0~9,'-','NULL'
fly.pos = 7; // 初始化飞入相关信息
// fly.len = 0; // 屏蔽赋初值代码,默认初值为0
// time.hour = 13; // 初始化时间
// time.miunte = 14;
// time.second = 52;
// time.count = 0;
// TL0 = 0xb0; // 计时50ms
// TH0 = 0x3c;
TMOD = 0x01; // 定时器T0工作与方式1
IE = 0x82;
TR0 = 1; // 启动定时器
while (1) {
ACC = time.second; // 数据拆分
B = 10;
buf[1] = ACC / B;
buf[0] = B;
buf[2] = CONNECT;
ACC = time.miunte;
B = 10;
buf[4] = ACC / B;
buf[3] = B;
buf[5] = CONNECT;
ACC = time.hour;
B = 10;
buf[7] = ACC / B;
buf[6] = B;
buf[fly.pos] = buf[fly.len]; // 取飞入的字符
for (i = fly.len; i != 8; i++) { // 清除
if(i != fly.pos) {
buf[i] = CLEAR;
}
}
ctrl_code = 0x80; // 数码管显示
for (i = 0; i != 8; i++) {
LED8_CTRL_PORT = ~ctrl_code; // 给位码
LED8_DATA_PORT = LED8_DATA_CODE[buf[i]]; // 给段码
ctrl_code >>= 1;
while(j++);
// LED8_DATA_PORT = 0x00; // 消隐
}
}
}
/*******************************************************************************
* 函 数 名:t0
* 函数功能:50ms中断计时
* 输 入:void
* 输 出:void
* 说 明:none
*******************************************************************************/
void t0(void) interrupt 1
{
// TL0 = 0xb0;
// TH0 = 0x3c;
time.count++; // 计时
if (time.count != 15) { // 约1秒
return;
}
time.count = 0;
time.second++;
if (time.second != 60) { // 1分
goto LED8_FLY;
}
time.second = 0;
time.miunte++;
if (time.miunte != 60) { // 1小时
goto LED8_FLY;
}
time.miunte = 0;
time.hour++;
if(time.hour != 24) { // 1天
goto LED8_FLY;
}
time.hour = 0;
LED8_FLY:
fly.pos--; // 飞入字符移位
if(fly.pos != (fly.len - 1)) { // 飞入下一个字符
return;
}
fly.pos = 7;
fly.len++;
if(fly.len == 8) { // 所有字符飞入完成
fly.len = 0;
}
}
汇编代码
屏蔽掉的代码基本是初始化寄存器,不是废代码,只是在不影响飞入效果的情况下,使编译产生的可执行文件尽量小。
LED8_CTRL_PORT EQU P2 ; 位码端口
LED8_DATA_PORT EQU P0 ; 段码端口
CLEAR EQU 11 ; 熄灭数码管
CONNECT EQU 10 ; 连接符号'-'
pos EQU 41H ; 飞入位置
len EQU 40H ; 飞入字符数
hour EQU R7 ; 时
miunte EQU R6 ; 分
second EQU R5 ; 秒
count EQU R4 ; 50ms
; 50H 51H 52H 53H 54H 55H 56H 57H 58H(多余项)
; 秒个位 秒十位 连接符号 分个位 分十位 连接符号 时个位 时十位 连接符号
buf EQU 50H ; 显示内容
ORG 0000H
AJMP MAIN
ORG 000BH
AJMP T0_INTERRUPT
ORG 0030H
MAIN:
MOV pos, #7 ; 初始化
; MOV len, #00H ; 屏蔽赋初值代码,默认初值为0
; MOV hour, #13
; MOV miunte, #14
; MOV second, #52
; MOV count, #0
; MOV TL0, #0B0H
; MOV TH0, #3CH
; MOV SP, #30H
MOV TMOD, #01H
MOV IE, #82H
SETB TR0
LOOP: ; 循环
MOV R0, #buf ; 数据拆分
MOV R1, #05H ; second(R5)地址
DATA_SPLIT:
MOV A, @R1
MOV B, #10
DIV AB
MOV @R0, B
INC R0
MOV @R0, A
INC R0
MOV @R0, #CONNECT ; 最末的CONNECT字符不影响
INC R0
INC R1
CJNE R1, #08H, DATA_SPLIT
MOV A, pos ; 取飞入的字符
ADD A, #buf
MOV R0, A
MOV A, len
ADD A, #buf
MOV R1, A
MOV A, @R1
MOV @R0, A
MOV R0, len ; 清除
CLEAR1:
MOV A, R0
CJNE A, pos, CLEAR2
CLEAR3:
INC R0
CJNE R0, #8, CLEAR1
AJMP DISPLAY
CLEAR2:
ADD A, #buf
MOV R1, A
MOV @R1, #CLEAR
AJMP CLEAR3
DISPLAY: ; 数码管显示
MOV A, #7FH
MOV R0, #buf
OUTPUT:
MOV LED8_CTRL_PORT, A ; 给位码
MOV A, @R0
INC R0
MOV DPTR, #LED8_DATA_CODE
MOVC A, @A+DPTR
MOV LED8_DATA_PORT, A ; 给段码
DELAY:
DJNZ R2, DELAY
; MOV LED8_DATA_PORT, #00H ; 消隐
MOV A, LED8_CTRL_PORT
RR A
CJNE R0, #58H, OUTPUT
AJMP LOOP
T0_INTERRUPT: ; 50ms中断计时
PUSH ACC
PUSH B
; MOV TH0, #3CH
; MOV TL0, #0B0H
CLR A
INC count ; 计时
CJNE count, #15, RETURN
MOV count, A
INC second ; 约1秒
CJNE second, #60, LED8_FLY
MOV second, A
INC miunte ; 1分
CJNE miunte, #60, LED8_FLY
MOV miunte, A
INC hour ; 1小时
CJNE hour, #24, LED8_FLY
MOV hour, A
LED8_FLY:
DEC pos ; 飞入字符移位
MOV A, pos ; 飞入下一个字符
MOV B, len
DEC B
CJNE A, B, RETURN
MOV pos, #7
INC len
MOV A, len ; 所有字符飞入完成
CJNE A, #8, RETURN
MOV len, #0
RETURN:
POP B
POP ACC
RETI
LED8_DATA_CODE: ; 共阴段码0~9, '-', 'NULL'
DB 3FH, 06H, 5BH, 4FH, 66H, 6DH
DB 7DH, 07H, 7FH, 6FH, 40H, 00H
END