蓝桥杯大赛-单片机设计与开发基础技能小蜜蜂课程学习笔记

LED 指示灯的基本控制

基本原理

74HC138 译码器

8 个输出为互斥的低有效输出,只有 1 路输出低电平,其他均输出高电平

74HC573 锁存器

8 路三态输出的非反转透明锁存器 当锁存使能端 LE 为高时,这些器件的锁存对于数据是透明的(即输出同步) 当锁存使能变低时,符合建立时间和保持时间的数据会被锁存(即输入端不影响输出端)

74HC02 或非门

当输入均为 0 时,或非门才输出 1

点亮 LED 完整流程

要使 LED 点亮,需要锁存器为透明状态(同步输出),此时锁存器使能端 LE 应为高电平 而要使 LE 为高电平,则或非门输入均应为低电平, WR 已经接地,只需将 Y4 电平拉低即可 而要使 Y4 为低电平,则需要译码器的输入为 1 0 0


代码如下:( 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
#include "reg52.h" 

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

//关闭蜂鸣器
void cls_buzz(void)
{
P2 = (P2&0x1F0xA0);
P0 = 0x00;
P2 &= 0x1F;
}

void Delay(unsigned int t)
{
while(t--);
while(t--);
}

void LEDRunning()
{
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;

//0x00 即为将 P0 所有引脚设为低电平 对应二进制“00000000”
//0xff 即为将 P0 所有引脚设为高电平 对应二进制“11111111”
//相应的,0xaa 即为二进制“10101010” 间隔点亮
P0 = 0x00;
Delay(60000);
Delay(60000);
P0 = 0xff;
Delay(60000);
Delay(60000);
}

void main(void)
{
cls_buzz();
while(1)
{
LEDRunning();
}
}

练习题目

先让 8 路 LED 指示灯闪烁 3 遍然后熄灭,接着依次点亮 LED 指示灯,最后依次熄灭指示灯,程序循环实现上述功能

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
#include "reg52.h" 

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

void cls_buzz(void)
{
P2 = (P2&0x1F0xA0);
P0 = 0x00;
P2 &= 0x1F;
}

void Delay(unsigned int t)
{
while(t--);
while(t--);
}

void LEDRunning()
{
unsigned char i;
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;

for(i=0; i<3; i++)
{
P0 = 0xff;
Delay(60000);
Delay(60000);
P0 = 0x1fe;
Delay(60000);
Delay(60000);
}
for(i=1; i<=8; i++)
{
//<< 为左移符号,如“11111111”左移后为“11111110”
//程序中的 LED 引脚按照从高位到低位排列,详见下方表格
P0 = 0xff << i;
Delay(60000);
Delay(60000);
}
for(i=1; i<=8; i++)
{
P0 = ~(0xff << i);
Delay(60000);
Delay(60000);
}
}

void main(void)
{
cls_buzz();
while(1)
{
LEDRunning();
}
}

1

1

1

1

1

1

1

0

P0^7

P06

P0^5

P0^4

P0^3

P0^2

P0^1

P0^0

LED8

LED7

LED6

LED5

LED4

LED3

LED2

LED1

即“11111110”代表灯 LED1 亮,其他全灭,反之亦然

蜂鸣器与继电器的基本控制

基本原理

ULN2003 IC 控制器

内部主要为一个非门,会对输入值进行取反,高电平转换为低电平

启动蜂鸣器与继电器完整流程

要启动蜂鸣器,就需要 N BUZZ 引脚为低电平 对应 ULN2003 的输出引脚 N BUZZ 为低电平,因其内部为一个非门,则其输入引脚应为高电平 此时 74HC573 锁存器的使能端 LE Y5C 应为高电平以向 ULN2002 输出高电平 要使 Y5C 输出高电平,则需要或非门 74HC02 的输入端均为低电平,因 WR 脚已经为低电平,只需将 Y5 脚拉低即可 通过控制 74HC138 译码器的输入为 101 可以使 Y5 脚输出低电平


代码如下:

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
#include "reg52.h" 

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

void Delay(unsigned int t)
{
while(t--);
while(t--);
}

void HC138()
{
//译码器输入 101,对 Y5 输出低电平
HC138_A = 1;
HC138_B = 0;
HC138_C = 1;
}

void InitSystem()
{
HC138();
P0 = 0x00;
}
void Relay()
{
HC138();
P0 = 0x10;
}

void Beep()
{
HC138();
P0 = 0x40;
}

//主函数
void main(void)
{
InitSystem();

while(1)
{
//继电器与蜂鸣器交替启动
Relay();
Delay(6000);
Delay(6000);
Beep();
Delay(6000);
Delay(6000);
}
}

练习题目

首先让 8 路 LED 指示灯闪烁 3 遍后熄灭,接着依次点亮 LED 指示灯,继电器吸合一会儿后断开,然后依次熄灭 LED 指示灯,蜂鸣器鸣叫一会儿后关闭,程序循环实现上述功能。

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
#include "reg52.h" 

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

void Delay(unsigned int t)
{
while(t--);
while(t--);
while(t--);
}

void HC138(unsigned char n)
{
switch(n)
{
case 4:
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;
break;
case 5:
//译码器输入 101,对 Y5 输出低电平
HC138_C = 1;
HC138_B = 0;
HC138_A = 1;
break;
}
}

void InitSystem()
{
HC138(5);
P0 = 0x00;
}

void Relay(unsigned char s)
{
switch(s)
{
case 1:
HC138(5);
P0 = 0x10;
break;
case 0:
HC138(5);
P0 = 0x00;
break;
}
}

void LED(unsigned char s)
{
switch(s)
{
case 1:
HC138(4);
P0 = 0x00;
break;
case 0:
HC138(4);
P0 = 0xff;
break;
}
}

void Beep(unsigned char n)
{
switch(n)
{
case 1:
HC138(5);
P0 = 0x40;
break;
case 0:
HC138(5);
P0 = 0x00;
break;
}
}

//主函数
void main(void)
{
unsigned int i=0;
InitSystem();
while(1)
{
//LED 闪烁 3 次
for(i=0 ;i<3 ;i++)
{
LED(1);
Delay(9000);
Delay(9000);
LED(0);
Delay(9000);
Delay(9000);
}
//LED 依次点亮
for(i=1 ;i<=8 ;i++)
{
P0 = 0xff << i;
Delay(9000);
Delay(9000);
}
//继电器吸合一会儿再关闭
Relay(1);
Delay(9000);
Delay(9000);
Relay(0);
Delay(9000);
//LED 依次熄灭
HC138(4);
for(i=1 ;i<=8 ;i++)
{
P0 = ~(0xff << i);
Delay(9000);
Delay(9000);
}
//蜂鸣器叫一会儿然后关闭
Beep(1);
Delay(9000);
Delay(9000);
Beep(0);
Delay(9000);
}
}

共阳数码管的静态显示

基本原理

7SEG-4 数码管

由段码和 com 口组成,com 口控制第几位的数码管进行显示,段码控制每一个数码管显示的内容

数码管静态显示完整流程

比赛平台使用的数码管为共阳数码管,当阴极为低电平时数码管点亮 段码控制数码管显示的具体内容,而段码由 74HC573 锁存器直接控制,需要将其使能端 Y7C 输出高电平从而使锁存器处于透明状态 数码管显示 0~F 以及 ” . ” 分别对应段码:

1
2
unsigned char code table[]={0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 
0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e, 0x7f};

com 口控制哪几个或第几个数码管进行显示,当 com 口处于高电平时,对应数码管点亮 com 口由 74HC573 锁存器直接控制,需要将其使能端 Y6C 输出高电平从而使锁存器处于透明状态

练习题目

8 个数码管分别单独依次显示 0~9 的值,然后所有数码管一起同时显示 0~F 的值,如此往复

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
#include "reg52.h"

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

unsigned char code ParaCode[]={0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8,
0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e, 0x7f};

void Delay(unsigned int t)
{
while(t--);
while(t--);
while(t--);
}

void HC138(unsigned char n)
{
switch(n)
{
case 6:
HC138_C = 1;
HC138_B = 1;
HC138_A = 0;
break;
case 7:
HC138_C = 1;
HC138_B = 1;
HC138_A = 1;
break;
}
}

void ShowAll(unsigned char dat)
{
HC138(6);
P0 = 0xff;
HC138(7);
P0 = ParaCode[dat];
}

void ShowStatic(unsigned char dat, unsigned char pos)
{
HC138(6);
P0 = 0x01 << pos;
HC138(7);
P0 = ParaCode[dat];
}

void main()
{
unsigned char i,j,k;
while(1)
{
for(j=0; j<8; j++)
{
for(i=0; i<=9; i++)
{
ShowStatic(i,j);
Delay(6000);
Delay(6000);
}
}
for(k=0; k<16; k++)
{
ShowAll(k);
Delay(6000);
Delay(6000);
}

}
}

共阳数码管的动态显示

基本原理

动态显示实质上就是轮流点亮单个数码管实现多位数码管整体显示的效果。 在轮流显示过程中,每位数码管点亮时间为 1~2s ,由于人的视觉暂留现象及发光二极管的余辉效应,尽管实际上各位 数码管并非同时点亮,但只要扫描的速度足够快,给人的印象就是一组稳定的显示数据,不会有闪烁感,动态显示的效果和静态显示是一样的,能够节省大量的 IO 端口,而且功耗更低。 对于一组 4 位数码管来说,静态显示和动态显示都能实现同样的效果,但需要的 IO 端口是不同的。 静态显示需要 IO 端口: 8 个段码 * 4 + 4 个 COM 端 =3 6 个 IO 引脚 动态显示需要 IO 端口:8 个段码 + 4 个 COM 端 = 12 个 IO 引脚

练习题目

在 8 位数码管中,前 4 位显示年份 “2022”,接着 2 位显示分隔符 “-”,最后两位是月份,从 1 月份开始,每隔一段时间加 1 个月,到 12 月之后又从 1 月开始递增,如此往复。

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
#include "reg52.h"

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;
void Delay(unsigned int t);
void DelayDigitalTube(unsigned int t);
void Show(unsigned char dat, unsigned char pos);

unsigned char code ParaCode[]={0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8,
0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e, 0x7f, 0xbf};

unsigned char month = 1;

void ShowYear()
{
Show(2,0);
DelayDigitalTube(600);
Show(0,1);
DelayDigitalTube(600);
Show(2,2);
DelayDigitalTube(600);
Show(2,3);
DelayDigitalTube(600);
Show(17,4);
DelayDigitalTube(600);
Show(17,5);
DelayDigitalTube(600);
}

void ShowMonth()
{
Show(month/10,6);
DelayDigitalTube(600);
Show(month%10,7);
DelayDigitalTube(600);
}

void Delay(unsigned int t)
{
while(t--)
{
ShowYear();
ShowMonth();
}
}

void DelayDigitalTube(unsigned int t)
{
while(t--);
}

void HC138(unsigned char n)
{
switch(n)
{
case 6:
HC138_C = 1;
HC138_B = 1;
HC138_A = 0;
break;
case 7:
HC138_C = 1;
HC138_B = 1;
HC138_A = 1;
break;
}
}

void Show(unsigned char dat, unsigned char pos)
{
HC138(6);
P0 = 0x01 << pos;
HC138(7);
P0 = ParaCode[dat];
}

void main()
{
while(1)
{
ShowYear();
ShowMonth();
if(month > 12)
{
month = 1;
}
Delay(100);
month++;
}
}

独立按键的基本操作与扩展应用

基本原理

将 J5 处跳帽接到 2~3 引脚,使按键 S4~S7 四个按键的另一端接地成为 4 个独立键盘。 因为 4 个按键的一端均已接地,只需要读取另一端 P30~P33 是否为低电平便可确认按键是否按下。 在扫描按键的过程中,发现有按键触发信号后,先做去抖动处理,当确认按键按下时,才进行相应的功能处理。

练习题目

按下 S7 点亮 L1 指示灯,松开按键指示灯熄灭。 S6 点亮 L2 指示灯;S5 点亮 L3 指示灯;S4 点亮 L4 指示灯。 都是松开熄灭,按键均作去抖。

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
#include "reg52.h"

//定义按键
sbit S7 = P3^0;
sbit S6 = P3^1;
sbit S5 = P3^2;
sbit S4 = P3^3;

//定义指示灯
sbit L1 = P0^0;
sbit L2 = P0^1;
sbit L3 = P0^2;
sbit L4 = P0^3;

//定义译码器
sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

//按键去抖动延时
void DelayKey(unsigned char t)
{
while(t--);
}

//按键扫描
void ScanInd()
{
if(S7 == 0)
{
DelayKey(200);
if(S7 == 0)
{
L1 = 0; //点亮LED1
while(S7 == 0);
L1 = 1;
}
}
if(S6 == 0)
{
DelayKey(200);
if(S6 == 0)
{
L2 = 0; //点亮LED2
while(S6 == 0);
L2 = 1;
}
}
if(S5 == 0)
{
DelayKey(200);
if(S5 == 0)
{
L3 = 0; //点亮LED3
while(S5 == 0);
L3 = 1;
}
}
if(S4 == 0)
{
DelayKey(200);
if(S4 == 0)
{
L4 = 0; //点亮LED4
while(S4 == 0);
L4 = 1;
}
}
}

void HC138(unsigned char n)
{
switch(n)
{
case 4:
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;
break;
case 5:
HC138_C = 1;
HC138_B = 0;
HC138_A = 1;
break;
case 6:
HC138_C = 1;
HC138_B = 1;
HC138_A = 0;
break;
case 7:
HC138_C = 1;
HC138_B = 1;
HC138_A = 1;
break;
}
}

void main()
{
HC138(5);
P0 = 0x00;//关闭继电器蜂鸣器
HC138(4);
P0 = 0xff;//关闭所有指示灯
while(1)
{
//P0 = 0x00;
ScanInd();
}
}

矩阵键盘的扫描原理与基本应用

基本原理

将 J5 处跳帽接到 1~2 引脚,使按键 S4~S7 四个按键的另一端接入 IO 口使得16个按键成为矩阵键盘。 矩阵键盘的两端均为 IO 口,一端输入一端输出。 初始时输入全部置为 1 ,扫描时输入逐行置 0 ,若在对应列的输出检测到低电平,则表明对应行列的按键被按下。

练习题目

按照自左到右自上到下的顺序,每个按键分别对应 0~F ,按下按键就将对应数字显示到数码管上。

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
#include "STC15F2K60S2.H"

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

sbit R1 = P3^0;
sbit R2 = P3^1;
sbit R3 = P3^2;
sbit R4 = P3^3;

sbit C4 = P3^4;
sbit C3 = P3^5;
sbit C2 = P4^2;
sbit C1 = P4^4;

unsigned char code ParaCode[]={0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8,
0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e, 0x7f, 0xbf};

void HC138(unsigned char n)
{
switch(n)
{
case 4:
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;
break;
case 5:
HC138_C = 1;
HC138_B = 0;
HC138_A = 01;
break;
case 6:
HC138_C = 1;
HC138_B = 1;
HC138_A = 0;
break;
case 7:
HC138_C = 1;
HC138_B = 1;
HC138_A = 1;
break;
}
}

void Display(unsigned char dat)
{
HC138(6);
P0 = 0x01;
HC138(7);
P0 = ParaCode[dat];
}

void ScanKeyMulti()
{
R1 = 0;//先从第1行开始扫描,第1行置0
R2 = R3 = R4 = 1;//其他行置1
if(C1 == 0)
{
while(C1 ==0)
{
Display(0);
}
}
else if(C2 == 0)
{
while(C2 ==0)
{
Display(1);
}
}
else if(C3 == 0)
{
while(C3 ==0)
{
Display(2);
}
}
else if(C4 == 0)
{
while(C4 ==0)
{
Display(3);
}
}

R2 = 0;//从第2行开始扫描,第2行置0
R1 = R3 = R4 = 1;//其他行置1
if(C1 == 0)
{
while(C1 ==0)
{
Display(4);
}
}
else if(C2 == 0)
{
while(C2 ==0)
{
Display(5);
}
}
else if(C3 == 0)
{
while(C3 ==0)
{
Display(6);
}
}
else if(C4 == 0)
{
while(C4 ==0)
{
Display(7);
}
}

R3 = 0;//从第3行开始扫描,第3行置0
R2 = R1 = R4 = 1;//其他行置1
if(C1 == 0)
{
while(C1 ==0)
{
Display(8);
}
}
else if(C2 == 0)
{
while(C2 ==0)
{
Display(9);
}
}
else if(C3 == 0)
{
while(C3 ==0)
{
Display(10);
}
}
else if(C4 == 0)
{
while(C4 ==0)
{
Display(11);
}
}

R4 = 0;//从第4行开始扫描,第4行置0
R2 = R3 = R1 = 1;//其他行置1
if(C1 == 0)
{
while(C1 ==0)
{
Display(12);
}
}
else if(C2 == 0)
{
while(C2 ==0)
{
Display(13);
}
}
else if(C3 == 0)
{
while(C3 ==0)
{
Display(14);
}
}
else if(C4 == 0)
{
while(C4 ==0)
{
Display(15);
}
}
}

void main()
{
while(1)
{
ScanKeyMulti();
}
}

中断系统与外部中断应用

基本原理

内核与外设之间的主要交互方式有两种:轮询和中断。 轮询的方式貌似公平,但实际工作效率很低,且不能及时响应紧急事件;中断系统使得内核具备了应对突发事件的能力。 在执行CPU当前程序时,由于系统中出现了某种急需处理的情况,CPU暂停正在执行的程序,转而去执行另外一段特殊程序来处理出现的紧急事务,处理结束后,CPU自动返回到原来暂停的程序中去继续执行。这种程序在执行过程中由于外界的原因而被中间打断的情况,称为中断。 中断服务函数:内核响应中断后执行的相应处理程序。 中断向量:中断服务程序的入口地址。每个中断源都对应 个固定的入口地址。当内核响应中断请求时,就会暂停当前的程序执行,然后跳转到该入口地址执行代码。 一般来说,51单片机有5个中断源(忽略定时/计数器2),分2个优先级,这个5个中断源按照自然优先级从高到低依次为:

优先级

中断源

0

外部中断0:INT0

1

定时/计数器0:TF0

2

外部中断1:INT1

3

定时/计数器1:TF1

4

串口中断:RI/TI

练习题目

首先将 J5 处的跳帽接到 2~3 引脚,即 S5 按键接到 P32/INT0, S4 按键接到 P33/INT1 。定义一个 Working() 函数, 使 L1 指示灯不断闪烁。将 P32 引脚定义成外部中断功能,按 S5 按键就会产生外部中断触发信号,在中断响应函数中,点亮 L8 指示灯, 延时一段较长的时间后熄灭,该功能用两种方式实现: 1-直接在中断服务函 数中延时。 2-在中断服务函数中标志变量,在外部执行延时。

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
#include "STC15F2K60S2.H"

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

void HC138(unsigned char n)
{
switch(n)
{
case 4:
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;
break;
case 5:
HC138_C = 1;
HC138_B = 0;
HC138_A = 1;
break;
}
}

void InitSystem()
{
HC138(4);
P0 = 0xff;
HC138(5);
P0 = 0x00;
}

void Delay(unsigned int t)
{
while(t--);
while(t--);
while(t--);
}

void Working()
{
HC138(4);
P0 = 0xfe;
Delay(9000);
P0 = 0xff;
Delay(9000);
}

void InitINT0()
{
IT0 = 1; //选择下降沿触发
EX0 = 1; //启动外部中断
EA = 1; //启动总中断
}

unsigned char InterruptStat = 0; //中断标志位
void ServiceINT0() interrupt 0
{
InterruptStat = 1;
}

void InterruptProgram()
{
if(InterruptStat == 1)
{
HC138(4);
P0 = 0x7f;
Delay(999999);
Delay(999999);
Delay(999999);
Delay(999999);
Delay(999999);
Delay(999999);
P0 = 0xff;
}
InterruptStat = 0;
}

void main()
{
InitSystem();
InitINT0();
while(1)
{
Working();
InterruptProgram();
}
}

定时计数器的基本原理与应用

基本原理

【蓝桥杯单片机08】定时器的基本原理与应用 - - 21ic电子技术开发论坛 定时/计数器的编程思路 在定时/计数器的程序设计中,通常有两个函数:初始化函数中断服务函数在初始化函数中,一般需要进行以下几个配置: <1> 配置工作模式,即对TMOD寄存器编程。 <2> 计算技术初值,即对THx和TLx寄存器进行赋值。 <3> 使能定时/计数器中断,即ET0或ET1置1。 <4> 打开总中断,即EA =1。 <5> 启动定时器,即TR0或TR1置1。 在中断服务函数中,一般需要进行以下的编程: <1> 如果不是自动重装模式,需要对THx和TLx重新赋值。 <2> 进行间隔定时到达的逻辑处理(越少越好)。 其程序框架和代码编写基本上差不多:

练习题目

利用单片机的定时/计数器 T0 的模式 1 实现间隔定时,每隔 1 秒 L1 指示灯闪烁一下,也就是点亮 0.5 秒,熄灭 0.5 秒; 每隔 10 秒 L8 指示灯闪烁一下,即点亮 5 秒,熄灭 5 秒。

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
#include "STC15F2K60S2.H"

sbit L1 = P0^0;
sbit L8 = P0^7;

sbit L2 = P0^1;
sbit L3 = P0^2;
sbit L4 = P0^3;
sbit L5 = P0^4;
sbit L6 = P0^5;
sbit L7 = P0^6;

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

void HC138(unsigned char n)
{
switch(n)
{
case 4:
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;
break;
case 5:
HC138_C = 1;
HC138_B = 0;
HC138_A = 1;
break;
}
}

void Delay(unsigned int t)
{
while(t--);
while(t--);
while(t--);
}

void InitSystem()
{
HC138(4);
P0 = 0xff;
HC138(5);
P0 = 0x00;
}

void InitTimer0()
{
TMOD = 0x01;
TH0 = (65535 - 50000) / 256;
TL0 = (65535 - 50000) % 256;

EA = 1; //启动总中断
ET0 = 1; //打开T0定时器中断
TR0 = 1; //启用T0计数器/定时器
}

unsigned int count = 0;
void ServiceTimer0() interrupt 1
{
HC138(4);
count++;
if(count % 10 == 0)
{
L1 = ~L1;
}
if(count == 100)
{
L8 = ~L8;
count = 0;
}
}

void main()
{
InitSystem();
Delay(99999);
InitTimer0();
while(1)
{
HC138(4);
L2 = 1;
L3 = 1;
L4 = 1;
L5 = 1;
L6 = 1;
L7 = 1;
}
}

定时器的进阶综合案例解析

利用定时器 T0 、数码管模块和 2 个独立按键(J5 的 2~3 短接),设计一个秒表,具有清零、暂停、启动功能。 1、显示格式为:分 - 秒 - 0.05 秒(即 50ms) 08-26-18 表示:8 分 26 秒 900 毫秒 2、独立按键 S4 为:暂停/启动;独立按键 S5 为:清零。按键均为按下有效

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
#include "STC15F2K60S2.H"

sbit S4 = P3^3;
sbit S5 = P3^2;

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

unsigned char min = 0;
unsigned char sec = 0;
unsigned char ms = 0;

void HC138(unsigned char n)
{
switch(n)
{
case 4:
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;
break;
case 5:
HC138_C = 1;
HC138_B = 0;
HC138_A = 1;
break;
case 6:
HC138_C = 1;
HC138_B = 1;
HC138_A = 0;
break;
case 7:
HC138_C = 1;
HC138_B = 1;
HC138_A = 1;
break;
}
}

void InitSystem()
{
HC138(4);
P0 = 0xff;
HC138(5);
P0 = 0x00;
}

//=============数码管相关函数==============
unsigned char code ParaCode[]=
{0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8,
0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e, 0x7f, 0xbf};

void DelayDitialTube(unsigned char t)
{
while(t--);
}

void DisplayBit(unsigned char dat, unsigned char pos)
{
HC138(6);
P0 = 0x01 << pos;
HC138(7);
P0 = dat;
}

void DisPlayTime()
{
DisplayBit(ParaCode[ms%10],7);//显示毫秒
DelayDitialTube(500);
DisplayBit(ParaCode[ms/10],6);
DelayDitialTube(500);

DisplayBit(ParaCode[17],5);//显示分隔线
DelayDitialTube(500);

DisplayBit(ParaCode[sec%10],4);//显示秒
DelayDitialTube(500);
DisplayBit(ParaCode[sec/10],3);
DelayDitialTube(500);

DisplayBit(ParaCode[17],2);//显示分隔线
DelayDitialTube(500);

DisplayBit(ParaCode[min%10],1);//显示分
DelayDitialTube(500);
DisplayBit(ParaCode[min/10],0);
DelayDitialTube(500);
}
//=========================================

//=============定时器相关函数==============
void InitTimer0()
{
TMOD = 0x01;
TH0 = (65535 - 50000) / 256;
TL0 = (65535 - 50000) % 256;

EA = 1;
ET0 = 1;
TR0 = 1;
}

void ServicesTimer0() interrupt 1
{
ms++;
if(ms == 20)
{
sec++;
ms = 0;
if(sec == 60)
{
min++;
sec = 0;
if(min == 99)
{
min = 0;
}
}
}
}
//=========================================

//=============按键相关函数==============
void DelayKey(unsigned char t)
{
while(t--);
}

void ScanKey()
{
if(S4 == 0)//秒表启动与暂停
{
DelayKey(100);
if(S4 == 0)
{
TR0 = ~TR0;
while(S4 == 0)
{
DisPlayTime();
}
}
}
if(S5 == 0)//秒表清零
{
DelayKey(100);
if(S5 == 0)
{
ms = 0;
sec = 0;
min = 0;
while(S5 == 0)
{
DisPlayTime();
}
}
}
}
//=========================================
void main()
{
InitSystem();
InitTimer0();
while(1)
{
DisPlayTime();
ScanKey();
}
}

PWM 脉宽调制信号的发生与控制

基本原理

基础是通过定时器进行中断控制,若题目要求为 100Hz ,则根据频率与周期的反比关系,一个周期应为 1/100 秒,即 10ms 而定时器的计时单位为微秒 us,故周期应换算为 10‘000us 在这 10’000us 的周期中,通过调控高电平持续的时间比例即占空比来控制设备

练习题目

利用 PWM 脉宽信号实现独立按键 S7 对 L1 指示灯亮度变化的控制。 具体要求如下:

  1. PWM 脉宽信号的频率为 100Hz
  2. 系统上电后 L1 指示灯处在熄灭状态
  3. L1 指示灯有 4 种亮度模式,分别是完全熄灭、10% 的亮度、50% 的亮度和 90% 的亮度。
  4. 按下 S7 键,循环切换 L1 指示灯的四种亮度模式。
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
#include "STC15F2K60S2.H"

sbit S7 = P3^0;

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

void HC138(unsigned char n)
{
switch(n)
{
case 4:
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;
break;

case 5:
HC138_C = 1;
HC138_B = 0;
HC138_A = 1;
break;
}
}

void InitSystem()
{
HC138(4);
P0 = 0xff;
HC138(5);
P0 = 0x00;
}

//=================计时器函数===================
void InitTimer0()
{
TMOD = 0x01;
TH0 = (65535 - 100) / 256;
TL0 = (65535 - 100) % 256;

//此处TR0不需要启用,在按键扫描函数内启用即可
EA = 1;
ET0 = 1;
}

unsigned char KeyStat = 0;//保存按键状态
unsigned char PWM = 0;//保存占空比
unsigned char count = 0;//保存当前进行到周期的第几次,当到达占空比时执行相应操作
void ServiceTimer0() interrupt 1
{
TH0 = (65535 - 100) / 256;
TL0 = (65535 - 100) % 256;

count++;
if(count == PWM)
{
//当进行到占空比时关闭LED
P0 = 0xff;
}
else if(count == 100)
{
//当占空比进行到100完成一整个周期时打开LED并将计数器归零
P0 = 0xfe;
count = 0;
}
}
//===============================================

//================键盘扫描函数===================
void DelayKey(unsigned int t)
{
while(t--);
}

void ScanKey()
{
if(S7 == 0)
{
DelayKey(200);//按键去抖动
if(S7 == 0)
{
//按键操作
switch(KeyStat)
{
//第一次按,打开LED和计时器TR0,占空比置10
case 0:
P0 = 0xfe;
TR0 = 1;
PWM = 10;
KeyStat = 1;
break;
//第二次按,调整占空比到50
case 1:
PWM = 50;
KeyStat = 2;
break;
//第三次按,调整占空比到90
case 2:
PWM = 90;
KeyStat = 3;
break;
//第四次按,关闭LED和计时器TR0,按键状态归零
case 3:
P0 = 0xff;
TR0 = 0;
KeyStat = 0;
break;
}
while(S7 == 0);
}
}
}
//===============================================

void main()
{
InitSystem();
HC138(4);
P0 = 0xff;
InitTimer0();
while(1)
{
ScanKey();
}
}

串口通信的基本原理与应用

基本原理

串口通信概述

微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:并行通信和串行通信并行通信:指数据的各位同时发送或接收,每个数据位使用一条导线。 串行通信:指数据一位接一位地顺序发送或接收。 串行通信有 SPI、IC、UART 等多种,最常见最通用的是指 UART,大多数情况下,串口通信指的就是 UART。 串行通信的制式有:单工、半双工、全双工三种。 RS485 总线是半双工的通信制式。 串行通信的主要方式有两种:同步和异步 同步串行通信:需要使用同一个时钟,以数据块为单位传送数据。 异步串行通信:每个设备都有自己的时钟信号,通信中双方的波特率要保 持一致,以字符为单位进行数据帧传送,一次传送一个帧。

UART 口的数据发送与接收

串行口中有两个缓冲寄存器 SBUF,一个是发送寄存器,一个是接收寄存器,在物理结构上是完全独立的。它们都是字节寻址的寄存器,字节地址均为 99H 。 这个重叠的地址靠读写指令区分: 串行发送时,CPU 向 SBUF 写入数据,此时99H表示发送缓存 SBUF 。 串行接收时,CPU 从 SBUF 读出数据,此时 99H 表示接收缓存 SBUF 。 数据发送,把数据扔进 SBUF 后,内核会自动将数据发送出去, 内容发生完成后,会将 TI 标志位置 1 。 发送数据程序:SBUF=数据/变量; 如:SBUF=0x58; 数据接收,内核从串口接收到一个完整的数据后,会将 RI 标志位置 1,用户用 SBUF 直接读取即可。 接收数据程序:变量=SBUF; 如:dat=SBUF;

串口控制寄存器 SCON

  • 异步8位UART并且允许接收:SCON=0x50;
  • 对于IAP15F2K61S2单片机,你还要对辅助寄存器 AUXR(0x8e) 进行设置。

练习题目

利用单片机的串行接口与上位机建立传输信道进行数据的收发。采用 8 位的 UART 模式,即模式 1,波特率为 9600 BPS。 数据发送采用查询方式,数据接收采用中断方式。 系统上电初始化之后,单片机向上位机发送两个字节:0x51 和 0xa5, 然后等待接收上位机的数据,每接收到一个字节后,在该字节的基础上加 1 然后返回给上位机。

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
#include "STC15F2K60S2.H"

//sfr AUXR = 0x8e; //当前头文件已经定义该辅助寄存器,不需要重复定义

sbit HC138_A = P2^5;
sbit HC138_B = P2^6;
sbit HC138_C = P2^7;

void Send(unsigned char dat);

void HC138(unsigned char n)
{
switch(n)
{
case 4:
HC138_C = 1;
HC138_B = 0;
HC138_A = 0;
break;

case 5:
HC138_C = 1;
HC138_B = 0;
HC138_A = 1;
break;
}
}

void InitSystem()
{
HC138(4);
P0 = 0xff;
HC138(5);
P0 = 0x00;
}

void InitUart()
{
TMOD = 0x20;
TH1 = 0xfd;
TL1 = 0xfd;
TR1 = 1; //启动定时器1

SCON = 0x50; //串口控制寄存器
AUXR = 0x00;

ES = 1; //串行中断允许控制位
EA = 1; //打开中断总开关
}

unsigned char dat;
void ServiceUart() interrupt 4
{
if(RI == 1) //如果标志位RI为1,则表示接收到数据
{
RI = 0; //手动将标志位RI归零
dat = SBUF;
Send(dat + 1);
}
}

void Send(unsigned char dat)
{
SBUF = dat;
while(TI == 0);//数据发送出去后会单片机会将标志位TI置1,此处循环等待TI变为1(即发送完成)
TI = 0; //标志位TI变为1后需要手动归零
}

void main()
{
InitSystem();
InitUart();
Send(0x5a);
Send(0xa5);
while(1)
{

}
}

串口通信进阶应用案例解析

利用单片机的串行接口与上位机建 立传数据输信道。 采用 8 位的 UART 模式,即模式 1,波特率为 9600BPS 。 数据发送采用查询方式,数据接收采用中断方式。

  1. 系统上电初始化之后,关闭蜂鸣器和继电器等无关设备,并向上位机发 送字符串:“Welcome to XMF system!”,回车换行。
  2. 上位机通过串口发送单字节命令可以控制下位机的 8 个 LED 灯开关。
  3. 上位机通过串口发送单字节命令可以读取下位机运行信息。
  4. 通信规约如下表:


蓝桥杯大赛-单片机设计与开发基础技能小蜜蜂课程学习笔记
https://blog.tddt.cc/posts/lanqiao-bee.html
作者
TechPANG
发布于
2022年2月24日
许可协议