51单片机的学习

一、单片机的介绍

1.1

1.2 如何使用软件

首先打开Keil 5软件,点击上面的project。

在下拉的菜单中选择New μ\muVision Project…

然后新增文件夹在桌面,例如我这里是my_51project,然后再新建一个文件名为2_1 light LED在我们的my_51project里面,最后可以将名为Project的文件建立在我们的2_1 light LED中,这是我们要写的第一个工程。

然后建立后点击保存,会出现下面的画面。

我们在Search栏中搜索AT89C52/AT89C51,点击OK,然后出现对话框

可以点击否,然后就可以看到我们的工程了,在工程下面有一个文件Target 1,在其里面有一个文件夹Source Group 1,这个就是我们写代码的地方。

来到文件夹Source Group 1,右键点击,选择Add New Item to Group 'Source Group 1'...

选择C语言文件,将名字设为main,点击Add添加。

编写代码前,可以修改一下代码的字体大小

选择第二个选项Color & Fonts

Window栏中选择C/C++ Editor files

点击右边的Courier New...

Size栏中选择自己喜欢的大小就行了,这里推荐16。

写代码前,注意要右键点击代码第一行,选择Insert '#include <REGX52.H>'

然后可以在下面写2.1的代码了,具体代码解析需要在[2.1](###2.1 点亮第一个LED)去看。

1
2
3
4
5
6
7
#include <REGX52.H>

void main(){
while(1) {
P2 = 0xFE; // 1111 1110
}
}

写完代码后记得点击编译,就是下图中三个图标里面的中间那个。

为了生成可以下载到单片机里面的hex文件,需要点击Options for Target...图标

打开该窗口后选择Output

勾选上内容Create HEX File这个选项

重新编译,可以看到下面提示已经创建好hex文件了

然后用USB连接好51单片机和电脑,打开STC-ISP软件,选择好单片机型号,安装好驱动软件选择好串口号,打开正确的程序文件,点击“下载/编程”,当软件右下半部分显示“正在检测目标单片机…”时,重新关闭并打开单片机的总电源,就可以将程序烧录进去了。

二、LED的学习

LED相关的原理图

2.1 点亮第一个LED

实现代码:

1
2
3
4
5
6
7
#include <REGX52.H>

void main(){
while(1) {
P2 = 0xFE; // 1111 1110
}
}
  • 如果不设置循环,单片机会在跑完main函数之后重新不断地继续跑main函数,所以为了方便或者其他原因,我们可以在main函数里面设置一个while的死循环。
  • 上面代码中,P2的表达方式是用两位十六进制,所以前面要加上0x
  • P2代表的是单片机的8个LED灯,用两位十六进制表示,为0时对应的位置亮灯,为1时不亮。

高级代码:

1
2
3
4
5
6
7
8
9
10
#include <REGX52.H>

sbit LED1 = P2^0;

void mian() {
LED1 = 0;
while(1) {

}
}
  • 在第3行代码中,sbit 用于特殊功能寄存器中可位寻址的位地址。类似于C语言中的宏定义,对选定位地址进行某特殊功能的命名。 格式为:sbit 命名功能 = 位地址
  • sbit使用相似的还有strsfr 定义特殊功能寄存器中的字节。类似于C语言中的宏定义,对选定字节地址进行某特殊功能的命名。 格式为:sfr 功能命名 = 地址(位地址首位)
  • 上面的代码对比前面的代码中,一开始就定义了LED1的位置,只需要定义变量一次,然后让程序一直进入死循环即可。可读性高,更加优雅。

2.2 LED闪烁

实现目的:让一个LED以1s为一周期闪烁

首先需要去寻找可用的延时函数,你可以打开STC-ISP软件,在右上部分右划到软件延时计算器部分,选择合适的系统频率(晶振的频率)以及适合芯片使用的8051指令集,最后选择需要的定时长度,然后点击下面的生成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
#include <REX52.H>
#include <INTRINS.H>

void Delay500ms()
{
unsigned char i, j, k;

_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}

void main()
{
while(1)
{
P2 = 0xFE;
Delay500ms();
P2 = 0xFF;
Delay500ms();
}

}
  • 通过软件复制代码得到延时函数。
  • 利用延时函数达到亮灭的效果。

高级代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <REGX52.H>

typedef unsigned int u16;
typedef unsigned char u8;

sbit LED1 = P2^0;

void delay_10us(u16 ten_us)
{
while(ten_us--);
}


void main() {
while(1){
LED1 = 0;
delay_10us(50000);
LED1 = 1;
delay_10us(50000);
}
}
  • 第3行的typedef unsigned int u16或者第4行typedef unsigned char u8,将变量名修改为u16u8,即u16可以当unsigned int使用、u8可以当unsigned char使用。
  • 第6行的sbit LED1 = P2^0是将LED1定义为管脚P2的0号管脚(第一个管脚,也是第一个led灯)的值,后面就可以使用LED1,给LED1定义值相当于给P2的第0位的值定义。

2.3 LED流水灯

实现目的:使得8个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
#include <REGX52.H>
#include <INTRINS.H>

void Delay500ms()
{
unsigned char i, j, k;

_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}

void main()
{
while(1)
{
P2 = 0xFE; // 1111 1110
Delay500ms();
P2 = 0xFD; // 1111 1101
Delay500ms();
P2 = 0xFB; // 1111 1011
Delay500ms();
P2 = 0xF7; // 1111 0111
Delay500ms();
P2 = 0xEF; // 1110 1111
Delay500ms();
P2 = 0xDF; // 1101 1111
Delay500ms();
P2 = 0xBF; // 1011 1111
Delay500ms();
P2 = 0x7F; // 0111 1111
Delay500ms();
}
}
  • 通过不断定义P2的值,可以使得8个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
#include "reg52.h"
#include "intrins.h"

typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;

#define LED_PORT P2 //使用宏定义P2端口


void delay_10us(u16 ten_us)
{
while(ten_us--);
}


void main()
{
u8 i=0;

LED_PORT=~0x01;
delay_10us(50000);
while(1)
{
//方法1:使用移位+循环实现流水灯
// for(i=0;i<8;i++)
// {
// LED_PORT=~(0x01<<i); //将1右移i位,然后取反将结果赋值到LED_PORT
// delay_10us(50000);
// }

//方法2:使用循环+_crol_或_cror_函数实现流水灯
for(i=0;i<7;i++) //将led左移一位
{
LED_PORT=_crol_(LED_PORT,1);
delay_10us(50000);
}
for(i=0;i<7;i++) //将led右移一位
{
LED_PORT=_cror_(LED_PORT,1);
delay_10us(50000);
}
}
}

  • 高级代码中使用了文件intrins.hINTRINS.H),来对流水灯进行移位,其中使用的函数分别为_crol__cror_,输入参数有两个,第一个为串口地址,第二个为需要左移或者右移的位数,其功能就是实现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
#include "reg52.h"
#include "intrins.h"

typedef unsigned int u16;
typedef unsigned char u8;

#define LED_PORT P2

void delay_10us(u16 ten_us)
{
while(ten_us--);
}
void main()
{
u8 i=0;
LED_PORT=~0x01;
delay_10us(50000);
while(1)
{
for(i=0;i<7;i++)
{
LED_PORT=_crol_(LED_PORT,1);
delay_10us(50000);
}
for(i=0;i<7;i++)
{
LED_PORT=_cror_(LED_PORT,1);
delay_10us(50000);
}
}
}

三、独立按键

与独立按键所相关的原理图

3.1 独立按键控制LED亮灭

实现功能:左边第一个按键控制一个LED灯的亮灭。

1
2
3
4
5
6
7
8
9
10
11
#include <REGX52.H>

void main() {
while (1) {
if (P3_1 == 0) {
P2_0 = 0;
} else {
P2_0 = 1;
}
}
}
  • 当按键按下时,其引脚返回的电平值为0;
  • LED灯为低电平亮灯。

高级代码:

实现功能:按下“独立按键”模块中K1键,控制D1指示灯亮灭

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

typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;

//定义独立按键控制脚
sbit KEY1=P3^1;
sbit KEY2=P3^0;
sbit KEY3=P3^2;
sbit KEY4=P3^3;

//定义LED1控制脚
sbit LED1=P2^0;

//使用宏定义独立按键按下的键值
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
#define KEY4_PRESS 4
#define KEY_UNPRESS 0

void delay_10us(u16 ten_us)
{
while(ten_us--);
}

u8 key_scan(u8 mode)
{
static u8 key=1;

if(mode)key=1;//连续扫描按键
if(key==1&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//任意按键按下
{
delay_10us(1000);//消抖
key=0;
if(KEY1==0)
return KEY1_PRESS;
else if(KEY2==0)
return KEY2_PRESS;
else if(KEY3==0)
return KEY3_PRESS;
else if(KEY4==0)
return KEY4_PRESS;
}
else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1) //无按键按下
{
key=1;
}
return KEY_UNPRESS;
}

void main()
{
u8 key=0;

while(1)
{
key=key_scan(0);
if(key==KEY1_PRESS)//检测按键K1是否按下
LED1=!LED1;//LED1状态翻转
}
}

  • 在前面重定义了默认数据类型,将unsigned char定义为u8,将unsigned int定义为u16,更加方便简洁清晰。
  • 分别定义了独立按键的控制脚和LED控制脚,注意对比原理图和控制脚的位置,独立按键的四个控制脚上面的管脚数字顺序并不是有顺序的。
  • 利用宏定义确定了返回数据值,这样使得代码更加易读。
  • 按键扫描函数key_scan:检测独立按键是否按下,按下则返回对应键值。只有一个输入参数mode, mode=0:单次扫描按键,mode=1:连续扫描按键。

3.2 独立按键控制LED状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <REGX52.H>

void delay_10us(u16 ten_us)
{
while(ten_us--);
}

void main() {
while (1) {
if (P3_1 == 0) {
delay_10us(2000);
while (P3_1 == 0) {
P2_0 = 0;
}
delay_10us(2000);
P2_2 = ~P2_2;
}
}
}
  • 由于LED的引脚输入低电平的时候会亮灯,所以只要按键按下后,经过20ms的消抖,如果按键依旧是按下的状态,那么对应的LED就会亮灯,如果松开,则会在延迟20ms之后灭灯。

3.3 独立按键控制LED显示二进制

实现功能:按键松开会使得所有的LED灯进行二进制变换亮灯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<REGX52.H>

void delay_10us(u16 ten_us)
{
while(ten_us--);
}

void main() {
unsigned char LED_num = 0;
while (1) {
if (P3_1 == 0) {
delay_10us(2000);
while (P3_1 == 0) ;
delay_10us(2000);
LED_num++;
P2 = ~LED_num;
}
}
}
  • 与前面不同的是,这里的LED灯会进行二进制的变换。
  • 不同的是,在while语句里面并没有操作,而是在延迟后才有操作,这样使得在按键抬起才会实现需要的功能。
  • 但是这种方法也有弊端,那就是这种代码只要P3_1一旦处于低电平状态,后面功能就会触发。而前面[3.2](###3.2 独立按键控制LED状态)中需要在P3_1在处于低电平的触发状态后20ms依旧还是低电平,这样才会触发功能。

3.4 独立按键控制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
#include<REGX52.H>

void delay_10us(u16 ten_us)
{
while(ten_us--);
}

void main() {
unsigned char LED_num = 0;
while (1) {
if (P3_1 == 0) {
delay_10us(2000);
while (P3_1 == 0) ;
delay_10us(2000);
LED_num++;
if (LED_num >= 8) {
LED_num = 0;
}
P2 = ~(0x01 << LED_num);
}

if (P3_0 == 0) {
delay_10us(2000);
while (P3_1 == 0) ;
delay_10us(2000);
LED_num++;
if (LED_num == 0) {
LED_num = 7;
} else {
LED_num--;
}
P2 = ~(0x01 << LED_num);
}
}
}
  • 通过控制LED_num的数字,来确定P2移位的数量。
  • 由于P2是低电平亮灯,所以需要进行取反,使得只有一个数字为零,即一个灯亮。
  • 通过两个if语句判断来实现两个按键不同的功能,一个进行右移,一个进行左移,而通过控制按键的响应为移位的数字的加减来达到左右移的目的。

四、数码管显示

与七段数码管所相关的原理图

  • 上图中,控制8个LED亮灭的是71LS138芯片的引脚P2_2、P2_3、P2_4,按照二进制来控制,即引脚P2_2、P2_3、P2_4组合成为的一个3位二进制数将对需要显示的LED进行选择。例如,如果引脚P2_2、P2_3、P2_4分别为010,则会使得后面的8位二进制数控制LED3。
  • 上图中,动态数码管模块的左边P0_1~P0_7八个引脚就是控制LED亮灭的8位二进制数,高电平亮灯,芯片74HC245的功能是管脚对应进行增加驱动。

4.1 静态数码管显示

点亮一个静态数码管LED6,使其显示6

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

unsigned char NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};

void delay_10us(u16 ten_us)
{
while(ten_us--);
}

void light(unsigned char location, unsigned char number) {
switch(location) {
case 1: P2_4=0; P2_3=0; P2_2=0; break;
case 2: P2_4=0; P2_3=0; P2_2=1; break;
case 3: P2_4=0; P2_3=1; P2_2=0; break;
case 4: P2_4=0; P2_3=1; P2_2=1; break;
case 5: P2_4=1; P2_3=0; P2_2=0; break;
case 6: P2_4=1; P2_3=0; P2_2=1; break;
case 7: P2_4=1; P2_3=1; P2_2=0; break;
case 8: P2_4=1; P2_3=1; P2_2=1; break;
}
P0 = NixieTable[number];
}


void main() {
light(6, 6);
while(1) {}
}
  • 通过一个函数进行实现。
  • 函数里面的case是选择哪个LED进行亮灯
  • 前面定义的数组NixieTable是数码管数字显示的阳码,一共十个数字,对应的下标里面的内容如果赋值到P0则会显示对应下标的数字

4.2 动态数码管显示

实现功能:从左往右第一、二、三个数码管动态显示数字456。

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

unsigned char NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};

void delay_10us(u16 ten_us)
{
while(ten_us--);
}

void light(unsigned char location, unsigned char number) {
switch(location) {
case 1: P2_4=0; P2_3=0; P2_2=0; break;
case 2: P2_4=0; P2_3=0; P2_2=1; break;
case 3: P2_4=0; P2_3=1; P2_2=0; break;
case 4: P2_4=0; P2_3=1; P2_2=1; break;
case 5: P2_4=1; P2_3=0; P2_2=0; break;
case 6: P2_4=1; P2_3=0; P2_2=1; break;
case 7: P2_4=1; P2_3=1; P2_2=0; break;
case 8: P2_4=1; P2_3=1; P2_2=1; break;
}
P0 = NixieTable[number];
delay_10us(100);
P0 = 0x00;
}

void main() {

while(1) {
light(6, 6);
// delay_10us(2000);
light(7, 5);
// delay_10us(2000);
light(8, 4);
// delay_10us(2000);
}
}
  • 与前面相比,只是修改了light函数,在后面增加了一下延迟而已。

高级代码:

实现功能:“数码管模块”显示01234567

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

typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;

#define SMG_A_DP_PORT P0 //使用宏定义数码管段码口

//定义数码管位选信号控制脚
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;

//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

void delay_10us(u16 ten_us)
{
while(ten_us--);
}

void smg_display(void)
{
u8 i=0;

for(i=0;i<8;i++)
{
switch(i)//位选
{
case 0: LSC=1;LSB=1;LSA=1;break;
case 1: LSC=1;LSB=1;LSA=0;break;
case 2: LSC=1;LSB=0;LSA=1;break;
case 3: LSC=1;LSB=0;LSA=0;break;
case 4: LSC=0;LSB=1;LSA=1;break;
case 5: LSC=0;LSB=1;LSA=0;break;
case 6: LSC=0;LSB=0;LSA=1;break;
case 7: LSC=0;LSB=0;LSA=0;break;
}
SMG_A_DP_PORT=gsmg_code[i]; //传送段选数据
delay_10us(100); //延时一段时间,等待显示稳定
SMG_A_DP_PORT=0x00; //消音
}
}

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

  • 实际上与普通代码无异,主要是体现在smg_display,必须要经历段选数据的传送、延时以及消音。可以通过增加smg_display的输入参数,使得函数的适用性更广。
  • 相比于普通的代码,这里的高级代码只是在前面定义了更多数据,增加了代码的易读性。

五、模块化编程

5.1自定义模版的固定开头

1
2
3
4
#ifndef __ _H__
#define

#endif

5.2 LCD1602模块代码

LCD1602.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
#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;

i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}

/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}

/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}

/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}

/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}

/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}

/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}

/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}

/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}

/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}

/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}

LCD1602.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

六、矩阵键盘按键

6.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
#include <regx51.h>
#include <INTRINS.H>

void Delay_1ms(unsigned char times) {
do{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do {
while(--j);
} while(--i);
} while(times--)
}

unsigned char MatrixKey() {
unsigned char KeyNumber = 0;

P1 = 0xFF;
P1_3 = 0;

if (P1_7 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 1;}
if (P1_6 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 5;}
if (P1_5 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 9;}
if (P1_3 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 13;}


P1 = 0xFF;
P1_2 = 0;

if (P1_7 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 2;}
if (P1_6 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 6;}
if (P1_5 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 10;}
if (P1_3 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 14;}


P1 = 0xFF;
P1_1 = 0;

if (P1_7 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 3;}
if (P1_6 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 7;}
if (P1_5 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 11;}
if (P1_3 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 15;}


P1 = 0xFF;
P1_0 = 0;

if (P1_7 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 4;}
if (P1_6 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 8;}
if (P1_5 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 12;}
if (P1_3 == 0) {Delay_1ms(20); while(P1_7 == 0); Delay_1ms(20); KeyNumber = 16;}


return KeyNumber;
}


void main() {
while(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
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
/**************************************************************************************
深圳市普中科技有限公司(PRECHIN 普中)
技术支持:www.prechin.net
PRECHIN
普中

实验名称:矩阵按键实验
接线说明:
实验现象:下载程序后,按下“矩阵按键”模块中S1-S16键,对应数码管最左边显示0-F
注意事项:
***************************************************************************************/
#include "reg52.h"

typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;

#define KEY_MATRIX_PORT P1 //使用宏定义矩阵按键控制口

#define SMG_A_DP_PORT P0 //使用宏定义数码管段码口

//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}

/*******************************************************************************
* 函 数 名 : key_matrix_ranks_scan
* 函数功能 : 使用行列式扫描方法,检测矩阵按键是否按下,按下则返回对应键值
* 输 入 : 无
* 输 出 : key_value:1-16,对应S1-S16键,
0:按键未按下
*******************************************************************************/
unsigned char key_matrix_ranks_scan(void)
{
unsigned char key_value=0;

KEY_MATRIX_PORT=0xf7;//给第一列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xf7)//判断第一列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第一列按键按下后的键值
{
case 0x77: key_value=1;break;
case 0xb7: key_value=5;break;
case 0xd7: key_value=9;break;
case 0xe7: key_value=13;break;
}
}
while(KEY_MATRIX_PORT!=0xf7);//等待按键松开

KEY_MATRIX_PORT=0xfb;//给第二列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xfb)//判断第二列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第二列按键按下后的键值
{
case 0x7b: key_value=2;break;
case 0xbb: key_value=6;break;
case 0xdb: key_value=10;break;
case 0xeb: key_value=14;break;
}
}
while(KEY_MATRIX_PORT!=0xfb);//等待按键松开

KEY_MATRIX_PORT=0xfd;//给第三列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xfd)//判断第三列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第三列按键按下后的键值
{
case 0x7d: key_value=3;break;
case 0xbd: key_value=7;break;
case 0xdd: key_value=11;break;
case 0xed: key_value=15;break;
}
}
while(KEY_MATRIX_PORT!=0xfd);//等待按键松开

KEY_MATRIX_PORT=0xfe;//给第四列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xfe)//判断第四列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第四列按键按下后的键值
{
case 0x7e: key_value=4;break;
case 0xbe: key_value=8;break;
case 0xde: key_value=12;break;
case 0xee: key_value=16;break;
}
}
while(KEY_MATRIX_PORT!=0xfe);//等待按键松开

return key_value;
}

/*******************************************************************************
* 函 数 名 : key_matrix_flip_scan
* 函数功能 : 使用线翻转扫描方法,检测矩阵按键是否按下,按下则返回对应键值
* 输 入 : 无
* 输 出 : key_value:1-16,对应S1-S16键,
0:按键未按下
*******************************************************************************/
u8 key_matrix_flip_scan(void)
{
static u8 key_value=0;

KEY_MATRIX_PORT=0x0f;//给所有行赋值0,列全为1
if(KEY_MATRIX_PORT!=0x0f)//判断按键是否按下
{
delay_10us(1000);//消抖
if(KEY_MATRIX_PORT!=0x0f)
{
//测试列
KEY_MATRIX_PORT=0x0f;
switch(KEY_MATRIX_PORT)//保存行为0,按键按下后的列值
{
case 0x07: key_value=1;break; // 0000 0111
case 0x0b: key_value=2;break; // 0000 1011
case 0x0d: key_value=3;break; // 0000 1101
case 0x0e: key_value=4;break; // 0000 1110
}
//测试行
KEY_MATRIX_PORT=0xf0;
switch(KEY_MATRIX_PORT)//保存列为0,按键按下后的键值
{
case 0x70: key_value=key_value;break; // 0111 0000
case 0xb0: key_value=key_value+4;break; // 1011 0000
case 0xd0: key_value=key_value+8;break; // 1101 0000
case 0xe0: key_value=key_value+12;break; // 1110 0000
}
while(KEY_MATRIX_PORT!=0xf0);//等待按键松开
}
}
else
key_value=0;

return key_value;
}

/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
u8 key=0;

while(1)
{
key=key_matrix_ranks_scan();
if(key!=0)
SMG_A_DP_PORT=gsmg_code[key-1];//得到的按键值减1换算成数组下标对应0-F段码
}
}

七、定时器

1、定时器0模版代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定时器0的初始化
void Timer0Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1; // ET0中断允许
EA=1; // 总中断开启
PT0=0; // 关闭优先级
}

// 定时器零的中断函数
void Timer0_Routine() interrupt 1
{

}

2、定时器1模版代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定时器1的初始化
void Timer1Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0x18; //设置定时初值
TH1 = 0xFC; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1=1; // 打开中断允许
EA=1; // 总中断开启
PT1=0; // 关闭优先级
}

// 定时器1的中断函数
void Timer1_Routine() interrupt 3
{

}

八、串口通信

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

void UART_Init(void) //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}

// 串口向电脑发送字节
void UART_SendByte(unsigned char byte) {
SBUF = byte;
while(TI == 0);
TI = 0;
}

// 串口中断,接受电脑的字节
void UART_Routine() interrupt 4
{
if (RI == 1) { // 如果是电脑接收中断
RI = 0; // 内部对RI软件复位
}

}

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

unsigned char Sec;

void UART_Init(void) //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}

void UART_SendByte(unsigned char byte) {
SBUF = byte;
while(TI == 0); // 等待发射中断有效
TI = 0; // 内部对TI进行软件复位
}

void delay_10us(unsigned int ten_us)
{
while(ten_us--);
}


void main() {
UART_Init();
UART_SendByte(0x11);
while(1) {
delay_10us(10000);
UART_SendByte(Sec++);

}
}

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

unsigned char Sec;

void UART_Init(void) //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
EA = 1;
ES = 1;
}

void UART_SendByte(unsigned char byte) {
SBUF = byte;
while(TI == 0);
TI = 0;
}

void UART_Routine() interrupt 4
{
if (RI == 1) { // 如果是电脑接收中断
P2 = ~SBUF;
UART_SendByte(SBUF);
RI = 0; // 内部对RI软件复位
}

}



void delay_10us(unsigned int ten_us)
{
while(ten_us--);
}


void main() {
UART_Init();
while(1) {

}
}

九、LED点阵屏

十、DS1302实时时钟

十一、蜂鸣器

十二、AT24C02(IC2总线)EFPROM

十三、DS18B20温度传感器

十四、LCD1602

十五、直流电机驱动(PWM)

十六、AD/DA转换

十七、红外控制