参考链接:
51单片机七段数码管显示时钟加按键控制—③—74HC595版
51单片机七段数码管显示时钟加按键控制—②—74HC595版
一、实验内容:
1.使用七段数码管显示一个时钟
2.编写程序让接在P0口的数码管显示时分秒,秒数每秒加一
3.要求1秒时间间隔使用定时器中断实现
4.七段数码管的位选和段选通过74HC595控制
二、实验分析:
①七段数码管显示屏需要显示数字的话需要知道段码(具体显示的数值)和位码(第 1——8 位)
②单片机传输信息告诉显示屏位码和段码是多少是通过74HC595芯片实现,使用此芯片的好处是能使用更少的引脚控制更多的位(原本需要单片机上8个引脚控制位码,8个引脚控制段值,现在只需要3根引脚即可“无限”扩展)
③单片机是怎么告诉显示器位码和段码是多少的呢,如果是使用8位+8位的话,那么直接就可以通过将配置引脚输出不同的值即可选中段码和位码 。示例
而使用74HC595的话则通过51芯片先发送八位位码,再送八位段码。一个比特一个比特传输,共两个字节,调用两次SendTo595(char byteData)函数。
因为51单片机是8位的不能一次送16位进去,所以分两次执行
8+8位控制举例:例如我拿出P0_0—P0_7 八引脚控制段值,P2_0—P2_7八引脚控制位码。如果我要做第1位显示数字5,那么我只需要在P0八口输出0x6D——0110 1101, P2八口输出0x7F——0111 1111
位码选择:
*注:此处使用的是CC共阴数码管
选择段值 :
七段数码管引脚定义:
(这里的编码显示的是共阳的,共阴没找到相应的图,知道怎么对应就行。)
三、仿真图:
四、源代码:
(注释我感觉写的挺详细的,要看懂的话要先了解74HC595的工作方式)
#include "reg52.h"
#include "intrins.h"
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
//定义P0口的三个引脚,赋予不同的涵义
sbit SER = P0^1; //p0.1脚控制串行数据输入
sbit SCK = P0^0; //串行输入时钟
sbit RCK = P0^2; //存储寄存器时钟
u8 code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0~F的值
char duanMa[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//选择1-8哪个数码管段
char duanZhi[8]={0,0,0x40,0,0,0x40,0,0}; //保存8个数码管的中每个数码管段的数值 0x40:显示横杠
//num1:秒初始值 num2:分初始值 num3:时初始值
u16 num1=55,num2=59,num3=23;
static int i = 0; //给中断计数使用
//函数声明
void SendTo595(char byteData);
/***********************************************************
*函数名 :display
*功能 :对传如的时分秒进行处理计算,转化为七段数码管要显示的值
*参数 :num1 秒 num2 分 num3 时
************************************************************/
void display(u16 num1,u16 num2,u16 num3)
{
//先发送8位位码,后发送8位段码
//8位数码管需要发送8次
char i=0; //给单片机for循环使用,由于Keil4 把 变量定义放for里会报错,只能放函数体前面
//分离每个数字的个位和十位/
static char shi1,ge1,shi2,ge2,shi3,ge3;
shi1=(char)num1/10;
ge1=(char)num1%10;
shi2=(char)num2/10;
ge2=(char)num2%10;
shi3=(char)num3/10;
ge3=(char)num3%10;
///=======
//保存段值/
duanZhi[0]=smgduan[shi1];
duanZhi[1]=smgduan[ge1];
duanZhi[3]=smgduan[shi2];
duanZhi[4]=smgduan[ge2];
duanZhi[6]=smgduan[shi3];
duanZhi[7]=smgduan[ge3];
///=======
i=0;
for(;i<8;i++)
{
SendTo595(~duanMa[i]); //送段码
SendTo595(duanZhi[i]); //送位码
/*位移寄存器数据准备完毕,转移到存储寄存器*/
RCK = 0;
_nop_();
_nop_();
RCK = 1; //检测到上升沿,让存储寄存器时钟变为高电平
}
}
/*******************************************************************************
* 函 数 名 : TimerInit
* 函数功能 : 定时器0初始化
* 参数 :无
*******************************************************************************/
void TimerInit()
{
TMOD|=0X01; //选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=0X4C; //给定时器赋初值,定时50ms 3CB0
TL0=0X00; //0X3CB0的十进制是15536 从15536计数到65536计数50000次 即50000us=50ms
ET0=1; //打开定时器0中断允许
EA=1; //打开总中断
TR0=1; //打开定时器
}
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 参数 :无
*******************************************************************************/
void main()
{
TimerInit();
while(1)
{
if(i==20)//20个50毫秒即一秒
{
i=0;
num1++;
if(num1==60)
{
num1=0;
num2++;
if(num2==60)//定时一小时自动清零
{
num2=0;
num3++;
if(num3==24)
{
num3=0;
}
}
}
}
display(num3,num2,num1);
}
}
/***********************************************************
*函数名 :SendTo595
*功能 :串行发送8个比特(一个字节)的数据给595,再并行输出
*参数 :byteData
************************************************************/
void SendTo595(char byteData)
{
char i=0;
for(;i<8;i++)
{
SER = byteData>>7; //取出最高位(第8位)
byteData= byteData<<1; //将第7位移动到最高位
SCK = 0; //变为低电平,为下次准备 ,并延时2个时钟周期
_nop_();
_nop_();
SCK = 1; //上升沿,让串行输入时钟变为高电平,
}
}
/*******************************************************************************
* 函 数 名 : Timer0()
* 函数功能 : 定时器0中断函数
* 参数 :无
*******************************************************************************/
void Timer0() interrupt 1
{
TH0=0x4C;
TL0=0x00;
i++;
}
最后也来个动图演示吧