写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。
标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。
点击此处进入学习日记的总目录
2023.4.22
- 一、51:蜂鸣器
- 二、51:按键控制蜂鸣器发声时间
- 三、51:蜂鸣器唱歌——天空之城
一、51:蜂鸣器
-
蜂鸣器介绍
-
驱动电路
-
ULN2003
-
音符对照频率
-
常见乐谱
二、51:按键控制蜂鸣器发声时间
- 寻找引脚,很显然是
P25
- 编写延时函数
为了实现震荡,首先编写一个延时函数,保证定时震荡
/**
* @brief 蜂鸣器私有延时函数,延时500us
* @param 无
* @retval 无
*/
void Buzzer_Delay500us() //@12.000MHz
{
unsigned char i;
_nop_();
i = 247;
while (--i);
}
- 编写发声函数
让蜂鸣器在规定时间内发声
//蜂鸣器端口:
sbit Buzzer=P2^5;
/**
* @brief 蜂鸣器发声
* @param ms 发声的时长,范围:0~32767
* @retval 无
*/
void Buzzer_Time(unsigned int ms)
{
unsigned int i;
for(i=0;i<ms*2;i++)
{
Buzzer=!Buzzer;
Buzzer_Delay500us();
}
}
在 Buzzer_Time
函数中,有一个循环语句 for(i=0;i<ms*2;i++)
。这个循环的目的是让蜂鸣器在指定的时长内产生一系列的脉冲
。
当调用 Buzzer_Time(1000)
时,参数 ms
的值为 1000
,表示要让蜂鸣器响 1000
毫秒(1秒)的时间。
在循环中,i
的初始值为 0
,每次循环结束后 i
增加 1
。由于循环条件是 i<ms*2
,也就是 i<2000
,所以这个循环将会执行 2000
次。
在循环体内部,首先会对蜂鸣器状态进行取反,然后调用Buzzer_Delay500us()
函数延时约为 500 微秒。因此,每次循环会产生一次蜂鸣器的状态变化和延时,持续约为 500
微秒。
总共有 2000
次循环,所以总的延时时间为 2000 * 500 微秒 = 1000 毫秒(1秒)
。
三、51:蜂鸣器唱歌——天空之城
- 自动重装载值(
TH0
,TL0
)计算
在8051系列微控制器中,定时器0的自动重装载值(Auto-Reload Value
)用于设置定时器的计时初值。定时器0是一个8位定时器,其计时范围为0到255。当定时器0计数器达到255时,会自动将计数器重置为自动重装载值,并触发定时器0溢出中断。
计算自动重装载值(TH0,TL0)的方法取决于所需的定时周期和时钟频率。下面是一个示例计算步骤:
- 确定所需的定时周期。假设我们希望定时器0每1毫秒触发一次中断。
- 确定系统的时钟频率。假设系统的时钟频率为12.000MHz。
- 计算定时器的计数周期。由于定时器0是一个8位定时器,它可以计数的最大值为255。因此,定时器的计数周期为256。
- 计算所需的计数值。要实现1毫秒的定时周期,需要确定每个计数周期的时间。根据时钟频率和计数周期,可以计算出每个计数周期的时间(以秒为单位)。
- 计数周期时间 = 1 / (时钟频率 / 计数周期) = 1 / (12.000MHz / 256) ≈ 21.33纳秒
注意:以上计算假设时钟频率和计数周期的单位相同,如均为Hz或均为MHz。
- 计算自动重装载值。根据所需的定时周期,可以计算出需要多少个计数周期才能达到该定时周期。然后,将计数周期数减去1,得到自动重装载值。根据上面的例子,每毫秒需要多少个计数周期:
计数周期数 = (1毫秒 / 计数周期时间) = (1毫秒 / 21.33纳秒) ≈ 46882个计数周期
自动重装载值 = 计数周期数 - 1 ≈ 46882 - 1 = 46881
- 因为定时器0是一个8位定时器,所以自动重装载值需要分为高8位(TH0)和低8位(TL0)。
TH0 = 自动重装载值 / 256 = 46881 / 256 ≈ 183
TL0 = 自动重装载值 % 256 = 46881 % 256 = 105
这样,将TH0
设置为183,TL0
设置为105,定时器0将每1毫秒触发一次中断。
- 计算各频率重装载值
//音符与索引对应表,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, //高频
};
- 编写 定时器0初始化函数
Timer0Init()
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0Init(void)
{
TMOD &= 0xF0; // 设置定时器模式
TMOD |= 0x01; // 设置定时器模式
TL0 = 0x18; // 设置定时初值
TH0 = 0xFC; // 设置定时初值
TF0 = 0; // 清除TF0标志
TR0 = 1; // 定时器0开始计时
ET0 = 1; // 允许定时器0中断
EA = 1; // 允许总中断
PT0 = 0; // 设置定时器0中断优先级
}
TMOD &= 0xF0;
和TMOD |= 0x01;
:对定时器模式寄存器 TMOD 进行设置,保留高4位的原值,并将低4位设置为 0x01,表示将定时器0设置为 16位工作模式。TL0 = 0x18;
和TH0 = 0xFC;
:分别设置 Timer0 的低字节和高字节的初始值,用于定时器的计时。这些值根据具体的时钟频率和所需的定时周期进行设置。(因为后面会设置,所以此处初始化不存在影响)TF0 = 0;
:清除 Timer0 溢出标志,确保 Timer0 中断开始时不会立即触发。TR0 = 1;
:启动定时器0,开始计时。ET0 = 1;
:允许定时器0中断,使得当定时器0溢出时可以触发中断服务函数。EA = 1;
:允许总中断,使得系统能够响应所有中断请求。PT0 = 0;
:设置定时器0中断的优先级为最低优先级。
这个函数的目的是将定时器0设置为一个 1 毫秒的定时器,在12.000MHz的时钟频率下工作。通过设置相应的寄存器值和使能中断,可以让定时器0每隔1毫秒触发一次中断,从而执行相应的中断服务函数。
- 编写 Timer0 中断的处理函数
void Timer0_Routine() interrupt 1
{
if (FreqTable[FreqSelect]) // 如果不是休止符
{
/* 取对应频率值的重装载值到定时器 */
TL0 = FreqTable[FreqSelect] % 256; // 设置定时初值
TH0 = FreqTable[FreqSelect] / 256; // 设置定时初值
Buzzer = !Buzzer; // 翻转蜂鸣器IO口
}
}
void Timer0_Routine() interrupt 1
:定义了一个中断服务函数Timer0_Routine()
,其中interrupt 1
表示这个函数是针对中断号 1 的处理函数。if (FreqTable[FreqSelect])
:检查FreqTable[FreqSelect]
的值是否非零,判断是否为休止符(停止蜂鸣器发声)。TL0 = FreqTable[FreqSelect] % 256;
和TH0 = FreqTable[FreqSelect] / 256;
:将FreqTable[FreqSelect]
的值分别赋值给 TL0 和 TH0 寄存器,以设置定时器的计时初值。其中%
表示取余操作,/
表示整数除法。Buzzer = !Buzzer;
:通过对Buzzer
变量进行逻辑取反操作,翻转蜂鸣器的 I/O 口状态。这样可以在定时器中断中控制蜂鸣器的开关,实现发声效果。
该中断服务函数根据 FreqTable[FreqSelect]
的值来控制蜂鸣器的频率和发声,当 FreqTable[FreqSelect]
不为零时,定时器的初值被设置为对应的频率值,同时蜂鸣器的 I/O 口状态被翻转,从而控制蜂鸣器的发声和停止。具体的频率和频率表的定义和赋值在代码中并未给出,你可以根据具体的需求自行添加或修改相应的代码。
- 编写
main
函数
//播放速度,值为四分音符的时长(ms)
#define SPEED 500
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);
}
}
}
在这段代码中,main()
函数是程序的主要入口点。以下是main()
函数的解释:
- 首先调用
Timer0Init()
函数进行定时器0的初始化,该函数会设置定时器0的模式、初值和使能定时器0的中断。 - 进入一个无限循环(
while(1)
),用于不断地处理音乐的播放。 - 在循环中,首先判断当前乐谱位置是否为终止标志(
Music[MusicSelect] != 0xFF
)。如果不是终止标志,说明还有音符需要播放。 - 获取当前音符对应的频率值,通过读取
Music
数组中的值(FreqSelect = Music[MusicSelect]
)。然后递增MusicSelect
的值,用于指向这一个音符的持续时间。 - 使用延迟函数(
Delay(SPEED/4*Music[MusicSelect])
)来延迟一段时间,时长为当前音符的时值乘以播放速度。这个延迟函数是根据乐谱中音符的时值来控制音符的持续时间。 - 在播放一个音符之前,先停止定时器0(
TR0 = 0
),然后进行一个短暂的停顿(Delay(5)
),再重新启动定时器0(TR0 = 1
)。这样可以在音符之间产生一个短暂的停顿,以区分不同音符。 - 如果当前乐谱位置是终止标志,表示音乐播放完毕,这时停止定时器0(
TR0 = 0
),然后进入一个无限循环(while(1)
),程序会一直停在这个循环中。
通过以上步骤,main()
函数实现了根据乐谱数组中的音符和时值控制蜂鸣器的播放。不断循环播放乐谱中的音符,直到遇到终止标志为止。
- 定义乐谱即可
这里放一首天空之城
//乐谱
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 //终止标志
};