目录
五、动态数码管(中断显示):(建议学习过定时器后再来看以下代码)
一、硬件部分:
1、573锁存器:
如果还不懂可以参考之前的点亮LED灯的文章
2、共阳极数码管:
图1
(1)其实可以把数码管可以看作多个LED灯组成的电路,每个数字由8个LED组成
(2)什么叫共阳极?
可以理解为LED灯的一端接了VCC,只需要在另一端输入低电平就能将他点亮
(3)封装好的共阳极数码管1-F对应的十六进制数:
//定义一个数组存放从0-f对应的16进制数
//code 关键字的作用是我定义的数据要存放到ROM区,写入后不可以被改变
//输入16为分隔符号
u8 code transistor_positive[]=
{
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,
0xfd
};
//带小数点
u8 code transistor_positive_point[]=
{
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,
0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e
};
3、总体思路:
图2
(1)由图1可知,我们先通过右边com口(位选)选择需要亮的数码管,再通过左边八个口(段选)确定要选择的数字
(2)由图2可知,单片机通过两个573锁存器控制数码管的位选和段选,只需要按照之前的方法对锁存器进行选择再输入数据即可
二、静态数码管:
实现功能:8个数码管分别单独逐次显示1-8,然后所有数码管同时逐次显示0-F
1、代码思路:
由上述硬件电路可知只需要选择相应的锁存器和输入相应的数据就行了
2、参考代码:
// 使用程序前,将J13调整为IO模式(2-3脚短接)
#include "reg52.h"
#include "Public.h"
//定义一个数组存放从1-f对应的16进制数
//code 关键字的作用是我定义的数据要存放到ROM区,写入后不可以被改变
u8 code transistor_positive[]=
{
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e
};
//带小数点
u8 code transistor_positive_point[]=
{
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,
0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e
};
void HC138_Y(u8 num);
void Close_All(void);
void main(void)
// 主函数
{
u8 i;
while(1)
{
Close_All();
//8个数码管分别单独逐次显示1-8
for(i=1;i<=8;i++)
{
//改变数据
HC138_Y(7);
P0 = transistor_positive[i];
//改变显示位置
HC138_Y(6);
P0 = 0x01<<(i-1);
Delay_1ms(500);
}
//数码管同时逐次显示0-F
for(i=0;i<16;i++)
{
//改变数据
HC138_Y(7);
P0 = transistor_positive[i];
//改变显示位置
HC138_Y(6);
P0 = 0xff;
Delay_1ms(500);
}
}
}
/*
输入变量:4-7
输出变量:无
功能:操作138译码器,4-7分别对应Y4-Y7,其余都会使译码器不起作用
*/
void HC138_Y(u8 num)
{
switch(num)
{
case 4:P2 = (P2 & 0x1f) | 0x80;break;
case 5:P2 = (P2 & 0x1f) | 0xA0;break;
case 6:P2 = (P2 & 0x1f) | 0xC0;break;
case 7:P2 = (P2 & 0x1f) | 0xE0;break;
default:P2 = (P2 & 0x1f) | 0x00;
}
}
/*
输入变量:无
输出变量:无
功能:关闭蜂鸣器和继电器
*/
void Close_All(void)
{
P2 = (P2 & 0x1f) | 0xA0;
P0 = 0x00;
}
3、参考代码(模块化):
// 使用程序前,将J13调整为IO模式(2-3脚短接)
#include "reg52.h"
#include "Public.h"
//定义一个数组存放从1-f对应的16进制数
//code 关键字的作用是我定义的数据要存放到ROM区,写入后不可以被改变
u8 code transistor_positive[]=
{
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e
};
//带小数点
u8 code transistor_positive_point[]=
{
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,
0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e
};
void HC138_Y(u8 num);
void Close_All(void);
void Transistor_Show(u8 num,u16 PIS);
void main(void)
// 主函数
{
u8 i;
while(1)
{
Close_All();
//8个数码管分别单独逐次显示1-8
for(i=1;i<=8;i++)
{
Transistor_Show(i,0x01<<(i-1));
Delay_1ms(500);
}
//数码管同时逐次显示0-F
for(i=0;i<16;i++)
{
Transistor_Show(i,0xff);
Delay_1ms(500);
}
}
}
/*
输入变量:num,显示数字;PIS,显示位置
输出变量:无
功能:操作138译码器,4-7分别对应Y4-Y7,其余都会使译码器不起作用
注意:需要把存放从1-f对应的16进制数数组也移植
*/
void Transistor_Show(u8 num,u16 PIS)
{
//改变数据
P2 = (P2 & 0x1f) | 0xE0;
P0 = transistor_positive[num];//需要显示小数点修改这里
//改变显示位置
P2 = (P2 & 0x1f) | 0xC0;
P0 = PIS;
}
/*
输入变量:4-7
输出变量:无
功能:操作138译码器,4-7分别对应Y4-Y7,其余都会使译码器不起作用
*/
void HC138_Y(u8 num)
{
switch(num)
{
case 4:P2 = (P2 & 0x1f) | 0x80;break;
case 5:P2 = (P2 & 0x1f) | 0xA0;break;
case 6:P2 = (P2 & 0x1f) | 0xC0;break;
case 7:P2 = (P2 & 0x1f) | 0xE0;break;
default:P2 = (P2 & 0x1f) | 0x00;
}
}
/*
输入变量:无
输出变量:无
功能:关闭蜂鸣器和继电器
*/
void Close_All(void)
{
P2 = (P2 & 0x1f) | 0xA0;
P0 = 0x00;
}
三、动态数码管:
实现功能:前四位显示年份“2022”,接着两位是分隔符“--”,最后两位是月份从一月份开始增加到十二月份,最后循环往复。
1、代码思路:
(1)我们如果曾经用过手机照一些分辨率较低屏幕,应该会见到闪烁的屏幕,也就会理解我们最常用的还是是用动态显示思路
(2)为什么我们一定要用动态显示呢?
如果用静态显示思路,那么每个显示部分都有独立的段选端口和独立的位选端口,以我们八位数码管为例:每一位都要八位的段选和一位的位选,一共就需要七十二位,这样就太浪费资源了
(3)利用人的视觉残留和数码管的余辉效应(1-2ms),我们可以让每个显示部分公用一个段选端口,只需要在极短时间不断改变位选,在人的眼中就会出现八位同时显示的情况
2、参考代码:
(1)宏定义函数:
#ifndef _PUBLIC_H
#define _PUBLIC_H
#include <reg52.h>
#define u8 unsigned char
#define u16 unsigned int
void Delay_1ms(u16 num);
void Delay_10us(u16 num);
void Close_All(void);
#endif
#include "Public.h"
// 延时函数(最小约1ms@12MHz)
void Delay_1ms(u16 num)
{
u16 i;
while(num--)
for(i=0; i<628; i++);
}
void Delay_10us(u16 num)
{
u16 i;
while(num--)
for(i=0; i<3; i++);
}
/*
输入变量:无
输出变量:无
功能:关闭所有外设
*/
void Close_All(void)
{
//关闭蜂鸣器和继电器
P0 = 0x00;
P2 = (P2 & 0x1f) | 0xA0;
P2 &= 0x1f;
//关闭LED灯
P0 = 0xff;
P2 = (P2 & 0x1F) | 0x80;
P2 &= 0x1f;
}
(2)主函数:
// 使用程序前,将J13调整为IO模式(2-3脚短接)
#include "reg52.h"
#include "Public.h"
u8 show_content[8]={2,0,2,3,16,16,0,1};
#define divide_num 16
//定义一个数组存放从0-f对应的16进制数
//code 关键字的作用是我定义的数据要存放到ROM区,写入后不可以被改变
//输入16为分隔符号
u8 code transistor_positive[]=
{
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,
0xbf
};
//带小数点
u8 code transistor_positive_point[]=
{
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,
0x00,0x10,0x08,0x03,0x46,0x21,0x06,0x0e
};
void Divide_NUM_16(u16 num,u8 D_num[5]);
void Transistor_Show(u8 num,u16 PIS);
// 主函数
void main(void)
{
u8 i,temp[5],j,k;
u16 year = 2023;
u8 divide = 16;
u8 month;
Close_All();
while(1)
{
month=1;
//年份
Divide_NUM_16(year,temp);
for(i=0;i<4;i++)
show_content[i] = temp[3-i];
//分隔符
for(i=4;i<6;i++)
show_content[i] = divide_num;
for(i=0;i<12;i++)
{
//月份
Divide_NUM_16(month,temp);
for(j=6;j<8;j++)
show_content[j] = temp[1-(j-6)];
for(j=0;j<100;j++)
for(k=0;k<8;k++)
{
Transistor_Show(transistor_positive[show_content[k]],k);
Delay_1ms(2);
}
month++;
}
}
}
/*
输入变量:num,一个小于65535的数;D_num,存放分割好数据的数组
输出变量:
功能:将一个小于65535数的每一位分割出来
*/
void Divide_NUM_16(u16 num,u8 D_num[5])
{
u8 C_MTP = 10;
u16 D_MTP = 1;
u8 i;
for(i=0;i<5;i++)
{
D_num[i] = num/D_MTP%C_MTP;
D_MTP*=10;
}
}
/*
输入变量:num,要显示数据;PIS,显示位置,从左到右分别为0-7
输出变量:无
功能:操作138译码器,4-7分别对应Y4-Y7,其余都会使译码器不起作用
注意:需要把存放从1-f对应的16进制数数组也移植
*/
void Transistor_Show(u8 num,u16 PIS)
{
//改变数据
P0 = num;
P2 = (P2 & 0x1f) | 0xE0;
P2 &= 0x1f;
//改变显示位置
P0 = 0x01<<PIS;
P2 = (P2 & 0x1f) | 0xC0;
P2 &= 0x1f;
}
更新:
在学习了其他代码后,感觉到用spritf函数可以极大优化数据处理过程,如果对该函数不了解可以参考以下文章:
四、动态数码管(优化):
// 使用程序前,将J13调整为IO模式(2-3脚短接)
#include "reg52.h"
#include "Public.h"
#include "stdio.h"
u8 SEG_COT[9];
u8 SEG_Code[8];
void SEG_TSL(u8 *input,u8 *output);
void SEG_Show(u8 num,u16 PIS);
// 主函数
void main(void)
{
u8 k,i;
u16 year = 2023;
u8 divide = 16;
u8 month=1;
Close_All();
while(1)
{
sprintf(SEG_COT, "%4u--%02u", year,(u16)month);
SEG_TSL(SEG_COT,SEG_Code);
for(i=0;i<50;i++)
{
for(k=0;k<8;k++)
{
SEG_Show(SEG_Code[k],k);
Delay_1ms(2);
}
Delay_1ms(2);
}
if(month == 12)
month = 0;
month++;
}
}
/*
输入变量:num,一个小于65535的数;D_num,存放分割好数据的数组
输出变量:
功能:将一个小于65535数的每一位分割出来
*/
void SEG_TSL(u8 *input,u8 *output)
{
u8 i,temp,j;
for(i=0;i<8;i++,j++)
{
switch(input[j])
{
case '0': temp = 0xc0; break;
case '1': temp = 0xf9; break;
case '2': temp = 0xa4; break;
case '3': temp = 0xb0; break;
case '4': temp = 0x99; break;
case '5': temp = 0x92; break;
case '6': temp = 0x82; break;
case '7': temp = 0xf8; break;
case '8': temp = 0x80; break;
case '9': temp = 0x90; break;
case 'A': temp = 0x88; break;
case 'B': temp = 0x83; break;
case 'C': temp = 0xc6; break;
case 'D': temp = 0xA1; break;
case 'E': temp = 0x86; break;
case 'F': temp = 0x8E; break;
case 'H': temp = 0x89; break;
case 'L': temp = 0xC7; break;
case 'N': temp = 0xC8; break;
case 'P': temp = 0x8c; break;
case 'U': temp = 0xC1; break;
case '-': temp = 0xbf; break;
case ' ': temp = 0xff; break;
default: temp = 0xff;
}
if(input[j+1] == ".")
{
temp &= 0x7f;
j++;
}
output[i] = temp;
}
}
/*
输入变量:num,要显示数据;PIS,显示位置,从左到右分别为0-7
输出变量:无
功能:操作138译码器,4-7分别对应Y4-Y7,其余都会使译码器不起作用
注意:需要把存放从1-f对应的16进制数数组也移植
*/
void SEG_Show(u8 num,u16 PIS)
{
//消影
P0 = 0xff;
P2 = (P2 & 0x1f) | 0xE0;
P2 &= 0x1f;
//改变显示位置
P0 = 0x01<<PIS;
P2 = (P2 & 0x1f) | 0xC0;
P2 &= 0x1f;
//改变数据
P0 = num;
P2 = (P2 & 0x1f) | 0xE0;
P2 &= 0x1f;
}
更新:在使用多个外设的情况下,如果还是按照之前的代码,采用轮询的方法显示,会导致每次显示间隔过长,在学过定时器后,我们可以采用中断的方法显示
五、动态数码管(中断显示):(建议学习过定时器后再来看以下代码)
// 使用程序前,将J13调整为IO模式(2-3脚短接)
#include "Public.h"
#include "stdio.h"
u8 SEG_COT[9];
u8 SEG_Code[8];
u8 SEF_POS = 0;
u16 count;
void SEG_TSL(u8 *input,u8 *output);
void SEG_Show(u8 num,u16 PIS);
void Timer_0_Init(u16 time);
// 主函数
void main(void)
{
u16 year = 2023;
u8 month=1;
Close_All();
Timer_0_Init(1000);//1ms
sprintf(SEG_COT, "%4u--%02u", year,(u16)month);
SEG_TSL(SEG_COT,SEG_Code);
while(1)
{
if(count == 1000)//1s
{
sprintf(SEG_COT, "%4u--%02u", year,(u16)month);
SEG_TSL(SEG_COT,SEG_Code);
if(month == 12)
month = 0;
month++;
count = 0;
}
}
}
/***************************数码管*****************************/
/*
输入变量:input,输入字符数组;output:输出16进制数数组
输出变量:
功能:将字符串转化为对应数码管显示的16进制数
*/
void SEG_TSL(u8 *input,u8 *output)
{
u8 i,temp,j;
for(i=0;i<8;i++,j++)
{
switch(input[j])
{
case '0': temp = 0xc0; break;
case '1': temp = 0xf9; break;
case '2': temp = 0xa4; break;
case '3': temp = 0xb0; break;
case '4': temp = 0x99; break;
case '5': temp = 0x92; break;
case '6': temp = 0x82; break;
case '7': temp = 0xf8; break;
case '8': temp = 0x80; break;
case '9': temp = 0x90; break;
case 'A': temp = 0x88; break;
case 'B': temp = 0x83; break;
case 'C': temp = 0xc6; break;
case 'D': temp = 0xA1; break;
case 'E': temp = 0x86; break;
case 'F': temp = 0x8E; break;
case 'H': temp = 0x89; break;
case 'L': temp = 0xC7; break;
case 'N': temp = 0xC8; break;
case 'P': temp = 0x8c; break;
case 'U': temp = 0xC1; break;
case '-': temp = 0xbf; break;
case ' ': temp = 0xff; break;
default: temp = 0xff;
}
if(input[j+1] == ".")
{
temp &= 0x7f;
j++;
}
output[i] = temp;
}
}
/*
输入变量:num,要显示数据;PIS,显示位置,从左到右分别为0-7
输出变量:无
功能:操作138译码器,4-7分别对应Y4-Y7,其余都会使译码器不起作用
注意:需要把存放从1-f对应的16进制数数组也移植
*/
void SEG_Show(u8 num,u16 PIS)
{
//消影
P0 = 0xff;
P2 = (P2 & 0x1f) | 0xE0;
P2 &= 0x1f;
//改变显示位置
P0 = 0x01<<PIS;
P2 = (P2 & 0x1f) | 0xC0;
P2 &= 0x1f;
//改变数据
P0 = num;
P2 = (P2 & 0x1f) | 0xE0;
P2 &= 0x1f;
}
/***************************定时器*****************************/
/*
输入变量:定时时长___us
输出变量:无
功能:配置并开启定时器0
*/
void Timer_0_Init(u16 time)
{
//12T模式
AUXR &= 0x7f;
//定时器0 模式0
TMOD &= 0xf0;
//设置初值
TH0 = (65536-time)/256;
TL0 = (65536-time)%256;
//打开中断
ET0 = 1;
EA = 1;
//开始计数
TR0 = 1;
}
void Timer_0_IT(void) interrupt 1
{
count++;
SEG_Show(SEG_Code[SEF_POS],SEF_POS);
if(++SEF_POS == 8)SEF_POS = 0;
}
更新:进一步模块化
// 使用程序前,将J13调整为IO模式(2-3脚短接)
#include "Public.h"
#include "stdio.h"
u8 SEG_COT[9];
u8 SEG_Code[8];
u8 SEF_POS = 0;
u16 SEG_delay;
void SEG_TSL(u8 *input,u8 *output);
void SEG_Show(u8 num,u16 PIS);
void Timer_0_Init(u16 time);
void SEG_Proc();
// 主函数
void main(void)
{
Close_All();
Timer_0_Init(1000);//1ms
while(1)
{
SEG_Proc();
}
}
void SEG_Proc()
{
u16 year = 2023;
static u8 month=1;
if(SEG_delay)return;
SEG_delay = 1;
sprintf(SEG_COT, "%4u--%02u", year,(u16)month);
SEG_TSL(SEG_COT,SEG_Code);
if(month == 12)
month = 0;
month++;
}
/***************************数码管*****************************/
/*
输入变量:input,输入字符数组;output:输出16进制数数组
输出变量:无
功能:将字符串转化为对应数码管显示的16进制数
注意:记得定义数组——u8 SEG_COT[9];u8 SEG_Code[8];
*/
void SEG_TSL(u8 *input,u8 *output)
{
u8 i=0,temp=0,j=0;
for(i=0;i<8;i++,j++)
{
switch(input[j])
{
case '0': temp = 0xc0; break;
case '1': temp = 0xf9; break;
case '2': temp = 0xa4; break;
case '3': temp = 0xb0; break;
case '4': temp = 0x99; break;
case '5': temp = 0x92; break;
case '6': temp = 0x82; break;
case '7': temp = 0xf8; break;
case '8': temp = 0x80; break;
case '9': temp = 0x90; break;
case 'A': temp = 0x88; break;
case 'B': temp = 0x83; break;
case 'C': temp = 0xc6; break;
case 'D': temp = 0xA1; break;
case 'E': temp = 0x86; break;
case 'F': temp = 0x8E; break;
case 'H': temp = 0x89; break;
case 'L': temp = 0xC7; break;
case 'N': temp = 0xC8; break;
case 'P': temp = 0x8c; break;
case 'U': temp = 0xC1; break;
case '-': temp = 0xbf; break;
case ' ': temp = 0xff; break;
default: temp = 0xff;
}
if(input[j+1] == ".")
{
temp &= 0x7f;
j++;
}
output[i] = temp;
}
}
/*
输入变量:num,要显示数据;PIS,显示位置,从左到右分别为0-7
输出变量:无
功能:操作138译码器,4-7分别对应Y4-Y7,其余都会使译码器不起作用
注意:需要把存放从1-f对应的16进制数数组也移植
*/
void SEG_Show(u8 num,u16 PIS)
{
//消影
P0 = 0xff;
P2 = (P2 & 0x1f) | 0xE0;
P2 &= 0x1f;
//改变显示位置
P0 = 0x01<<PIS;
P2 = (P2 & 0x1f) | 0xC0;
P2 &= 0x1f;
//改变数据
P0 = num;
P2 = (P2 & 0x1f) | 0xE0;
P2 &= 0x1f;
}
/***************************定时器*****************************/
/*
输入变量:定时时长___us
输出变量:无
功能:配置并开启定时器0
*/
void Timer_0_Init(u16 time)
{
//12T模式
AUXR &= 0x7f;
//定时器0 模式0
TMOD &= 0xf0;
//设置初值
TH0 = (65536-time)/256;
TL0 = (65536-time)%256;
//打开中断
ET0 = 1;
EA = 1;
//开始计数
TR0 = 1;
}
void Timer_0_IT(void) interrupt 1
{
if(SEG_delay++ == 1000)SEG_delay = 0;//1s
SEG_Show(SEG_Code[SEF_POS],SEF_POS);
if(++SEF_POS == 8 )SEF_POS = 0;
}