51单片机

LED点亮

P2=0xFE;操纵LED的亮灭,后面两个字母是16进制,换为1111 1110
1代表灭,0代表亮

闪烁

完整代码如下

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
#include <REGX52.H>
#include <INTRINS.H>
void Delay500ms() //@12.000MHz
{
unsigned char i, j, k;

_nop_();
i = 4;
j = 205;
k = 187;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{

while(1)
{
P2=0x55;
Delay500ms();
P2=0xAA;
Delay500ms();
}
}

直接在stc-isp中找到软件延时计算器直接生成delay即可.
使用nop函数时要加上<INTRINS.H>头文件,nop函数为空函数,运行时间大约1μ\mus.

流水灯在while函数内多复制几个即可

延时函数的改进:可以通过形参传入实现更加灵活的delay

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Delay1ms(unsigned int t)		//@12.000MHz
{
unsigned char i, j;
while(t)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
t--;
}

}

按键控制

单独控制一个LED:在REGX52.H头文件中,可以通过P2_X来操控单个灯

非零值
0
1
2
P2_0=0; \\light0 on
P3_0=0; \\按键按下

数据运算:
单独的if(P3_1==0)P2_0~P2_0就已经满足要求了,但是在单片机开发中,往往会加一些防抖代码来防止点一次触发多次,简单的delay是一种思路.
按键防抖:

  1. 硬件设计时防抖
  2. 软件防抖 ```
1
2
3
4
5
6
if(P3_0=0){
delay(20);
while(P3_0=0);
delay(20);
P2_0=~P2_0;
}

实现LED二进制递增效果(3-3):

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

void Delay1ms(unsigned int t) //@12.000MHz
{
unsigned char i, j;
while(t)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
t--;
}
}

void main()
{
int num = 0;
while(1)
{
if(P3_0 == 0)
{
Delay1ms(20);
while(P3_0 == 0);
Delay1ms(20);
num++;
P2 = ~num;
}
}
}

实现LED移位(3-4):

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
void main()
{
int num = 0;
P2=~0x01;
while(1)
{
if(P3_0 == 0)
{
Delay1ms(20);
while(P3_0 == 0);
Delay1ms(20);
num++;
if(num==8)num=0;
P2 = ~(0x01<<num);
}
if(P3_1 == 0)
{
Delay1ms(20);
while(P3_1 == 0);
Delay1ms(20);
if(num==0)num=7;
num--;
P2 = ~(0x01>>num);
}
}
}

数码管

数码管可以看作是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
#include <REGX52.H>
unsigned char NixieTable[10]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
void Nixie(unsigned char Location, Number)
{
switch (Location)
{
case 1:P2_4=1;P2_3=1;P2_2=1;break;
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
P0=NixieTable[Number];
}

void main()
{
Nixie(2,3);
while(1)
{

}
}

这行代码实现了在第二个数码管显示为3的功能.NixieTable储存了数码管的输出代码
P0为数码管输出.P2的234控制哪个数码管,只能是234

动态数码管显示(可以显示多个):

1
2
3
4
5
6
7
8
9
void main()
{
while(1)
{
Nixie(1,1);
Nixie(2,2);
Nixie(3,3);
}
}

原理是视觉暂留,这样才能同时显示多个数字.
但是此时运行会发现,不仅显示了123,残影影响十分严重,对Nixie函数改进,可以达到目的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Nixie(unsigned char Location, Number)
{
switch (Location)
{
case 1:P2_4=1;P2_3=1;P2_2=1;break;
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
P0=NixieTable[Number];
delay(1);
P0=0x00;
}

我们使用的是单片机直接扫描

模块化编程

之后如delay()函数等较为常用的函数可以放在文件夹,命名为delay.c,再加入delay.h预编译文件,之后直接在文件中#include <delay.h>即可
文件delay.h:

1
2
3
4
#ifndef _DELAY_H__
#define _DELAY_H__
void delay(unsigned int x);
#endif

  • C语言的预编译以 # 开头,作用是在真正的编译开始之前,对代码做一些处理(预编译)
预编译 意义
#include <REGX52.H> REGX52.H 文件的内容搬到此处
#define PI 3.14 定义 PI,将 PI 替换为 3.14
#define ABC 定义 ABC
#ifndef __XX_H__ 如果没有定义 __XX_H__
#endif #ifndef#ifdef 匹配,组成“括号”

此外还有 #ifdef#if#else#elif#undef

示例:REGX52.H头文件

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
/*--------------------------------------------------------------------------
AT89X52.H

Header file for the low voltage Flash Atmel AT89C52 and AT89LV52.
Copyright (c) 1988-2002 Keil Elektronik GmbH and Keil Software, Inc.
All rights reserved.
--------------------------------------------------------------------------*/

#ifndef __AT89X52_H__
#define __AT89X52_H__

/*------------------------------------------------
Byte Registers
------------------------------------------------*/
sfr P0 = 0x80;
sfr SP = 0x81;
sfr DPL = 0x82;
sfr DPH = 0x83;
sfr PCON = 0x87;
sfr TCON = 0x88;
sfr TMOD = 0x89;
sfr TL0 = 0x8A;
sfr TL1 = 0x8B;
sfr TH0 = 0x8C;
sfr TH1 = 0x8D;
sfr P1 = 0x90;
sfr SCON = 0x98;
sfr SBUF = 0x99;
sfr P2 = 0xA0;
sfr IE = 0xA8;
sfr P3 = 0xB0;
sfr IP = 0xB8;
sfr T2CON = 0xC8;
sfr T2MOD = 0xC9;
sfr RCAP2L = 0xCA;
sfr RCAP2H = 0xCB;
sfr TL2 = 0xCC;
sfr TH2 = 0xCD;
sfr PSW = 0xD0;
sfr ACC = 0xE0;
sfr B = 0xF0;

/*------------------------------------------------
P0 Bit Registers
------------------------------------------------*/
sbit P0_0 = 0x80;
sbit P0_1 = 0x81;
sbit P0_2 = 0x82;
sbit P0_3 = 0x83;
sbit P0_4 = 0x84;
sbit P0_5 = 0x85;
sbit P0_6 = 0x86;
sbit P0_7 = 0x87;

/*------------------------------------------------
PCON Bit Values
------------------------------------------------*/
#define IDL_ 0x01

#define STOP_ 0x02
#define PD_ 0x02 /* Alternate definition */

#define GF0_ 0x04
#define GF1_ 0x08
#define SMOD_ 0x80

/*------------------------------------------------
TCON Bit Registers
------------------------------------------------*/
sbit IT0 = 0x88;
sbit IE0 = 0x89;
sbit IT1 = 0x8A;
sbit IE1 = 0x8B;
sbit TR0 = 0x8C;
sbit TF0 = 0x8D;
sbit TR1 = 0x8E;
sbit TF1 = 0x8F;

/*------------------------------------------------
TMOD Bit Values
------------------------------------------------*/
#define T0_M0_ 0x01
#define T0_M1_ 0x02
#define T0_CT_ 0x04
#define T0_GATE_ 0x08
#define T1_M0_ 0x10
#define T1_M1_ 0x20
#define T1_CT_ 0x40
#define T1_GATE_ 0x80

#define T1_MASK_ 0xF0
#define T0_MASK_ 0x0F

/*------------------------------------------------
P1 Bit Registers
------------------------------------------------*/
sbit P1_0 = 0x90;
sbit P1_1 = 0x91;
sbit P1_2 = 0x92;
sbit P1_3 = 0x93;
sbit P1_4 = 0x94;
sbit P1_5 = 0x95;
sbit P1_6 = 0x96;
sbit P1_7 = 0x97;

sbit T2 = 0x90; /* External input to Timer/Counter 2, clock out */
sbit T2EX = 0x91; /* Timer/Counter 2 capture/reload trigger & dir ctl */

/*------------------------------------------------
SCON Bit Registers
------------------------------------------------*/
sbit RI = 0x98;
sbit TI = 0x99;
sbit RB8 = 0x9A;
sbit TB8 = 0x9B;
sbit REN = 0x9C;
sbit SM2 = 0x9D;
sbit SM1 = 0x9E;
sbit SM0 = 0x9F;

/*------------------------------------------------
P2 Bit Registers
------------------------------------------------*/
sbit P2_0 = 0xA0;
sbit P2_1 = 0xA1;
sbit P2_2 = 0xA2;
sbit P2_3 = 0xA3;
sbit P2_4 = 0xA4;
sbit P2_5 = 0xA5;
sbit P2_6 = 0xA6;
sbit P2_7 = 0xA7;

/*------------------------------------------------
IE Bit Registers
------------------------------------------------*/
sbit EX0 = 0xA8; /* 1=Enable External interrupt 0 */
sbit ET0 = 0xA9; /* 1=Enable Timer 0 interrupt */
sbit EX1 = 0xAA; /* 1=Enable External interrupt 1 */
sbit ET1 = 0xAB; /* 1=Enable Timer 1 interrupt */
sbit ES = 0xAC; /* 1=Enable Serial port interrupt */
sbit ET2 = 0xAD; /* 1=Enable Timer 2 interrupt */

sbit EA = 0xAF; /* 0=Disable all interrupts */

/*------------------------------------------------
P3 Bit Registers (Mnemonics & Ports)
------------------------------------------------*/
sbit P3_0 = 0xB0;
sbit P3_1 = 0xB1;
sbit P3_2 = 0xB2;
sbit P3_3 = 0xB3;
sbit P3_4 = 0xB4;
sbit P3_5 = 0xB5;
sbit P3_6 = 0xB6;
sbit P3_7 = 0xB7;

sbit RXD = 0xB0; /* Serial data input */
sbit TXD = 0xB1; /* Serial data output */
sbit INT0 = 0xB2; /* External interrupt 0 */
sbit INT1 = 0xB3; /* External interrupt 1 */
sbit T0 = 0xB4; /* Timer 0 external input */
sbit T1 = 0xB5; /* Timer 1 external input */
sbit WR = 0xB6; /* External data memory write strobe */
sbit RD = 0xB7; /* External data memory read strobe */

/*------------------------------------------------
IP Bit Registers
------------------------------------------------*/
sbit PX0 = 0xB8;
sbit PT0 = 0xB9;
sbit PX1 = 0xBA;
sbit PT1 = 0xBB;
sbit PS = 0xBC;
sbit PT2 = 0xBD;

/*------------------------------------------------
T2CON Bit Registers
------------------------------------------------*/
sbit CP_RL2= 0xC8; /* 0=Reload, 1=Capture select */
sbit C_T2 = 0xC9; /* 0=Timer, 1=Counter */
sbit TR2 = 0xCA; /* 0=Stop timer, 1=Start timer */
sbit EXEN2= 0xCB; /* Timer 2 external enable */
sbit TCLK = 0xCC; /* 0=Serial clock uses Timer 1 overflow, 1=Timer 2 */
sbit RCLK = 0xCD; /* 0=Serial clock uses Timer 1 overflow, 1=Timer 2 */
sbit EXF2 = 0xCE; /* Timer 2 external flag */
sbit TF2 = 0xCF; /* Timer 2 overflow flag */

/*------------------------------------------------
T2MOD Bit Values
------------------------------------------------*/
#define DCEN_ 0x01 /* 1=Timer 2 can be configured as up/down counter */
#define T2OE_ 0x02 /* Timer 2 output enable */

/*------------------------------------------------
PSW Bit Registers
------------------------------------------------*/
sbit P = 0xD0;
sbit F1 = 0xD1;
sbit OV = 0xD2;
sbit RS0 = 0xD3;
sbit RS1 = 0xD4;
sbit F0 = 0xD5;
sbit AC = 0xD6;
sbit CY = 0xD7;

/*------------------------------------------------
Interrupt Vectors:
Interrupt Address = (Number * 8) + 3
------------------------------------------------*/
#define IE0_VECTOR 0 /* 0x03 External Interrupt 0 */
#define TF0_VECTOR 1 /* 0x0B Timer 0 */
#define IE1_VECTOR 2 /* 0x13 External Interrupt 1 */
#define TF1_VECTOR 3 /* 0x1B Timer 1 */
#define SIO_VECTOR 4 /* 0x23 Serial port */

#define TF2_VECTOR 5 /* 0x2B Timer 2 */
#define EX2_VECTOR 5 /* 0x2B External Interrupt 2 */

#endif

LCD1602

LCD1602是单片机的液晶屏,用于在单片机上的液晶屏上的调试,在Keil 5加入LCD1602的.c和.h文件后,打头文件即可使用里面的函数,具体说明在.c文件中.注意头文件是自己加的时用冒号代替<>.
有些函数有四个变量,分别是行,列,目标,长度,具体可以看lcd1602头文件.

函数 意义
LCD_Init(); 初始化
LCD_ShowChar(1,1,‘A’); 显示一个字符
LCD_ShowString(1,3,“Hello”); 显示字符串
LCD_ShowNum(1,9,123,3); 显示十进制数字
LCD_ShowSignedNum(1,13,-66,2); 显示有符号十进制数字
LCD_ShowHexNum(2,1,0xA8,2); 显示十六进制数字
LCD_ShowBinNum(2,4,0xAA,8); 显示二进制数字

键盘扫描

51单片机的矩阵键盘如图所示

实现显示按键数的功能

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

void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
unsigned char MatrixKey()
{
unsigned char KeyNumber=0;

P1=0xFF;
P1_3=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}

P1=0xFF;
P1_2=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}

P1=0xFF;
P1_1=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}

P1=0xFF;
P1_0=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}

return KeyNumber;
}
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Key:");
while(1)
{
int key=MatrixKey();
if(key)LCD_ShowNum(2,1,key,2);
}
}

其中原理是键盘扫描,首先,将P1口设置为全高电平 (0xFF),然后将P1_3(对应第一列)设为低电平(0),以激活这一列。然后检查对应行的引脚(P1_7, P1_6, P1_5, P1_4),如果某一列的引脚为低电平,则表示该列上的按键被按下,记录按键编号为1、5、9、13。
密码锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Key:");
sum=0,key=0,time=1;
while(time<=4)
{
key=MatrixKey();
if(!key)continue;
if(key<10){sum=sum*10+key;LCD_ShowNum(2,1,sum,6);time++;}
else if(key==10){sum*=10;LCD_ShowNum(2,1,sum,6);time++;}
else if(key==11){sum/=10;LCD_ShowNum(2,1,sum,6);time--;}
}
key=0;
while(key-16)key=MatrixKey();
LCD_Init();
if(sum==4587)LCD_ShowString(2,1,"Right");
else LCD_ShowString(2,1,"Wrong");
while(1){}
}

这个代码实现了输入4位数字后不再变化,而且按下四行四列按键时显示结果.

总结:学习这一课,说明了单片机开发本质为代码给单片机发指令,比如检测时就通过设置高低电平来判断时哪个数字,key=MatrixKey();这行代码在循环体内运行时会返回大量的0,直至不返回0时才有下一步的操作.

定时器

在制作LED流水灯时,我们使用的是delay函数,这其实是一种很笨的方法,而且使得按键变得不灵敏,这一节我们会通过定时器实现这个效果.


配置寄存器:

可位寻址:可以对一个单独的寄存器赋值.
不可位寻址:必须整个一行赋值.

有这些信息,看电路图,我们可以写一个初始化函数

1
2
3
4
5
6
7
8
9
10
11
12
void timer0_init()
{
TMOD = 0x01; // 设置计时模式, 选择模式1(16位定时/计数模式)
TF0 = 0; // 清除定时器0溢出标志位
TR0 = 1; // 启动定时器0
TH0 = 0xFF; // 设置定时器0的高8位寄存器TH0
TL0 = 0xFF; // 设置定时器0的低8位寄存器TL0,在16位计时模式下,最大计数值为65536,设置FF FF为初始值
// 开始设置中断
ET0 = 1; // 允许定时器0中断
EA = 1; // 开启总中断
PT0 = 0; // 设置定时器0中断优先级为低(可选配置)
}

![[Pasted image 20240821071854.png]]
写一个中断函数:

1
2
3
4
5
6
7
8
9
10
11
12
unsigned int time=0;
void timer_interrupt() interrupt 1//中断模式1
{
TH0=0xFF;
TL0=0xFF;//复位
time++;
if(time>=1000)
{
time=0;
P2_1=~P2_1;
}
}

发现2号灯亮了,说明计时中断了,下面写一个程序实现1ms计时一次而不是1us,并改进TMOD赋值后面四位时为0001的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
void timer0_init()
{
TMOD &= 0xF0;
TMOD |= 0x01;// 设置计时模式, 选择模式1(16位定时/计数模式)
TF0 = 0; // 清除定时器0溢出标志位
TR0 = 1; // 启动定时器0
TH0 = 0x18; // 设置定时器0的高8位寄存器TH0
TL0 = 0xFC; // 设置定时器0的低8位寄存器TL0,在16位计时模式下,最大计数值为65536,设置FF FF为初始值
// 开始设置中断
ET0 = 1; // 允许定时器0中断
EA = 1; // 开启总中断
PT0 = 0; // 设置定时器0中断优先级为低(可选配置)
}

在中断函数中定义time可以是static unsigned time=0;
用计时器功能实现流水灯,引入头文件实现cror,可以不断左移u,移出去在右面补上.
完整代码如下:

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
#include <REGX52.H>
#include <INTRINS.H>
#include "LCD1602.h"
unsigned int sum,key,time;
unsigned int keynum,ledmode=1;
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
void timer0_init()
{
TMOD &= 0xF0;
TMOD |= 0x01;
TF0 = 0;
TR0 = 1;
TH0 = 0xFC;
TL0 = 0x18;
ET0 = 1;
EA = 1;
PT0 = 0;
}

void timer_interrupt() interrupt 1
{
static unsigned int time=0;
TH0=0xFC;
TL0=0x18;
time++;
if(time>=500)
{
time=0;
if(ledmode==1)P2=_crol_(P2,1);
if(ledmode==0)P2=_cror_(P2,1);
}
}
unsigned char Keyboard()
{
unsigned char KeyNumber=0;

if(P3_1 == 0) { Delay(20); while(P3_1 == 0); Delay(20); KeyNumber = 1; }
if(P3_0 == 0) { Delay(20); while(P3_0 == 0); Delay(20); KeyNumber = 2; }
if(P3_2 == 0) { Delay(20); while(P3_2 == 0); Delay(20); KeyNumber = 3; }
if(P3_3 == 0) { Delay(20); while(P3_3 == 0); Delay(20); KeyNumber = 4; }

return KeyNumber;
}

void main()
{
P2=0xF7;
timer0_init();
while(1)
{
keynum=Keyboard();
if(keynum)
{
if(keynum==1)ledmode=0;
else if(keynum==2)ledmode=1;
}
}
}

51单片机
https://miao62.github.io/2024/08/01/51单片机/
Author
Miao
Posted on
August 1, 2024
Updated on
August 21, 2024
Licensed under