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小时还会归零那种,学到这里 成就感满满,可以睡了。

串口协议

简单发送信息

用串口可以方便的和一些外部设备沟通,是对外常见的一个通信协议,类似的 还有 I^2C,USB,SPI总线协议等,这些都是物联网或单片机MCU开发常用的设备通信协议。

串口通信的代码,实现每隔1s往外发送一个数字:main.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<8052.h>
#include "delay.h"
#include "intrins.h"
#include "uart.h"

unsigned char sec = 0;

void main() {
UART_Init();
while(1) {
UART_SendByte(sec);
sec++;
delay(1000);
}
}

uart.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef __UART_H__
#define __UART_H__

void UART_Init() {
PCON &= 0x7F; // 波特率不倍速 4800
SCON = 0x50; // 8位可变波特率
TMOD &= 0x0F;
TMOD |= 0x20;
TL1 = 0xFA;
TH1 = 0xFA;
ET1 = 0;
TR1 = 1;
}

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

#endif

主要还是要看数据手册上的定义,以及使用模式,这里主要是使用 8位可重装计数器来用串口,而串口主要是SBUF,所以可以看到发送的主要操作也就是往SBUF这个寄存器写数据。这里的参数,要看你看的教程不一样,而不一样了,我这里看的是4800波特率,11.0592Mhz晶振频率,关闭中断得到的参数,如果你的有些区别,则按你的来动态变化。

还有个事是,因为我们用的是 PlatformIO IDE,所以串口的显示 有些不太好用,别个都用STC-ISP这个软件,我们不行,我们有内置的一个脚本来使用,

platformio device monitor --port /dev/cu.usbserial-120 -b 4800 --encoding UTF-8 就是这个脚本,port就是你的设备路径,b就是你的波特率,encoding 就是字符编码集。

同时收发数据

还有个更高级的工具,就是VSCode 有个 微软的工具,叫做 Serial Monitor,串口监视器,他可以直接监听串口,选择波特率,显示模式为Hex,这样子就可以直接观察输出,而发送也可以直接在发送端口输入字符,来输出,同时也可以选择按文本模式或Hex格式输入。
同时收发的代码:

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

unsigned char sec = 0;

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

void UART_Routine() __interrupt(4) {
if(RI == 1) {
P2 = ~SBUF;
UART_SendByte(0x66);
RI = 0;
}
}

这里的头文件里,要开一个串口的中断,EA =1, ES =1; 这两句 就是这样子来做的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef __UART_H__
#define __UART_H__

void UART_Init() {
PCON &= 0x7F; // 波特率不倍速 4800
SCON = 0x50; // 8位可变波特率
TMOD &= 0x0F;
TMOD |= 0x20;
TL1 = 0xFA;
TH1 = 0xFA;
ET1 = 0;
TR1 = 1;
EA = 1;
ES = 1;
}

void UART_SendByte(unsigned char data) {
SBUF = data;
while(TI == 0);
TI = 0;
}
#endif

LED点阵屏

74HC595芯片

这里讲到了一个特殊的芯片,74HC595,这是一个可以用3个引脚控制8位甚至更多引脚的一个比较牛逼的芯片,可以多个芯片串联,共同控制多个8位引脚,来达到驱动点阵LED的目的。

用LED点阵屏 显示一个笑脸图案

点阵屏驱动函数,matrix_led.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
#ifndef __MATRIX_LED_H__
#define __MATRIX_LED_H__

#include<8052.h>
#include "delay.h"

#define SER P3_4
#define RCK P3_5 //RCLK
#define SCK P3_6 // SRCLK
#define MATRIX_LED_PORT P0

void _74595_WriteByte(unsigned char data) {
for(unsigned char i =0; i<8; i++) {
SER = data & (0x80>>i);
SCK = 1;
SCK = 0;
}
RCK = 1;
RCK = 0;
}

void MatrixLED_Init() {
SCK = 0;
RCK = 0;
}

void MatrixLED_Show(unsigned char col, unsigned char data) {
_74595_WriteByte(data);
MATRIX_LED_PORT = ~(0x80 >> col);
delay(1);
MATRIX_LED_PORT = 0xFF;
}
#endif

代码main.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<8052.h>
#include "delay.h"
#include "intrins.h"
#include "matrix_led.h"

void main() {
MatrixLED_Init();
while(1) {
MatrixLED_Show(0, 0x3C);
MatrixLED_Show(1, 0x42);
MatrixLED_Show(2, 0xA9);
MatrixLED_Show(3, 0x85);
MatrixLED_Show(4, 0x85);
MatrixLED_Show(5, 0xA9);
MatrixLED_Show(6, 0x42);
MatrixLED_Show(7, 0x3C);
}
}

用LED点阵屏 显示一个流动的Hello!

这里开始有点体会到了 这个时钟线路的意义,他可以把IO口的时序设计一下,因为之前只有很少的几个电路,IO引脚也不是很多,所以说IO通过时钟信号来作为泵,来传入0/1.
上代码,matrix_led.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
#ifndef __MATRIX_LED_H__
#define __MATRIX_LED_H__

#include<8052.h>
#include "delay.h"

#define SER P3_4
#define RCK P3_5 //RCLK
#define SCK P3_6 // SRCLK
#define MATRIX_LED_PORT P0

void _74595_WriteByte(unsigned char data) {
for(unsigned char i =0; i<8; i++) {
SER = data & (0x80>>i);
SCK = 1;
SCK = 0;
}
RCK = 1;
RCK = 0;
}

void MatrixLED_Init() {
SCK = 0;
RCK = 0;
}

void MatrixLED_Show(unsigned char col, unsigned char data) {
_74595_WriteByte(data);
MATRIX_LED_PORT = ~(0x80 >> col);
delay(1);
MATRIX_LED_PORT = 0xFF;
}

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

unsigned char Ani[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x08, 0x08, 0x08, 0xFF, 0x00, 0x0E, 0x15,
0x15, 0x15, 0x08, 0x00, 0x7E, 0x01, 0x02, 0x00,
0x7E, 0x01, 0x02, 0x00, 0x0E, 0x11, 0x11, 0x0E,
0x00, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

void main() {
unsigned char i = 0;
unsigned char offset = 0;
unsigned char count = 0;
MatrixLED_Init();
while(1) {
for(unsigned char i=0; i<8; i++) {
MatrixLED_Show(i, Ani[i+offset]);
}
count++;
if(count>10) {
count = 0;
offset++;
if(offset> 40) {
offset = 0;
}
}
}
}

ani这个实际上是一个数组,固定了”Hello!”这个字符的字模,也就是LED哪个地方亮,哪个地方不亮,并且是一个比较大的字模,足足有328这么大,而且加上了头尾加的为了这个LED的效果显示流畅故意加的空白行,所以这个数组大概是488这么大,动态给他显示出来就ok了。

DS1302时钟芯片

用DS1302时钟芯片做一个不靠中断的时间表

这篇代码比较长,所以全给贴上来一份吧,

delay.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef __DELAY_H__

#define __DELAY_H__


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

#endif

lcd1602.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
#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 int 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, unsigned 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

ds1302.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
#ifndef __DS_1302_H__
#define __DS_1302_H__
#include<8052.h>

#define DS1302_IO P3_4 // IO引脚
#define DS1302_CE P3_5 // 使能引脚
#define DS1302_SCLK P3_6 // 时钟信号引脚

// 星期
#define DS1302_Write_Week 0x8A
#define DS1302_Read_Week 0x8B
// 秒
#define DS1302_Write_Sec 0x80
#define DS1302_Read_Sec 0x81
// 分钟
#define DS1302_Write_Min 0x82
#define DS1302_Read_Min 0x83
// 小时
#define DS1302_Write_Hour 0x84
#define DS1302_Read_Hour 0x85
// 日
#define DS1302_Write_Day 0x86
#define DS1302_Read_Day 0x87
// 月
#define DS1302_Write_Month 0x88
#define DS1302_Read_Month 0x89
// 年
#define DS1302_Write_Year 0x8C
#define DS1302_Read_Year 0x8D
// 写保护
#define DS1302_Write_WP 0x8E
#define DS1302_Read_WP 0x8F




unsigned char DS1302_Time[7] = {26, 9, 13, 22, 44, 0, 7};


unsigned char BCD2Decimal(unsigned char bcd) {
return bcd/16*10 + bcd%16;
}

unsigned char Decimal2BCD(unsigned char num) {
return num/10*16+num%10;
}


void DS1302_Init() {
DS1302_CE = 0;
DS1302_SCLK = 0;
}

void DS1302_WriteByte(unsigned char command, unsigned char data) {
DS1302_CE = 1;
for(unsigned char i=0; i<8; i++) {
DS1302_IO = command &(0x01<<i);
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
for(unsigned char i=0; i<8; i++) {
DS1302_IO = data &(0x01<<i);
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
DS1302_CE = 0;

}

unsigned char DS1302_ReadByte(unsigned char command) {
unsigned char data = 0x00;
// command |= 0x01; 这个行是为了兼容 少定义一个 command 但我们分开定义
DS1302_CE = 1;
for(unsigned char i=0; i<8; i++) {
DS1302_IO = command &(0x01<<i);
DS1302_SCLK = 0;
DS1302_SCLK = 1;
}
for(unsigned char i=0; i<8; i++) {
DS1302_SCLK = 1;
DS1302_SCLK = 0;
if(DS1302_IO == 1) {
data = data | (0x01<<i);
}
}
DS1302_CE = 0;
DS1302_IO = 0;
return data;
}

void DS1302_SetTime() {
DS1302_WriteByte(DS1302_Write_WP, 0x00);
DS1302_WriteByte(DS1302_Write_Year, Decimal2BCD(DS1302_Time[0]));
DS1302_WriteByte(DS1302_Write_Month, Decimal2BCD(DS1302_Time[1]));
DS1302_WriteByte(DS1302_Write_Day, Decimal2BCD(DS1302_Time[2]));
DS1302_WriteByte(DS1302_Write_Hour, Decimal2BCD(DS1302_Time[3]));
DS1302_WriteByte(DS1302_Write_Min, Decimal2BCD(DS1302_Time[4]));
DS1302_WriteByte(DS1302_Write_Sec, Decimal2BCD(DS1302_Time[5]));
DS1302_WriteByte(DS1302_Write_Week, Decimal2BCD(DS1302_Time[6]));
DS1302_WriteByte(DS1302_Write_WP, 0x80);
}

void DS1302_ReadTime() {
DS1302_Time[0] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Year));
DS1302_Time[1] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Month));
DS1302_Time[2] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Day));
DS1302_Time[3] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Hour));
DS1302_Time[4] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Min));
DS1302_Time[5] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Sec));
DS1302_Time[6] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Week));
}

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


void main() {
LCD_Init();
DS1302_Init();
LCD_ShowString(1,1, "RTC Clock:");
DS1302_SetTime();
while(1) {
DS1302_ReadTime();
LCD_ShowNum(2,1, DS1302_Time[0], 2);
LCD_ShowNum(2,3, DS1302_Time[1], 2);
LCD_ShowNum(2,5, DS1302_Time[2], 2);
LCD_ShowNum(2,7, DS1302_Time[3], 2);
LCD_ShowNum(2,9, DS1302_Time[4], 2);
LCD_ShowNum(2,11, DS1302_Time[5], 2);
LCD_ShowNum(2,13, DS1302_Time[6], 2);
}
}

这个代码结合1602,DS1302,代码量感觉比较大,深入学习了时钟引脚带动电路运转的逻辑。

结合定时器,LCD1602,DS1302这几个,做一个综合时钟模块

这块他说比较难,但是 对我们来说比较简单了已经,既然已经封装成了函数,无非也就是调用一下而已。

确实比较麻烦,而且很简单,就是程序逻辑调用,日期的越界判断之类的,把代码贴上来吧,这个有点复杂的程序还是,另外我写的也不定能按照他的完全,还有bug,等有心情了再慢慢修吧,或者借助ai帮你修复吧。
delay.h的代码:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef __DELAY_H__

#define __DELAY_H__


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

#endif

lcd1602.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
#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 int 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, unsigned 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

ds1302.h代码:这里有个时间的格式,本来原来写的是 unsigned char,但是为了越界判断好判断,改成了 char,这样子范围就变成 -128~127,因为日期这个程序所有位都不会超过100,所以导师也还好的,而且方便减的时候做判断。

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
#ifndef __DS_1302_H__
#define __DS_1302_H__
#include<8052.h>

#define DS1302_IO P3_4 // IO引脚
#define DS1302_CE P3_5 // 使能引脚
#define DS1302_SCLK P3_6 // 时钟信号引脚

// 星期
#define DS1302_Write_Week 0x8A
#define DS1302_Read_Week 0x8B
// 秒
#define DS1302_Write_Sec 0x80
#define DS1302_Read_Sec 0x81
// 分钟
#define DS1302_Write_Min 0x82
#define DS1302_Read_Min 0x83
// 小时
#define DS1302_Write_Hour 0x84
#define DS1302_Read_Hour 0x85
// 日
#define DS1302_Write_Day 0x86
#define DS1302_Read_Day 0x87
// 月
#define DS1302_Write_Month 0x88
#define DS1302_Read_Month 0x89
// 年
#define DS1302_Write_Year 0x8C
#define DS1302_Read_Year 0x8D
// 写保护
#define DS1302_Write_WP 0x8E
#define DS1302_Read_WP 0x8F




char DS1302_Time[7] = {26, 9, 13, 22, 44, 0, 7};


char BCD2Decimal(char bcd) {
return bcd/16*10 + bcd%16;
}

char Decimal2BCD(char num) {
return num/10*16+num%10;
}


void DS1302_Init() {
DS1302_CE = 0;
DS1302_SCLK = 0;
}

void DS1302_WriteByte(char command, char data) {
DS1302_CE = 1;
for(unsigned char i=0; i<8; i++) {
DS1302_IO = command &(0x01<<i);
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
for(unsigned char i=0; i<8; i++) {
DS1302_IO = data &(0x01<<i);
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
DS1302_CE = 0;

}

char DS1302_ReadByte(char command) {
char data = 0x00;
// command |= 0x01; 这个行是为了兼容 少定义一个 command 但我们分开定义
DS1302_CE = 1;
for(unsigned char i=0; i<8; i++) {
DS1302_IO = command &(0x01<<i);
DS1302_SCLK = 0;
DS1302_SCLK = 1;
}
for(unsigned char i=0; i<8; i++) {
DS1302_SCLK = 1;
DS1302_SCLK = 0;
if(DS1302_IO == 1) {
data = data | (0x01<<i);
}
}
DS1302_CE = 0;
DS1302_IO = 0;
return data;
}

void DS1302_SetTime() {
DS1302_WriteByte(DS1302_Write_WP, 0x00);
DS1302_WriteByte(DS1302_Write_Year, Decimal2BCD(DS1302_Time[0]));
DS1302_WriteByte(DS1302_Write_Month, Decimal2BCD(DS1302_Time[1]));
DS1302_WriteByte(DS1302_Write_Day, Decimal2BCD(DS1302_Time[2]));
DS1302_WriteByte(DS1302_Write_Hour, Decimal2BCD(DS1302_Time[3]));
DS1302_WriteByte(DS1302_Write_Min, Decimal2BCD(DS1302_Time[4]));
DS1302_WriteByte(DS1302_Write_Sec, Decimal2BCD(DS1302_Time[5]));
DS1302_WriteByte(DS1302_Write_Week, Decimal2BCD(DS1302_Time[6]));
DS1302_WriteByte(DS1302_Write_WP, 0x80);
}

void DS1302_ReadTime() {
DS1302_Time[0] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Year));
DS1302_Time[1] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Month));
DS1302_Time[2] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Day));
DS1302_Time[3] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Hour));
DS1302_Time[4] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Min));
DS1302_Time[5] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Sec));
DS1302_Time[6] = BCD2Decimal(DS1302_ReadByte(DS1302_Read_Week));
}

#endif

keys.h代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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

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 这里是你想实现的控制位

}
}
*/

最后,最重要的主函数:

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

unsigned char keyNum = 0;
unsigned char MODE = 0;
unsigned char SetTimeSelect = 0;
unsigned char TimeFlag = 0;

void ShowTime() {

DS1302_ReadTime();
LCD_ShowNum(1,1, DS1302_Time[0], 2);
LCD_ShowNum(1,4, DS1302_Time[1], 2);
LCD_ShowNum(1,7, DS1302_Time[2], 2);
LCD_ShowNum(2,1, DS1302_Time[3], 2);
LCD_ShowNum(2,4, DS1302_Time[4], 2);
LCD_ShowNum(2,7, DS1302_Time[5], 2);
}

void SetTime() {
switch (keyNum) {
case 2:
SetTimeSelect++;
SetTimeSelect %= 6;
break;
case 3:
DS1302_Time[SetTimeSelect]++;
if(DS1302_Time[0]>99) { DS1302_Time[0] =0;}
if(DS1302_Time[1]>12) { DS1302_Time[1] =1;}
if(DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 || DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12) {
if(DS1302_Time[2]>31) { DS1302_Time[2] =1;}
} else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11) {
if(DS1302_Time[2]>30) { DS1302_Time[2] =1;}
} else if(DS1302_Time[1]==2) {
if(DS1302_Time[0]%4 ==0) {
if(DS1302_Time[2]>29) {
DS1302_Time[2] = 1;
}
} else {
if(DS1302_Time[2]>28) {
DS1302_Time[2] = 1;
}
}
}

if(DS1302_Time[3]>24) { DS1302_Time[3] =0;}
if(DS1302_Time[4]>60) { DS1302_Time[4] =0;}
if(DS1302_Time[5]>60) { DS1302_Time[5] =0;}
break;
case 4:
DS1302_Time[SetTimeSelect]--;
if(DS1302_Time[0]<0) { DS1302_Time[0] =99;}
if(DS1302_Time[1]<1) { DS1302_Time[1] =12;}
if(DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 || DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12) {
if(DS1302_Time[2]<1) { DS1302_Time[2] =31;}
} else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11) {
if(DS1302_Time[2]<1) { DS1302_Time[2] =30;}
} else if(DS1302_Time[1]==2) {
if(DS1302_Time[0]%4 ==0) {
if(DS1302_Time[2]<1) {
DS1302_Time[2] = 29;
}
} else {
if(DS1302_Time[2]<1) {
DS1302_Time[2] = 28;
}
}
}

if(DS1302_Time[3]<0) { DS1302_Time[3] =23;}
if(DS1302_Time[4]<0) { DS1302_Time[4] =59;}
if(DS1302_Time[5]<0) { DS1302_Time[5] =59;}
break;
}
if(SetTimeSelect == 0 && TimeFlag == 1) { LCD_ShowString(1,1, " "); } else {
LCD_ShowNum(1,1, DS1302_Time[0], 2);
}
if(SetTimeSelect == 1 && TimeFlag == 1) { LCD_ShowString(1,4, " "); } else {
LCD_ShowNum(1,4, DS1302_Time[1], 2);
}
if(SetTimeSelect == 2 && TimeFlag == 1) { LCD_ShowString(1,7, " "); } else {
LCD_ShowNum(1,7, DS1302_Time[2], 2);
}
if(SetTimeSelect == 3 && TimeFlag == 1) { LCD_ShowString(2,1, " "); } else {
LCD_ShowNum(2,1, DS1302_Time[3], 2);
}
if(SetTimeSelect == 4 && TimeFlag == 1) { LCD_ShowString(2,4, " "); } else {
LCD_ShowNum(2,4, DS1302_Time[4], 2);
}
if(SetTimeSelect == 5 && TimeFlag == 1) { LCD_ShowString(2,7, " "); } else {
LCD_ShowNum(2,7, DS1302_Time[5], 2);
}
}

void main() {
LCD_Init();
DS1302_Init();
Timer0_Init();
LCD_ShowString(1,1, " - - ");
LCD_ShowString(2,1, " : : ");
DS1302_SetTime();
while(1) {
keyNum = Key();
if(keyNum==1) {
if(MODE ==0) { MODE = 1;}
else if(MODE ==1) { MODE = 0; DS1302_SetTime();}
}
switch(MODE) {
case 0:
ShowTime();
break;
case 1:
SetTime();
break;

}


}
}



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 >= 10) {
T0TimerCount = 0;
// TODO 这里是你想实现的控制位
TimeFlag = !TimeFlag;
}
}

再说一遍,这个代码还有bug,如果抄的话 不要照抄,逻辑上的问题要自己修一下。

蜂鸣器

让蜂鸣器响

这里是实现蜂鸣器+按键+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>
#include "delay.h"
#include "intrins.h"
#include "keys.h"
#include "timer0.h"
#include "digital_led.h"
#include "beep.h"

unsigned char keyNum = 0;

void main() {
digital_led_segment(1, keyNum);
while(1) {
keyNum = Key();
if(keyNum != 0) {
for(int i =0; i< 200; i++) {
BEEP = !BEEP;
delay(1);
}
digital_led_segment(1, keyNum);
}
}
}

实现的功能大概是 按独立按键,LED数码管上显示什么数字,顺带中间,蜂鸣器以i hz 响一下。

实现任意一个乐谱

这里是天空之城的乐谱,其实乐谱有让ai帮我抓了一个出来,但是道理其实懂了就很好写谱子。
还有个东西,是一些做铺子的乐理知识,大概是12平均律,他的每个音调的频率,比如中央C的音调频率定为440Hz,那按 低中高,三个音调,平均每个相差一倍,那么低音就是220hz,高音就是440*2=880hz,然后每个音阶,他在钢琴上有7个白键,5个黑键,按照经典排布,音阶关系大概是全全半,全全全半的关系,同时再结合他的频率,按照 1是比440高的频率,按照 上一个音符 * 2^(1/12) 的关系,比440低的音符,按照440/ 2^(1/12) 的关系,推出来的音符频率表,再取整,得出来这个表:

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
1	262
1# 277
2 294
2# 311
3 330
4 349
4# 370
5 392
5# 415
6 440
6# 466
7 494
1 523
1# 554
2 587
2# 622
3 659
4 698
4# 740
5 784
5# 831
6 880
6# 932
7 988
1 1046
1# 1109
2 1175
2# 1245
3 1318
4 1397
4# 1480
5 1568
5# 1661
6 1760
6# 1865
7 1976

这里只算了12*3 = 36个音符,实际可以写的更全一点。
beep.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
#ifndef __BEEP_H__
#define __BEEP_H__

#include "delay.h"
#define BEEP P2_5


#define STOP 0
#define L1 1
#define L1_ 2
#define L2 3
#define L2_ 4
#define L3 5
#define L4 6
#define L4_ 7
#define L5 8
#define L5_ 9
#define L6 10
#define L6_ 11
#define L7 12
#define M1 13
#define M1_ 14
#define M2 15
#define M2_ 16
#define M3 17
#define M4 18
#define M4_ 19
#define M5 20
#define M5_ 21
#define M6 22
#define M6_ 23
#define M7 24
#define H1 25
#define H1_ 26
#define H2 27
#define H2_ 28
#define H3 29
#define H4 30
#define H4_ 31
#define H5 32
#define H5_ 33
#define H6 34
#define H6_ 35
#define H7 36

static void delay_500us(void)
{
unsigned int n;
TR1 = 0;
n = ((unsigned int)TH1 << 8) | TL1; // 回读补偿
n += 65075; // 65536 - 461(460.8 取整)
TH1 = n >> 8;
TL1 = n;
TF1 = 0;
TR1 = 1;
while (!TF1)
;
TR1 = 0;
}

void Beep_Time(unsigned int time) {
for(unsigned int i=0;i<time*2; i++) {
BEEP = !BEEP;
delay_500us();
}
}
#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
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
#include<8052.h>
#include "delay.h"
#include "intrins.h"
#include "timer0.h"
#include "beep.h"
// 四分音符为一个时间间隔
#define MUSIC_SPEED 500

__code unsigned int FreqTable[] = {
0,
63777, 63872, 63969, 64054, 64140, 64216, 64291, 64360, 64426, 64489, 64547, 64603,
64655, 64704, 64751, 64795, 64837, 64876, 64913, 64948, 64981, 65012, 65042, 65070,
65095, 65120, 65144, 65166, 65186, 65206, 65225, 65242, 65259, 65274, 65289, 65303,
};

__code unsigned char Music_LittleStar[] = {
13, 4,
13, 4,
20, 4,
20, 4,
22, 4,
22, 4,
20, 8,
0, 2,
18, 4,
18, 4,
17, 4,
17, 4,
15, 4,
15, 4,
13, 8,
0xFF,
};

__code unsigned char Music_SkyCity[] = {
STOP, 4,
STOP, 4,
STOP, 4,
M6, 2,
M7, 2,
H1, 4+2,
M7, 2,
H1, 4,
H3, 4,
M7, 4+4+4,
M3, 2,
M3, 2,
M6, 4+2,
M5, 2,
M6, 4,
H1, 4,
M5, 4+4+4,
M3, 4,
M4, 4+2,
M3, 2,
M4, 4,
H1, 4,
M3, 4+4,
STOP, 2,
H1, 2,
H1, 2,
H1, 2,
M7, 4+2,
M4_, 2,
M4_, 4,
M7, 4,
M7, 8,
STOP, 4,
M6, 2,
M7, 2,
H1, 4+2,
M7, 2,
H1, 4,
H3, 4,
M7, 4+4+4,
M3, 2,
M3, 2,
M6, 4+2,
M5, 2,
M6, 4,
H1, 4,
M5, 4+4+4,
M2, 2,
M3, 2,
M4, 4,
H1, 2,
M7, 2+2,
H1, 2+4,
H2, 2,
H2, 2,
H3, 2,
H1, 2+4+4,
H1, 2,
M7, 2,
M6, 2,
M6, 2,
M7, 4,
M5_, 4,
M6, 4+4+4,
H1, 2,
H2, 2,
H3, 4+2,
H2, 2,
H3, 4,
H5, 4,
H2, 4+4+4,
M5, 2,
M5, 2,
H1, 4+2,
M7, 2,
H1, 4,
H3, 4,
H3, 4+4+4+4,
M6, 2,
M7, 2,
H1, 4,
M7, 4,
H2, 2,
H2, 2,
H1, 4+2,
M5, 2+4+4,
H4, 4,
H3, 4,
H3, 4,
H1, 4,
H3, 4+4+4,
H3, 4,
H6, 4+4,
H5, 4,
H5, 4,
H3, 2,
H2, 2,
H1, 4+4,
STOP, 2,
H1, 2,
H2, 4,
H1, 2,
H2, 2,
H2, 4,
H5, 4,
H3, 4+4+4,
H3, 4,
H6, 4+4,
H5, 4+4,
H3, 2,
H2, 2,
H1, 4+4,
STOP, 2,
H1, 2,
H2, 4,
H1, 2,
H2, 2+4,
M7, 4,
M6, 4+4+4,
STOP, 4,
0xFF

};

unsigned char FreqSelect = 0;
unsigned char MusicSelect = 0;

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

FreqSelect = Music_SkyCity[MusicSelect];
if (FreqSelect != 0xFF) {
MusicSelect++;
delay(MUSIC_SPEED/4*Music_SkyCity[MusicSelect]);
MusicSelect++;
// MusicSelect %= 14;
TR0 = 0;
delay(5);
TR0 = 1;
} else {
TR0 = 0;
}
}
}

void Timer0_Routine() __interrupt(1) {
if(FreqTable[FreqSelect] != 0) {
TL0 = FreqTable[FreqSelect] % 256;
TH0 = FreqTable[FreqSelect] / 256;
BEEP = !BEEP;
}

}

FreqTable 这个定义了每个音符频率,对应的定时器的初始装载值,超过65535-这个值,定时器就会溢出重置进入中断,而我们要做的就是使用这个中断函数,当超过这个值之后,定时器中断里,不断的反转蜂鸣器的IO,为蜂鸣器提供一个PWM信号,这样子不断调整他的占空比,来达到输出不同音高的目的,这就是调整定时器装载值的目的。
而至于谱子,只是一个灵魂,可以移植,如果移植的话,只要把 频率对应的表 修改一下,其他定义完全不需要变。里面还有个小星星的谱子。