单片机入门学习2

51单片机入门学习记录(2)

教程:江科协

51单片机 Keil C语言

目录:

[LED点阵屏](#1.25 LED点阵屏)

1.25 LED点阵屏

过年好 单片机的学习停了一个星期 刚刚看到弹幕一句话:都学到这了 怎么能半途而废呢

啊啊啊啊可是才P21 一共有P38 才学到一半啊啊啊啊啊啊啊啊啊啊啊!

8*8个LED组成

image-20230125205121954

因为引脚共用 所以仍需采取逐行逐列扫描 扫描过快 显示就可以一起

开发板引脚对应关系

image-20230125205842157

image-20230125210214567

移位寄存器 74HC595

74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。

image-20230125210917876

image-20230125212522272

  • sfr(special function register):特殊功能寄存器声明

例:sfr P0 = 0x80;

声明P0口寄存器,物理地址为0x80

  • sbit(special bit):特殊位声明

例:sbit P0_1 = 0x81; 或 sbit P0_1 = P0^1;

声明P0寄存器的第1位

可位寻址/不可位寻址:每8个寄存器中,只有一个是可以位寻址的。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作

看到文件中 同一个地址有不同的名称?去搜了一下

image-20230125221948217

贴一个看到的博客

字节地址和位地址的区别:

一、位地址是字节地址中的某一位,在RAM 中,位地址20H是字节地址24H的最低位。

二、字节地址20H有8个位地址:从00H—07H,所以在用汇编去编程的时候,需要注意操作的地址是位地址还是字节地址。

三、把数据存放在含有位地址的字节地址中相连时,可以在程序中去改变这个数据的某一位,所以字节地址中可以存放8个位变量。

四、在用汇编去编程的时候,需要注意操作的地址是位地址还是字节地址。

五、把数据存放在含有位地址的字节地址中时,可以在程序中去改变这个数据的某一位,字节地址中可以存放8个位变量

六、位寻址取值范围就是0和1,字节寻址是0-255

image-20230125221647455

LED点阵屏显示图片

给一些位置进行重新申明

image-20230125223204219

RCLK在头文件里已经定义过了 这些只是相当于外号 并没有什么关系 改名~

  1. 定义一个函数用来把Byte写入八个引脚

​ 首先要把数据赋值给SER:取Byte的最高位 SER = Byte&0x80

​ 注意:把一个数赋值给一个位,原则是:非0即1,即这个数不是0 则赋给前面1 ,也就是这里0x80非0 则赋给SER1,当然也可以用if原则

  1. 给SELK时钟高电平让其移位

    首先上电后 SCK全是高电平 所以要先置0

测试74HC585芯片
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 <REGX52.H>

sbit RCK = P3^5;//P3的第五位,操作RCLK时等同于操作P3_5
sbit SCK = P3^6;//上升沿移位
sbit SER = P3^4;//上升沿锁存

void _74HC585_WriteByte(unsigned char Byte){ //把Byte写入八个引脚
unsigned char i;
for(i=0;i<8;i++){
SER = Byte&(0x80>>i);// [1]000 0000
SCK = 1;//时钟上电后会移动数据
SCK = 0;
}
RCK = 1;
RCK = 0;
}

void main(){
SCK = 0;//初始化
RCK = 0;
_74HC585_WriteByte(0xF0);
while(1){

}
}

hh发现我的板子没有74H595的LED灯(ˉ▽ˉ;)…😶

按照弹幕的说法可以给P0=0x00一个初值,此时LED点阵屏亮了一半,测试595芯片ok

对点阵屏进行操作,引脚关系详见

测试:以第一列为例

1
2
3
4
5
6
7
8
9
10
11
12
13
void Matrixled_ShowColumn(unsigned char Column,Data){ //第0列到第7列
_74HC585_WriteByte(Data);
P0 = ~(0x80>>Column);
}

void main(){
SCK = 0;//初始化
RCK = 0;
Matrixled_ShowColumn(0,0xAA);
while(1){

}
}

类似于数码管,如果多列显示未消抖延迟的话,会类似数码管(段选->位选->段选->位选->段选···会造成错乱),故需要Delay函数

使得(段选->位选**->延时->位清零**->段选->位选->段选)

1
2
3
P0 = ~(0x80>>Column);
Delay(1);
P0 = 0xFF;
测试对角线显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main(){
SCK = 0;//初始化
RCK = 0;
while(1){
Matrixled_ShowColumn(0,0x80);
Matrixled_ShowColumn(1,0x40);
Matrixled_ShowColumn(2,0x20);
Matrixled_ShowColumn(3,0x10);
Matrixled_ShowColumn(4,0x08);
Matrixled_ShowColumn(5,0x04);
Matrixled_ShowColumn(6,0x02);
Matrixled_ShowColumn(7,0x01);
}
}

af441434420dee18f87d43e8f0d9875

画笑脸
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 <REGX52.H>
#include "Delay.h"

sbit RCK = P3^5;//P3的第五位,操作RCLK时等同于操作P3_5
sbit SCK = P3^6;//上升沿移位
sbit SER = P3^4;//上升沿锁存

#define MATRIX_LED_PORT P0

/**
* @brief 74HC595写入一个字节
* @param 要写入的字节
* @retval 无
*/
void _74HC585_WriteByte(unsigned char Byte){ //把Byte写入八个引脚
unsigned char i;
for(i=0;i<8;i++){
SER = Byte&(0x80>>i);// [1]000 0000
SCK = 1;//时钟上电后会移动数据
SCK = 0;
}
RCK = 1;
RCK = 0;
}

/**
* @brief LED点阵屏显示一列数据
* @param Column要选择的列(0~7),0在最左边
* @param Data选择列显示的数据,高位在上,1为亮
* @retval 无
*/
void MatrixLED_ShowColumn(unsigned char Column,Data){ //第0列到第7列
_74HC585_WriteByte(Data);
MATRIX_LED_PORT = ~(0x80>>Column);
Delay(1);
MATRIX_LED_PORT = 0xFF;
}

void main(){
SCK = 0;//初始化
RCK = 0;
while(1){
MatrixLED_ShowColumn(0,0x3C);
MatrixLED_ShowColumn(1,0x42);
MatrixLED_ShowColumn(2,0xA9);
MatrixLED_ShowColumn(3,0x85);
MatrixLED_ShowColumn(4,0x85);
MatrixLED_ShowColumn(5,0xA9);
MatrixLED_ShowColumn(6,0x42);
MatrixLED_ShowColumn(7,0x3C);
}
}

b7f52e97a78071484a193192844c6dc

LED点阵屏显示动画

先模块化一下

显示动画可以先存一个数组 然后逐个显示(从左往右)

up主给了一个字模提取很便捷的软件

image-20230126130116413

MatrixLED.h

1
2
3
4
5
6
7
8
9
#ifndef __MATRIX_LED_H__
#define __MATRIX_LED_H__

void _74HC585_WriteByte(unsigned char Byte);
void MatrixLED_ShowColumn(unsigned char Column,Data);
void MatrixLED_Init();


#endif

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

sbit RCK = P3^5;//P3的第五位,操作RCLK时等同于操作P3_5
sbit SCK = P3^6;//上升沿移位
sbit SER = P3^4;//上升沿锁存

#define MATRIX_LED_PORT P0

/**
* @brief 74HC595写入一个字节
* @param 要写入的字节
* @retval 无
*/
void _74HC585_WriteByte(unsigned char Byte){ //把Byte写入八个引脚
unsigned char i;
for(i=0;i<8;i++){
SER = Byte&(0x80>>i);// [1]000 0000
SCK = 1;//时钟上电后会移动数据
SCK = 0;
}
RCK = 1;
RCK = 0;
}

/**
* @brief 点阵屏初始化
* @param 无
* @retval 无
*/
void MatrixLED_Init(){
SCK = 0;
RCK = 0;
}

/**
* @brief LED点阵屏显示一列数据
* @param Column要选择的列(0~7),0在最左边
* @param Data选择列显示的数据,高位在上,1为亮
* @retval 无
*/
void MatrixLED_ShowColumn(unsigned char Column,Data){ //第0列到第7列
_74HC585_WriteByte(Data);
MATRIX_LED_PORT = ~(0x80>>Column);
Delay(1);
MATRIX_LED_PORT = 0xFF;
}

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

unsigned char code Animation[]={0x11,0x12,0x1C,0x32,0xD1,0x1F,0x00,0x1E,0x12,0x12,0x12,0x1E,0x00,0x00,0x49,0x2A,
0x00,0x3E,0x2A,0xFE,0x2A,0x3E,0x00,0x00,0x00,0xFA,0x00,0x00,0xFA,0x00,0x00,0xFA,};

void main(){
unsigned char i,offset=0,count=0;
MatrixLED_Init();
while(1){
for(i=0;i<8;i++){
MatrixLED_ShowColumn(i,Animation[i+offset]);
}
count++;
if(count>10){
offset++;
count = 0;
if(offset>24){//防止数组溢出乱码
offset = 0;
}
}
}
}

一般数组是会放在ram里会很浪费 要给他放在flash里unsigned char code Animation

1.27 DS1302实时时钟

时钟显示+调节时间

RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片

定时器的时钟会占用CPU时间且精度不高断电就会清零

实时时钟内部带一个备用电池,断电后会提供能量

image-20230127101008185

内部结构图

image-20230127101513157

内部RTC寄存器

image-20230127102711156

WP:write protect,置1的话写入无效

目标:在寄存器写入单片机的数据、在寄存器读出时钟芯片(DS1302)的数据

时序定义

上升沿时地址,下降沿是数据?

image-20230127103805054

写入数据:

先把CE置1,发一个命令字(Command Byte)决定读写位置与方式,IO口写入最低位决定是读还是写(最开始),SCLK一个上升沿读入数据,归零后再给一个上升沿读取新的数据,依次循环八位···操作完后再把CE置0

读取数据:

按照上面的依然需要八个上升沿决定读写位置和方式,W给1单片机接收到命令值,紧跟着时钟的下降沿开始读出适中的数据

1.28 DS1302实时时钟&可调时钟

实时时钟

先对三个端口进行定义(方便一点)

image-20230128192041831

1
2
3
sbit DS1302_SCLK = P3^6;
sbit DS1302_IO = P3^4;
sbit DS1302_CE = P3^5;

^是位选,_是已经定义的, ^其实是PIO口的地址

模拟时序

操作前要先初始化,CE默认是0,但单片机上电时默认都是1

1
2
3
4
void DS1302_Init(void){
DS1302_CE = 0;
DS1302_SCLK = 0;
}
单字节写

先高电平使能,后从低到高取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void DS1302_WriteByte(unsigned char Command,Data){
DS1302_CE = 1;
unsigned char i;
for(i=0;i<8;i++){
DS1302_IO = Command&(0x01<<i);//取出最低位
DS1302_SCLK = 1
DS1302_SCLK = 0
}
for(i=0;i<8;i++){
DS1302_IO = Data&(0x01<<i);//取出最低位
DS1302_SCLK = 1
DS1302_SCLK = 0
}
DS1302_CE = 0;
}
单字节读取

如果按照上面的话在循环结束后是已经完整地进行了八个上下沿,第一个数据已经被读取了

image-20230128194252363

读取是要比写入少一个脉冲的 因为最后一次是立马读出数据的,所以要更换顺序,使得第一次循环结束后所在的位置左边都是与写入有关的

测试

显示了255?

看到弹幕说是有写入保护,要在LCD_Init();后写DS1302_WriteByte(0x8E,0x00);,但是好像也没有用欸

和up主的源代码对比发现少了一个DS1302_IO=0; //读取后将IO设置为0,否则读出的数据会出错

正常了但是很不稳定 但是不怎么懂原理 这一节的读取好不容易 不太理解 到时候再看一下(要加上接触芯片保护的)

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

unsigned char Second;

void main(){
LCD_Init();
LCD_ShowString(1,1,"RTC");
DS1302_WriteByte(0x8E,0x00);
DS1302_WriteByte(0x80,0x03);

while(1){
Second = DS1302_ReadByte(0x81);
LCD_ShowNum(2,1,Second,3);
}
}

但是到009后直接变成了016

原因:DS1302内部的寄存器是以BDC码进行存储的

image-20230128202329458

9为0000 1001 10为 0001 0000 16为0001 0110

解决方法:以16进制显示LCD_ShowHexNum(2,1,Second,3);,或者LCD_ShowNum(2,1,Second/16*10+Second%16,3);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned char Second,Minite;

void main(){
LCD_Init();
LCD_ShowString(1,1,"RTC");
DS1302_WriteByte(0x8E,0x00);//解除芯片保护
DS1302_WriteByte(0x80,0x55);
DS1302_WriteByte(0x82,0x03);


while(1){
Second = DS1302_ReadByte(0x81);
Minite = DS1302_ReadByte(0x83);
LCD_ShowNum(2,1,Second/16*10+Second%16,2);
LCD_ShowNum(2,3,Minite/16*10+Minite%16,2);
}
}

秒分钟小时对应的参考如下

image-20230127102711156

为了每次可以不用查表,可以配置一个表格

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

//引脚定义
sbit DS1302_SCLK = P3^6;
sbit DS1302_IO = P3^4;
sbit DS1302_CE = P3^5;

//寄存器写入地址/指令定义
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84
#define DS1302_DATE 0x86
#define DS1302_MONTH 0x88
#define DS1302_DAY 0x8A
#define DS1302_YEAR 0x8C
#define DS1302_WP 0x8E

unsigned char DS1302_Time[] = {23,1,28,20,27,50,6};//当前时间年月日时分秒星期

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

/**
* @brief DS1302写一个字节
* @param Command 命令字/地址
* @param Data 要写入的数据
* @retval 无
*/
void DS1302_WriteByte(unsigned char Command,Data){
unsigned char i;
DS1302_CE = 1;
for(i=0;i<8;i++){
DS1302_IO = Command&(0x01<<i);//取出最低位
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
for(i=0;i<8;i++){
DS1302_IO = Data&(0x01<<i);//取出最低位
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
DS1302_CE = 0;
}

/**
* @brief DS1302读一个字节
* @param Command 命令字/地址
* @retval 读出的数据
*/
unsigned char DS1302_ReadByte(unsigned char Command){
unsigned char i,Data=0x00;
Command|=0x01;//读的地址要比写的地址多一位(比如81h和80h),直接定义
DS1302_CE = 1;
for(i=0;i<8;i++){
DS1302_IO = Command&(0x01<<i);//取出最低位
DS1302_SCLK = 0;
DS1302_SCLK = 1;
}
for(i=0;i<8;i++){
DS1302_SCLK = 1;
DS1302_SCLK = 0;
if(DS1302_IO){Data|=(0x01<<i);}
}
DS1302_CE = 0;
DS1302_IO=0; //读取后将IO设置为0,否则读出的数据会出错
return Data;
}

/**
* @brief DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到DS1302中
* @param 无
* @retval 无
*/
void DS1302_SetTime(void){
DS1302_WriteByte(DS1302_WP,0x00);//解除芯片保护
DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);//十进制转BCD码后写入
DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
DS1302_WriteByte(DS1302_WP,0x80);
}

/**
* @brief DS1302读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
* @param 无
* @retval 无
*/
void DS1302_ReadTime(void){
unsigned char Temp;
Temp=DS1302_ReadByte(DS1302_YEAR);
DS1302_Time[0]=Temp/16*10+Temp%16;//BCD码转十进制后读取
Temp=DS1302_ReadByte(DS1302_MONTH);
DS1302_Time[1]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_DATE);
DS1302_Time[2]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_HOUR);
DS1302_Time[3]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_MINUTE);
DS1302_Time[4]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_SECOND);
DS1302_Time[5]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_DAY);
DS1302_Time[6]=Temp/16*10+Temp%16;
}

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

unsigned char Second,Minite;

void main(){
LCD_Init();
LCD_ShowString(1,1,"RTC");
LCD_ShowString(1,1," - - ");//静态字符初始化显示
LCD_ShowString(2,1," : : ");

DS1302_SetTime();//设置时间

while(1){
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);//显示秒
}
}

好难···有很多细节不是很理解···只能大体知道点···

2.1 蜂鸣器

介绍原理

按驱动方式分类:

有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定

无源蜂鸣器:需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音

(本节内用单片机提供方波脉,交流振荡发声,电能转化为机械能,但蜂鸣器不能长时间一直通电,容易造成烧毁)

单片机的I/O口不能直接驱动蜂鸣器,必须要过ULN2003芯片

image-20230201130453834

ULN2003芯片(一边用于五线四相步进电机的驱动)

image-20230201131538757

所以只需控制P15口就可以产生振动频率输出01来控制蜂鸣器(好像有说新版变成了P25口,到时候编程的时候测试一下)

注意:单片机上电的时候默认是高电平,P15默认取反为0,故OUT5为0,即默认会有电流,蜂鸣器会响一下,且始终通电流

播放音乐的参数:音高、时长

image-20230201133730841

根据频率值控制定时器产生相应频率的计时,从而控制中断控制IO口的反转

频率的关系,可以计算定时器的重装值:

注意:

  1. 一个机器周期=12个晶振周期

  2. 翻转IO口控制频率时,翻转两次才是一个周期image-20230201135730573

  3. 所以翻转的时间时1/2

  4. 重装载值将高八位取出放到TH0,第八位放在TL0,再控制定时器中断

image-20230201140206405

播放提示音

目的:按键松开瞬间给一个按键提示音

注意:

  1. 要把数码管模块代码中清零部分删除(此次是静态显示)

  2. 蜂鸣器标准频率是1000hz

up主最后是用了默认的标准频率,在他的基础上我修改了为可以自己设定频率(可选参数)和延长的时间

不过现在才发现,用C语言定义带默认参数/不定参数的函数不像C++那样便捷

参考了博客要写些代码···嗯好麻烦好麻烦的···

所以还是简单些说明,如果不自定义频率的参数就要传入0吧···下次有时间写一下cpp文件试试?(大概率是不会去写的hh)

2.3 蜂鸣器

感觉越临近开学越不好好学了,难道是开学焦虑综合征(fp)应该又是太多东西堆积所以顾不过来的时候,就好想摆烂

真的感觉在多线程前会非常力不从心且一件都很难做好,还会搞得身心很疲惫

用板子试了一下 确实是P2^5

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Buzzer.h"

unsigned char keynumber;

void main(){
Nixie(1,0);//使上电是0 而不是全亮的状态
while(1){
keynumber = Key();
if(keynumber){
Buzzer_Time(1000,0);
Nixie(1,keynumber);//数码管静态显示
}
}
}

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

//蜂鸣器端口:
sbit Buzzer = P2^5;

/**
* @brief 蜂鸣器按照标准1000hz延时
* @param 无
* @retval 无
*/
void Buzzer_Delay500us() //@12.000MHz
{
unsigned char i;

i = 247;
while (--i);
}

/**
* @brief 蜂鸣器发声
* @param xms 发声的时长,范围:0~32767
* @param 可选参数frequency(1~500) 因为delay的参数最小是1,如果不传入频率默认要写入0
* @retval 无
以500hz的频率响100ms
for(i=0;i<100;i++){
Buzzer = !Buzzer;
Delay(1);//每隔1ms翻转一次,周期为2ms,频率为500hz
}
*/
void Buzzer_Time(unsigned int xms,frequency){
unsigned int i,Delay_Time,For_Time;

if(frequency){
Delay_Time = 500/frequency;
For_Time = xms/Delay_Time;
for(i=0;i<For_Time;i++){
Buzzer = !Buzzer;
Delay(Delay_Time);//每隔1ms翻转一次,周期为2ms,频率为500hz
}
}
/*按标准的1000hz*/
else{
for(i=0;i<xms*2;i++){
Buzzer = !Buzzer;
Buzzer_Delay500us();//延时0.5ms
}
}
}

音乐

受不鸟了听不下去了,直接copy了…记录一下听到了32:20,如果下次需要的话再继续看…

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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#include <REGX52.H>
#include "Delay.h"
#include "Timer0.h"

//蜂鸣器端口定义
sbit Buzzer=P1^5;

//播放速度,值为四分音符的时长(ms)
#define SPEED 500

//音符与索引对应表,P:休止符,L:低音,M:中音,H:高音,下划线:升半音符号#
#define P 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

//索引与频率对照表
unsigned int FreqTable[]={
0,
63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,
};

//乐谱
unsigned char code Music[]={
//音符,时值,

//1
P, 4,
P, 4,
P, 4,
M6, 2,
M7, 2,

H1, 4+2,
M7, 2,
H1, 4,
H3, 4,

M7, 4+4+4,
M3, 2,
M3, 2,

//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,

//3
M3, 4+4,
P, 2,
H1, 2,
H1, 2,
H1, 2,

M7, 4+2,
M4_,2,
M4_,4,
M7, 4,

M7, 8,
P, 4,
M6, 2,
M7, 2,

//4
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,

//5
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,

//6
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,

//7
H2, 4+4+4,
M5, 2,
M5, 2,

H1, 4+2,
M7, 2,
H1, 4,
H3, 4,

H3, 4+4+4+4,

//8
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,

//9
H3, 4+4+4,
H3, 4,

H6, 4+4,
H5, 4,
H5, 4,

H3, 2,
H2, 2,
H1, 4+4,
P, 2,
H1, 2,

//10
H2, 4,
H1, 2,
H2, 2,
H2, 4,
H5, 4,

H3, 4+4+4,
H3, 4,

H6, 4+4,
H5, 4+4,

//11
H3, 2,
H2, 2,
H1, 4+4,
P, 2,
H1, 2,

H2, 4,
H1, 2,
H2, 2+4,
M7, 4,

M6, 4+4+4,
P, 4,

0xFF //终止标志
};

unsigned char FreqSelect,MusicSelect;

void main()
{
Timer0Init();
while(1)
{
if(Music[MusicSelect]!=0xFF) //如果不是停止标志位
{
FreqSelect=Music[MusicSelect]; //选择音符对应的频率
MusicSelect++;
Delay(SPEED/4*Music[MusicSelect]); //选择音符对应的时值
MusicSelect++;
TR0=0;
Delay(5); //音符间短暂停顿
TR0=1;
}
else //如果是停止标志位
{
TR0=0;
while(1);
}
}
}

//原本是1ms中断一次,现改为直接进来中断的时候
void Timer0_Routine() interrupt 1
{
if(FreqTable[FreqSelect]) //如果不是休止符
{
/*取对应频率值的重装载值到定时器*/
TL0 = FreqTable[FreqSelect]%256; //设置定时初值
TH0 = FreqTable[FreqSelect]/256; //设置定时初值
Buzzer=!Buzzer; //翻转蜂鸣器IO口
}
}

单片机入门学习2
http://gigiboo.github.io/2023/02/14/“单片机2”/
作者
Gigiboo
发布于
2023年2月14日
许可协议