51开发板样例代码合集

学习51单片机开发板的代码样例备份一下。

LED相关控制

点亮一个LED

先来看一下LED部分和51单片机的电路连接,这个影响到我们代码应该怎么写,比如LED是共阴极,那你LED控制亮的话,要输出高电平,如果你LED共阳极,则需要输出低电平,
c51_LED_image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<8052.h>
// 0.01 * 100 = 1ms * 500
void delay_500ms(){
int us = 50000;
while(us--);
}
void main() {
// 1111 1110
P2 = 0xFE;
delay_500ms();
P2 = 0xFF;
delay_500ms();

}

单片机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
#include<8052.h>
// 0.01 * 100 = 1ms * 500
void delay_500ms(){
int us = 50000;
while(us--);
}
void main() {

P2 = 0xFE;// 1111 1110
delay_500ms();
P2 = 0xFD;// 1111 1101
delay_500ms();
P2 = 0xFB;// 1111 1011
delay_500ms();
P2 = 0xF7;// 1111 0111
delay_500ms();
P2 = 0xEF;// 1110 1111
delay_500ms();
P2 = 0xDF;// 1101 1111
delay_500ms();
P2 = 0xBF;// 1011 1111
delay_500ms();
P2 = 0x7F;// 0111 1111
delay_500ms();
}

按键控制LED

单个独立按键控制LED,按下点亮,松开熄灭

这里主要是检测IO口的电平高低,上电后寄存器可以操作IO引脚电平高低,比如P1_0 = 1 输出高电平,那么同理,反过来 如果外围电路按键,参考电路图,c51_spilt_buttons_dianlu.png
这里的电路图,K1按键按下,P3_1 和 GND导通,那么P3_1 自然就是低电平,此时如果去检测P3_1 IO口的电平,那他就是低电平,IO口除了可以用来输出高低电平,还可以用作检测输入,那么这里,是把按键绑定到了LED亮灭来指示效果。

1
2
3
4
5
6
7
8
9
10
#include<8052.h>
void main() {
while(1) {
if(P3_1 == 0) {
P2_0 = 0;
} else {
P2_0 = 1;
}
}
}

两个按键一起绑定,控制LED亮灭,K1 & K2 一起按下LED点亮,松开其中一个熄灭

同样,中间的表达式改成 || 的话,就两个按键 都可以控制LED亮灭。再同样,左移,右移操作,可以玩LED灯的按位与,按位或,可以实现一个逻辑门电路的结果展示,以及LED左右移,来实现流水灯。

1
2
3
4
5
6
7
8
9
10
#include<8052.h>
void main() {
while(1) {
if(P3_1 == 0 && P3_0 == 0) {
P2_0 = 0;
} else {
P2_0 = 1;
}
}
}

按键控制LED长亮,或长灭

这里包含了按键消抖,按键因为是机械部件,设计不太会很精密,按下和松开的时候,不会直接就变高电平或低电平,他可能会有一部分抖动的时间,大概20ms左右才变稳定,所以要加按键消抖。
而代码上,P2_0 的取反,江科大他写的是 P2_0 = ~P2_0; 但是在我们这里不起作用,问了AI,建议是用 逻辑取反,因为AI说 具体运行的时候,P2_0会被当作int来处理,int是16位,也不知真假,估计是编译器的问题,可能用Keil就正常了,这个差异,AI说是我们编译器的问题,我们用的是 sdcc,这个本身没有什么问题,能够学到就ok了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<8052.h>
// 0.01 * 100 = 1ms * 500
void delay(int xms){
int us = xms * 100;
while(us--);
}

void main() {
while(1) {
if(P3_1 == 0) {
// 按键消抖
delay(20);
while(P3_1 ==0);
delay(20);
P2_0 = !P2_0;
}
}
}

LED展示二进制加法操作

P2++ 这个是核心的操作,最开始是1111 1111 8个全亮,++ 第一次全部熄灭,然后按一次D1亮,按第二次,D1灭,D2亮,第三次D1再亮,D2也亮,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<8052.h>
// 0.01 * 100 = 1ms * 500
void delay(int xms){
int us = xms * 100;
while(us--);
}

void main() {
while(1) {
if(P3_1 == 0) {
// 按键消抖
delay(20);
while(P3_1 ==0);
delay(20);
P2++;
}
}
}

LED显示二进制加法操作的另一种方式

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

// 0.01 * 100 = 1ms * 500
void delay(int xms){
int us = xms * 100;
while(us--);
}

void main() {
unsigned char LED = 0;
while(1) {
if(P3_1 == 0) {
// 按键消抖
delay(20);
while(P3_1 ==0);
delay(20);
LED++;
P2 = ~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
#include<8052.h>
// 0.01 * 100 = 1ms * 500
void delay(int xms){
int us = xms * 100;
while(us--);
}

void main() {
unsigned char LED = 0;
P2 = ~0x01;
while(1) {
if(P3_1 == 0) {
// 按键消抖
delay(20);
while(P3_1 ==0);
delay(20);
LED++;
LED = LED%8;
P2 = ~(0x01<<LED);// 这里LED会是0-7,0x01是 0000 0001 然后取反, 只有一个灯高电平,但是这个LED输出低电平才会亮,所以

}
}
}

一个按键控制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
#include<8052.h>
// 0.01 * 100 = 1ms * 500
void delay(int xms){
int us = xms * 100;
while(us--);
}

void main() {
unsigned char LED = 0;
P2 = ~0x01;
while(1) {
if(P3_1 == 0) {
// 按键消抖
delay(20);
while(P3_1 ==0);
delay(20);
LED++;
LED = LED%8;
P2 = ~(0x01<<LED);
}
if(P3_0 == 0) {
// 按键消抖
delay(20);
while(P3_0 ==0);
delay(20);
LED--;
LED = LED%8;
P2 = ~(0x01<<LED);
}
}
}

单片机控制数码管的操作

背景资料

我们使用的数码管,有单个位的,只能显示一个数字,一般是有共阴极数码管和共阳极数码管,每个段其实可以看成是一个LED,那么数码管的每一位其实都是很多LED组成,单个段的话,ABCDEFG DP 从数码管顶那个横杠开始,,这两个编程方式不一样,c51_digital_led_segment_pos.png 示意图如图所示,abcdefgDP 这几个 是开发的时候每个数码管的段选顺序,按照这个特定的顺序,给ABCDEFG DP几个引脚 给对应的亮灭,就可以让这个数码管出现对应的数字,而共阴极和共阳极的数码管,体现在段选的差异上,段选就是说要哪个数码管显示数字, c51_digital_led_segment_pos_2.png
比如上面的图,上面的是共阴极,那如果想要第一位 数码管显示,就要12引脚给低电平,其他9 8 6三个引脚给高电平,而ABCDEFG DP 这几个位,可以看到这几个是统一接到了 1 2 3 4 5 7 10 11 这几个引脚上,可以提高单片机IO口的复用率,用一套8个引脚,控制所有数码管的位选,而用另外4个引脚,控制4个段选,而我们这个单片机上一共有2组4位一体数码管,所以位选是统一绑定到P00-P07,也就是P0口上,而段选,可以看到旁边有个 74LS138芯片,他可以通过3个引脚,控制8个引脚输出高低电平,因为2^3=8,而每个引脚可以有2个状态,高低电平,通过这个,可以进一步减少IO口的占用,提高IO口的使用效率。可以看下接线:c51_digital_led_dianlutu.png,可以看到,P22,P23,P24这三个引脚通过38译码器,控制LED1-LED8,而LED1-LED8正好是8个LED的段选引脚,再然后,可以看一下 又有一个 74HC245,他是一个单向的输出功能,可以理解成是驱动电路,数码管需要VCC来驱动电路,而单片机的IO口引脚输出的电平太低,需要有一个类似三极管的芯片,这里可以不用管这个 74HC245,对编程无感。

第一个例程,数码管的静态显示

主要用到段选,和位选,位选负责控制数码管显示什么数字,段选控制哪个数码管亮,P22-P24 3个引脚,负责控制8个数码管。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<8052.h>
// 0.01 * 100 = 1ms * 500
void delay(int xms){
int us = xms * 100;
while(us--);
}
void main() {
// 计算第几位,是反过来的 比如 P22-P24 000 得出来0,Y0,对应LED1,那就是第8个数码管,101 得出来5,那就是Y5,得出来第3个数码管
// 如果想要第四个,那就是 101 100
P2_4 = 1;
P2_3 = 0;
P2_2 = 1;
P0 = 0x7D;
while(1){}
}

通过内置的数组和 switch case 来实现数码管单个的延时显示

主要内容是循环每个数码管的位,在每个数码管位上轮流显示0-F,显示间隔为500ms

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
#include<8052.h>
// 0.01 * 100 = 1ms * 500
void delay(int xms){
int us = xms * 100;
while(us--);
}

unsigned char digital_led_numbers[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x00};
void digital_led_segment(int seg, int number) {
switch (seg) {
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 = digital_led_numbers[number];
}

void main() {
while(1){
for(int i=1; i<9; i++) {
for(int j=0; j< 16; j++) {
digital_led_segment(i, j);
delay(500);
}
}
}
}

数码管的动态显示与消影

上面的基础上,要在第一位显示1,第二位显示2,第三位显示3,需要注意一下digital_led_segment 这个函数的最后两行,delay(1);P0 = 0x00,这个就是去消影,因为数码管的段选和位选是同时操作的,所以会有概率段选和位选串位,导致下一个段显示了上一个段的内容,所以说 在输出之后立马把P0口置0,就不会受到上一位的影响。这个是因为我们这个74LS138这个芯片,被设置为单向输出。

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
#include<8052.h>
// 0.01 * 100 = 1ms * 500
void delay(int xms){
int us = xms * 100;
while(us--);
}

unsigned char digital_led_numbers[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x00};




void digital_led_segment(int seg, int number) {
switch (seg) {
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 = digital_led_numbers[number];
delay(1);
P0 = 0x00;
}

void main() {
while(1){
digital_led_segment(1,1);
// delay(20);
digital_led_segment(2,2);
// delay(20);
digital_led_segment(3,3);
// delay(20);

}
}

模块化编程

模块化编程,其实就是 把原本写到一个 main.c文件里的内容,分散到多个 .h 和 .c 组成的 文件中,可以代码结构比较合理,同C语言的分散编程。
然后他教程里,直接出了个 LCD1602的debug演示,所以我们就从 LCD1602这个地方开始学,学完之后再去debug LCD1602之后的课程继续学。

LCD1602的控制与输出

LCD1602的概念,也就是一个 16列,2行的古早显示器,每行每列都是一个5*7的点阵组成的字符显示输出设备,LCD1602的原理就和现代LCD屏幕比较像了,所以学一下,后面可以控制更现代化的LCD屏幕。

lcd1602_link.png 先看一下LCD1602的电路图,相关的代码,得结合LCD 1602的数据手册来分析,主要是RS,RW,和E这几个引脚,以及1是控制写命令,还是写数据,写数据得往P0 这个组合引脚写,因为我们这几个LCD的数据引脚,在D0-D7,A/K引脚,VCC,VSS,GND这几个也可以不管,模块也都解决了。
那么主要就是看LCD1602的数据手册了,数据手册上有给一些demo样例参考,可以看一下。

代码部分,由于用了分布模块的方式,每个文件都在自己的那一小块里面,所以一起贴上来吧。
main.c,主函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<8052.h>
#include "delay.h"
#include "lcd_1602.h"
void main() {
// 注意一下,这里的每一行注释 就是一个例程,需要搭配 LCD_Init 函数一起用,最后那个 while循环里写 0x18是控制LCD屏幕滑动,实现类似字幕循环滚动的效果
LCD_Init();
// LCD_ShowChar(1,1, '\\');
// LCD_ShowString(1,2, "abcdefg");
// LCD_ShowNum(1,1, 88, 4);
// LCD_ShowSignedNum(1,1, -32768, 5);
// LCD_ShowHexNum(1,1, 0xFF, 2);
// LCD_ShowBinNum(1,1, 0xA5, 8);

LCD_ShowString(1,1, "Welcome to CHINA!");

while(1){
LCD_WriteCommand(0x18);
delay(500);
}
}

delay.h,这个就不贴了,是个延时函数,可以参考上面的 delay函数抄一个

lcd_1602.h,这个里面主要是 LCD1602的控制和显示部分了。

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

#ifndef __LCD_1602_H__
#define __LCD_1602_H__

#define LCD_RS P2_6
#define LCD_RW P2_5
#define LCD_E P2_7
#define LCD_DataPort P0

void LCD_WriteCommand(unsigned char command) {
// 参考数据手册
LCD_RS = 0;
LCD_RW = 0;
LCD_DataPort = command;
LCD_E = 1;
delay(1);
LCD_E = 0;
delay(1);
}

void LCD_WriteData(unsigned char data) {
// 参考数据手册
LCD_RS = 1;
LCD_RW = 0;
LCD_DataPort = data;
LCD_E = 1;
delay(1);
LCD_E = 0;
delay(1);

}


void LCD_Init() {
LCD_WriteCommand(0x38);
LCD_WriteCommand(0x0C);
LCD_WriteCommand(0x06);
LCD_WriteCommand(0x01);
}

void lcd_setCursor(unsigned char row, unsigned char col) {
if(row == 1){
LCD_WriteCommand(0x80|(col-1));
} else {
LCD_WriteCommand(0x80|(col-1)+0x40);
}

}

void LCD_ShowChar(unsigned char row, unsigned char col, unsigned char data) {
lcd_setCursor(row, col);
LCD_WriteData(data);
}

void LCD_ShowString(unsigned char row, unsigned char col, unsigned char *strs) {
lcd_setCursor(row, col);
for(int i=0;strs[i]!= '\0'; i++) {
LCD_WriteData(strs[i]);
}
}

int lcd_pow(int x, int y) {
int result = 1;
for(int i=0; i<y; i++) {
result =result * x;
}
return result;
}

void LCD_ShowNum(unsigned char row, unsigned char col, int num, int length) {
lcd_setCursor(row, col);
for(int i=length; i>0; i--) {
LCD_WriteData(num/ lcd_pow(10, i-1)%10 + '0');
}
}

void LCD_ShowSignedNum(unsigned char row, unsigned char col, int num, int length) {
lcd_setCursor(row, col);
unsigned int showNum = 0;
if(num >=0) {
LCD_WriteData('+');
showNum = num;
} else {
LCD_WriteData('-');
showNum = -num;
}
for(int i=length; i>0; i--) {
LCD_WriteData(showNum/ lcd_pow(10, i-1)%10 + '0');
}
}

void LCD_ShowHexNum(unsigned char row, unsigned char col, unsigned int num, int length) {
lcd_setCursor(row, col);
unsigned char bitNum = 0;
for(int i=length; i>0; i--) {
bitNum = num/ lcd_pow(16, i-1)%16;
if(bitNum < 10) {
LCD_WriteData(bitNum + '0');
} else {
LCD_WriteData(bitNum + 'A' - 10);
}
}
}

void LCD_ShowBinNum(unsigned char row, unsigned char col, unsigned int num, int length) {
lcd_setCursor(row, col);
for(int i=length; i>0; i--) {
LCD_WriteData(num/ lcd_pow(2, i-1)%2 + '0');
}
}

#endif

这个代码上,还是有很多小巧思,易读,模块化,以及抽出冗余的地方,这些还是真的挺牛逼的。

矩阵键盘

矩阵键盘输入检测以及结合LCD1602做显示

metrix_keys.png 电路图,

同理,delay.h,lcd1602.h,这几个都不继续贴了,主要贴一下 核心的 metrixkeys.h,还有另外的几个方法,叫做线反转法,或者是行检测,列检测等多种方法,等后面有空了再给独立补全一下这些方法。实现的效果,就是按 S1-S16,LCD上显示对应的标号。

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
#include<8052.h>
#include "delay.h"
#ifndef __MARRTXKEYS_H__
#define __MARRTXKEYS_H__


unsigned char MetrixKeys() {
unsigned char KeyNum = 0;


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


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


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


P1 = 0xFF;
P1_0 = 0;
if(P1_7 ==0) { delay(20); while(P1_7 == 0); delay(20); KeyNum = 4; }
if(P1_6 ==0) { delay(20); while(P1_6 == 0); delay(20); KeyNum = 8; }
if(P1_5 ==0) { delay(20); while(P1_5 == 0); delay(20); KeyNum = 12; }
if(P1_4 ==0) { delay(20); while(P1_4 == 0); delay(20); KeyNum = 16; }
return KeyNum;
}
#endif

还有一个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
#include<8052.h>
#include "delay.h"
#include "lcd_1602.h"
#include "matrixkeys.h"


unsigned char keyNum = 0;

void main() {
LCD_Init();
// LCD_ShowChar(1,1, '\\');
// LCD_ShowString(1,2, "abcdefg");
// LCD_ShowNum(1,1, 88, 4);
// LCD_ShowSignedNum(1,1, -32768, 5);
// LCD_ShowHexNum(1,1, 0xFF, 2);
// LCD_ShowBinNum(1,1, 0xA5, 8);

LCD_ShowString(1,1, "Welcome to CHINA!");



while(1){
// LCD_WriteCommand(0x18);
// delay(500);
keyNum = MetrixKeys();
if(keyNum != 0) {
LCD_ShowNum(2,1, keyNum, 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
#include<8052.h>
#include "delay.h"
#include "lcd_1602.h"
#include "matrixkeys.h"


unsigned char keyNum = 0;
int pass = 0;
int count = 0;

void main() {
LCD_Init();
LCD_ShowString(1,1, "Password:");
while(1){
keyNum = MetrixKeys();
if(keyNum != 0) {
if(keyNum <=10) {
if(count < 4) {
pass = pass*10;
pass += keyNum % 10;
}
count++;
}
LCD_ShowNum(2,1, pass, 4);
if(keyNum == 11) {
if(pass == 1234) {
LCD_ShowString(1, 14, "OK ");
pass = 0;
count = 0;
LCD_ShowNum(2,1, pass, 4);
} else {
LCD_ShowString(1, 14, "ERR");
pass = 0;
count = 0;
LCD_ShowNum(2,1, pass, 4);
}
}
if(keyNum == 12) {
pass = 0;
count = 0;
LCD_ShowNum(2,1, pass, 4);
}
}

}
}

定时器

定时器的基础知识

STC89C52RC里面有三个定时器,T0/T1/T2,一般不用T2,只用T0,T1。详细的内容要参考数据手册来写,不过有个问题是 定时器和晶振的频率还有区别。
我的开发板 晶振是11.0592Mhz,所以在AI的辅助下,我和江科大的定时器参数有些许的不一样,如果你的晶振也是12Mhz,可以按照他的来。

他的思路

他的思路是:他的晶振是12Mhz,用16位计数器,计数器每累加一次 代表一个时钟周期,单片机内部采用12T分频,也就是1us,得到 12Mhz / 12 = 1us,就是1微秒,刚好能被整除,然后16位计数器累加最大的就是 2^16=65536,我们用无符号整数就可以刚好拿到 0-65535这个值,那么这个累加器每次超过65535以后,这个值就要溢出归零,溢出归零以后,我们通过寄存器开一个中断,溢出以后定时器就会调用一次中断函数,来达到我们的目的。
那么可以给TH0和 TL0这两个寄存器,这两个就是存那个16位累加器的数字的,给他俩一个初始值,这样子,限定到65535 只有 1000的位置,假如说是64535,那么初始化上去,他第一次就从64535开始累加,累加1000次,也就是1000us=1ms,自动触发中断函数,中断函数里再次把这个寄存器给到这个初始,然后就一直能够得到稳定的1ms,这样子我们累计1000次,得到1000ms=1s,这样子1s 以后 就可以控制 P2_0口反转,获得稳定的时间。

我们的思路

我们的晶振是11.0592Mhz,被1ms除不尽,所以永远会有误差,我们如果想要模拟1ms的话, T = 12 / 11.0592 = 1.08507us,定时1ms = 1000us / 1.08507us = 921.6次累加,那所以是 近似922次,65536-922=64614,所以我们的TH0和 TL0初始值是 64614,但是我试了,LED闪烁可能刚开始还好,后来就慢慢不准。但,按照我们这个晶振频率,给出来50ms是准确的,能够被算法除尽,所以我们就改成了50ms中断一次。
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
#include<8052.h>
#include "timer0.h"
void Timer0_Routine() __interrupt(1) {
// 这里是ai给的一个补偿的代码,好像是回读高位补偿,也不太懂 推荐了就给写上去,能够得到和现代时间比较接近的时间了
unsigned int n = ((unsigned int)TH0 << 8) | TL0;
n += 19456;
TH0 = n >> 8;
TL0 = n;
T0TimerCount++;
// 50ms 中断一次 20*50 = 1000ms = 1s
if(T0TimerCount >= 20) {
T0TimerCount = 0;
// TODO 这里是你想实现的控制位
P2_0 = !P2_0;
P2_1 = !P2_1;
}
}

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

timer0.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
#include<8052.h>

static unsigned int T0TimerCount = 0;
void Timer0_Init() {
// 工作模式寄存器,0x01 代表:M0=1 M1=0 代表定时器工作在16位累加器,TH0 和 TL0 两个8位寄存器
TMOD = 0x01;
TF0 = 0; // 中断标志位
TR0 = 1; //
TH0 = 19456/256; // 这里是 因为我的单片机是 11.0592Mhz的晶振,改为按50ms 触发一次中断
TL0 = 19456%256;
ET0 = 1;
EA = 1;
PT0 = 0;
}



/*
定时器中断函数
void Timer0_Routine() __interrupt(1) {
unsigned int n = ((unsigned int)TH0 << 8) | TL0;
n += 19456;
TH0 = n >> 8;
TL0 = n;
T0TimerCount++;
// 50ms 中断一次 20*50 = 1000ms = 1s
if(T0TimerCount >= 20) {
T0TimerCount = 0;
// TODO 这里是你想实现的控制位

}
}
*/

结合定时器+独立按键+LED流水灯的左移,右移

独立按键,实现控制LED左移右移的变量;另外是这个方法里,多了很多其他的东西,有循环移位,之类的操作比较高级了;

库函数:intrins.h,因为这个是keil的库函数,所以我们自己开发的话,AI给我们提供了一个自己实现的库函数,放到 include目录里 引入就好。
在这里,我们用的是 crol 循环左移 和 cror 循环右移这两个函数。

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
#ifndef __INTRINS_H__
#define __INTRINS_H__

// SDCC 兼容版 intrins.h,模拟 Keil C51 内建函数

#define _nop_() __asm__("nop")

static unsigned char _crol_(unsigned char v, unsigned char n) {
n %= 8;
if (n == 0) return v;
return (unsigned char)((v << n) | (v >> (8 - n)));
}

static unsigned char _cror_(unsigned char v, unsigned char n) {
n %= 8;
if (n == 0) return v;
return (unsigned char)((v >> n) | (v << (8 - n)));
}

static unsigned char _irol_(unsigned int v, unsigned char n) {
n %= 16;
if (n == 0) return v;
return (unsigned int)((v << n) | (v >> (16 - n)));
}

static unsigned char _iror_(unsigned int v, unsigned char n) {
n %= 16;
if (n == 0) return v;
return (unsigned int)((v >> n) | (v << (16 - n)));
}

static unsigned char _testbit_(volatile unsigned char *b, unsigned char bit) {
if (*b & (1 << bit)) {
*b &= (unsigned char)~(1 << bit);
return 1;
}
return 0;
}
#endif

timer0.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
#include<8052.h>

static unsigned int T0TimerCount = 0;
void Timer0_Init() {
// 工作模式寄存器,0x01 代表:M0=1 M1=0 代表定时器工作在16位累加器,TH0 和 TL0 两个8位寄存器
TMOD = 0x01;
TF0 = 0; // 中断标志位
TR0 = 1; //
TH0 = 19456/256; // 这里是 因为我的单片机是 11.0592Mhz的晶振,改为按50ms 触发一次中断
TL0 = 19456%256;
ET0 = 1;
EA = 1;
PT0 = 0;
}



/*
定时器中断函数
void Timer0_Routine() __interrupt(1) {
unsigned int n = ((unsigned int)TH0 << 8) | TL0;
n += 19456;
TH0 = n >> 8;
TL0 = n;
T0TimerCount++;
// 50ms 中断一次 20*50 = 1000ms = 1s
if(T0TimerCount >= 20) {
T0TimerCount = 0;
// TODO 这里是你想实现的控制位

}
}
*/

keys.h 的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<8052.h>
#include "delay.h"
#ifndef __KEYS_H__
#define __KEYS_H__
unsigned char Key() {
unsigned char KeyNum = 0;
if(P3_1 ==0) { delay(20); while(P3_1 == 0); delay(20); KeyNum = 1; }
if(P3_0 ==0) { delay(20); while(P3_0 == 0); delay(20); KeyNum = 2; }
if(P3_2 ==0) { delay(20); while(P3_2 == 0); delay(20); KeyNum = 3; }
if(P3_3 ==0) { delay(20); while(P3_3 == 0); delay(20); KeyNum = 4; }
return KeyNum;
}
#endif

主函数 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
#include<8052.h>
#include "delay.h"
#include "keys.h"
#include "intrins.h"
#include "timer0.h"

// #include "lcd_1602.h"

unsigned char keyNum = 0;
unsigned char ledMode = 0;


void Timer0_Routine() __interrupt(1) {
unsigned int n = ((unsigned int)TH0 << 8) | TL0;
n += 19456;
TH0 = n >> 8;
TL0 = n;
T0TimerCount++;
// 50ms 中断一次 10*50 = 500ms
if(T0TimerCount >= 10) {
T0TimerCount = 0;
// TODO 这里是你想实现的控制
if(ledMode ==0 ) {
P2 = _crol_(P2, 1);
} else {
P2 = _cror_(P2, 1);
}

}
}

void main() {
P2 = 0xFE;
Timer0_Init();
// LCD_Init();
while(1) {
unsigned char keyNum = Key();
if(keyNum != 0) {
if(keyNum == 1) {
ledMode++;
if (ledMode >=2) { ledMode = 0; }
}
}
}
}

结合LCD1602,和定时器,做一个简易的24小时时钟

先来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
#include<8052.h>
#include "delay.h"
#include "intrins.h"
#include "timer0.h"
#include "lcd_1602.h"

unsigned char sec, min, hour = 0;


void Timer0_Routine() __interrupt(1) {
unsigned int n = ((unsigned int)TH0 << 8) | TL0;
n += 19456;
TH0 = n >> 8;
TL0 = n;
T0TimerCount++;
// 50ms 中断一次 20*50 = 1000ms = 1s
if(T0TimerCount >= 20) {
T0TimerCount = 0;
// TODO 这里是你想实现的控制
sec++;
if(sec >=60) {
min++;
sec = 0;
}
if(min>=60){
hour++;
min = 0;
sec = 0;
}
if(hour >=24) {
hour = 0;
min = 0;
sec = 0;
}
}
}


void main() {
LCD_Init();
LCD_ShowString(1,1, "Now Colck:");
LCD_ShowString(2,1, " : :");
Timer0_Init();
while(1) {
LCD_ShowNum(2,1, hour, 2);
LCD_ShowNum(2,4, min, 2);
LCD_ShowNum(2,7, sec, 2);
}
}

其他的参考一下之前的就可以,可以在LCD1602上显示一个 时分秒的时钟,到了24小时还会归零那种,学到这里 成就感满满,可以睡了。