基于51单片机+74HC595移位寄存器+8位共阴数码管时钟设计—按键修改时间

基于51单片机+74HC595移位寄存器+8位共阴数码管时钟设计—按键修改时间


本文基于《基于51单片机定时器计数+74HC595移位寄存器+8位数码管时钟》修改实现,添加了按键调整时间

  • 仿真效果图
    在这里插入图片描述

程序代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#include "reg52.h"
#include "intrins.h"

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

//定义P0口的三个引脚,赋予不同的涵义
sbit SER = P0^0; //p0.1脚控制串行数据输入DS
sbit SCK = P0^1; //串行输入时钟SH
sbit RCK = P0^2; //存储寄存器时钟ST

//按键定义
sbit key_stop = P1^0;
sbit key_star = P1^1;
sbit key_fen = P1^2;
sbit key_shi = P1^3;
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 miao=55,fen=59,shi=23;
//秒、分、时高位低位
u8 miao_L, miao_H, fen_L, fen_H, shi_L, shi_H;

static int i = 0; //给中断计数使用

//函数声明
void SendTo595(char byteData);
void display();
void keyscan(); //按键函数
void key\_delay(int xms); //按键延时函数 x ms
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*函数名 :display
\*功能 :对传入的时分秒进行处理计算,转化为七段数码管要显示的值
\*参数 :num1 秒 num2 分 num3 时
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void display()
{

//先发送8位位码,后发送8位段码
//8位数码管需要发送8次
char i=0; //给单片机for循环使用,由于Keil4 把 变量定义放for里会报错,只能放函数体前面

//分离每个数字的个位和十位/
// static char shi1,ge1,shi2,ge2,shi3,ge3;
// shi1=(char)miao/10;
// ge1=(char)miao%10;

// shi2=(char)fen/10;
// ge2=(char)fen%10;

// shi3=(char)shi/10;
// ge3=(char)shi%10;
///=======

//保存段值/
duanZhi[7]=smgduan[miao_L];
duanZhi[6]=smgduan[miao_H];
duanZhi[3]=smgduan[fen_H];
duanZhi[4]=smgduan[fen_L];
duanZhi[1]=smgduan[shi_L];
duanZhi[0]=smgduan[shi_H];
///=======

i=0;
for(;i<8;i++)
{
SendTo595(~duanMa[i]); //送段码
SendTo595(duanZhi[i]); //送位码

/\*位移寄存器数据准备完毕,转移到存储寄存器\*/
RCK = 0; //ST
\_nop\_();
\_nop\_();
RCK = 1; //检测到上升沿,让存储寄存器时钟变为高电平
}

}


/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* 函 数 名 : TimerInit
\* 函数功能 : 定时器0初始化
\* 参数 :无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void TimerInit()
{
TMOD|=0X01; //选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=0X3c; //给定时器赋初值,定时50ms 3CB0
TL0=0Xb0; //0X3CB0的十进制是15536 从15536计数到65536计数50000次 即50000us=50ms
ET0=1; //打开定时器0中断允许
EA=1; //打开总中断
TR0=1; //打开定时器

}

/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* 函 数 名 : main
\* 函数功能 : 主函数
\* 参数 :无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void main()
{
TimerInit();

while(1)
{
display();
keyscan(); //按键函数
}
}


/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*函数名 :SendTo595
\*功能 :串行发送8个比特(一个字节)的数据给595,再并行输出
\*参数 :byteData
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void SendTo595(char byteData)
{
char i=0;
for(;i<8;i++)
{
SER = byteData>>7; //DS取出最高位(第8位)
byteData= byteData<<1; //将第7位移动到最高位

SCK = 0; //变为低电平,为下次准备 ,并延时2个时钟周期
\_nop\_();
\_nop\_();

SCK = 1; //上升沿,让串行输入时钟变为高电平,
}
}


/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* 函 数 名 : Timer0()
\* 函数功能 : 定时器0中断函数
\* 参数 :无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void Timer0() interrupt 1
{
TL0 =(65536-50000)%256;//50ms预装载值
TH0 =(65536-50000)/256;
i++;
if(i==20)//20个50毫秒即一秒
{
i=0;
miao++;
if(miao==60)
{
miao=0;
fen++;
if(fen==60)//定时一小时自动清零
{
fen=0;
shi++;
if(shi==24)
{
shi=0;
fen =0;
miao = 0;
}
}
}
miao_L = miao%10;
miao_H = miao/10;

fen_L = fen%10;
fen_H = fen/10;

shi_L = shi%10;
shi_H = shi/10;
}
// display();
}
void keyscan(){ //按键函数

if(key_stop == 0){
key\_delay(10);
if(key_stop == 0){
EA = 0; //关总中断,即停止
while(!key_stop);
}
}//key\_stop

if(key_star == 0){
key\_delay(10);
if(key_star == 0){
EA = 1; //开总中断
while(!key_star);
}
}//key\_star

if(key_fen == 0){
key\_delay(10);
if(key_fen == 0){
fen++;
while(!key_fen);
}
}//key\_fen++

if(key_shi == 0){
key\_delay(10);
if(key_shi == 0){
shi++;
while(!key_shi);
}
}//key\_shi++
}
void key\_delay(int xms){ //按键延时函数 x ms
unsigned int i, j;
for(i=0; i<xms; ++i)
for(j=0; j<110; ++j)
;
}//key\_delay

  • 如果采用共阳数码管
1
2
3
4
5
6
7
u8 code smgduan[17]={
// 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
// 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//共阴显示0~F的值
//0 1 2 3 4 5 6 7 8 9 A b C d E F -
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x8C, 0xBF, 0xC6, 0xA1, 0x86, 0xFF, 0xbf
};//共阳显示0~F的值

共阳数字间隔代码

1
2
3
char duanZhi[8]={0,0,0xbf,0,0,0xbf,0,0};	 	//保存8个数码管的中每个数码管段的数值 0x40共阴,0xbf共阳:显示横杠


  • 发送数据
1
2
3
SendTo595(duanMa[i]); 		//送段码,采用共阴就取反
SendTo595(duanZhi[i]); //送位码

在移位寄存器数据处理函数比较好理解代码方式

74HC595数据处理方式:
ST拉低,for(i=0;i<8;i++){SH拉低,存入DS数据,SH拉高,}ST拉高

  • main.c代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#include "reg52.h"
#include "intrins.h"

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

//定义P0口的三个引脚,赋予不同的涵义
sbit DS= P0^1; //p0.1脚控制串行数据输入DS
sbit SH = P0^0; //串行输入时钟SH
sbit ST = P0^2; //存储寄存器时钟ST

//按键定义
sbit key_stop = P1^0;
sbit key_star = P1^1;
sbit key_fen = P1^2;
sbit key_shi = P1^3;
u8 code smgduan[17]={
// 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
// 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//共阴显示0~F的值
//0 1 2 3 4 5 6 7 8 9 A b C d E F -
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x8C, 0xBF, 0xC6, 0xA1, 0x86, 0xFF, 0xbf
};//共阳显示0~F的值

char duanMa[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//选择1-8哪个数码管段
char duanZhi[8]={0,0,0xbf,0,0,0xbf,0,0}; //保存8个数码管的中每个数码管段的数值 0x40共阴,0xbf共阳:显示横杠

//num1:秒初始值 num2:分初始值 num3:时初始值
u16 miao=55,fen=59,shi=23;
//秒、分、时高位低位
u8 miao_L, miao_H, fen_L, fen_H, shi_L, shi_H;

static int i = 0; //给中断计数使用

//函数声明
void SendTo595(char byteData);
void display();
void keyscan(); //按键函数
void key\_delay(int xms); //按键延时函数 x ms
/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*函数名 :display
\*功能 :对传入的时分秒进行处理计算,转化为七段数码管要显示的值
\*参数 :num1 秒 num2 分 num3 时
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void display()
{

//先发送8位位码,后发送8位段码
//8位数码管需要发送8次
char i=0; //给单片机for循环使用,由于Keil4 把 变量定义放for里会报错,只能放函数体前面

//分离每个数字的个位和十位/
// static char shi1,ge1,shi2,ge2,shi3,ge3;
// shi1=(char)miao/10;
// ge1=(char)miao%10;

// shi2=(char)fen/10;
// ge2=(char)fen%10;

// shi3=(char)shi/10;
// ge3=(char)shi%10;
///=======

//保存段值/
duanZhi[7]=smgduan[miao_L];
duanZhi[6]=smgduan[miao_H];
duanZhi[3]=smgduan[fen_H];
duanZhi[4]=smgduan[fen_L];
duanZhi[1]=smgduan[shi_L];
duanZhi[0]=smgduan[shi_H];
///=======

i=0;
for(;i<8;i++)
{
SendTo595(duanMa[i]); //送段码,采用共阴就取反
SendTo595(duanZhi[i]); //送位码

/\*位移寄存器数据准备完毕,转移到存储寄存器\*/
ST = 0; //ST
\_nop\_();
\_nop\_();
ST = 1; //检测到上升沿,让存储寄存器时钟变为高电平
}

}


/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* 函 数 名 : TimerInit
\* 函数功能 : 定时器0初始化
\* 参数 :无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void TimerInit()
{
TMOD|=0X01; //选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=0X3c; //给定时器赋初值,定时50ms 3CB0
TL0=0Xb0; //0X3CB0的十进制是15536 从15536计数到65536计数50000次 即50000us=50ms
ET0=1; //打开定时器0中断允许
EA=1; //打开总中断
TR0=1; //打开定时器

}

/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* 函 数 名 : main
\* 函数功能 : 主函数
\* 参数 :无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void main()
{
TimerInit();

while(1)
{
display();
keyscan(); //按键函数
}
}


/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\*函数名 :SendTo595
\*功能 :串行发送8个比特(一个字节)的数据给595,再并行输出
\*参数 :byteData
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void SendTo595(char byteData)
{
char i=0;
ST = 0; //ST
for(;i<8;i++)
{
DS = byteData>>7; //DS取出最高位(第8位)
byteData= byteData<<1; //将第7位移动到最高位

SH = 1; //上升沿,让串行输入时钟变为高电平,并延时2个时钟周期
\_nop\_();
\_nop\_();

SH = 0; //变为低电平,为下次准备
}
}


/\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*
\* 函 数 名 : Timer0()
\* 函数功能 : 定时器0中断函数
\* 参数 :无
\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/
void Timer0() interrupt 1
{
TL0 =(65536-50000)%256;//50ms预装载值
TH0 =(65536-50000)/256;
i++;
if(i==20)//20个50毫秒即一秒
{
i=0;
miao++;
if(miao==60)
{
miao=0;
fen++;
if(fen==60)//定时一小时自动清零
{
fen=0;
shi++;
if(shi==24)
{
shi=0;
fen =0;
miao = 0;
}
}
}
miao_L = miao%10;
miao_H = miao/10;

fen_L = fen%10;
fen_H = fen/10;

shi_L = shi%10;
shi_H = shi/10;
}
// display();
}
void keyscan(){ //按键函数

if(key_stop == 0){
key\_delay(10);
if(key_stop == 0){
EA = 0; //关总中断,即停止
while(!key_stop);
}
}//key\_stop

if(key_star == 0){
key\_delay(10);
if(key_star == 0){
EA = 1; //开总中断
while(!key_star);
}
}//key\_star

if(key_fen == 0){
key\_delay(10);
if(key_fen == 0){
fen++;
while(!key_fen);
}
}//key\_fen++

if(key_shi == 0){
key\_delay(10);
if(key_shi == 0){
shi++;
while(!key_shi);
}
}//key\_shi++
}
void key\_delay(int xms){ //按键延时函数 x ms
unsigned int i, j;
for(i=0; i<xms; ++i)
for(j=0; j<110; ++j)
;
}//key\_delay


📚程序源码和仿真资源

✨本试验基于Proteus8.12平台。

1
2
3
链接:https://pan.baidu.com/s/1ePM0G9xEfIP5NgZL1f\_wCw 
提取码:4pe2