(学习日记)2023.4.22

本文介绍了如何使用51单片机控制蜂鸣器,包括基础知识、按键控制发声时间和实现音乐播放。通过定时器0初始化和中断服务函数,实现了按乐谱播放‘天空之城’的简单音乐播放功能。
摘要由CSDN通过智能技术生成

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

一、51:蜂鸣器

  1. 蜂鸣器介绍
    在这里插入图片描述

  2. 驱动电路
    在这里插入图片描述

  3. ULN2003
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  4. 音符对照频率
    在这里插入图片描述
    在这里插入图片描述

  5. 常见乐谱
    在这里插入图片描述
    在这里插入图片描述

二、51:按键控制蜂鸣器发声时间

  1. 寻找引脚,很显然是P25
    在这里插入图片描述
    在这里插入图片描述
  2. 编写延时函数
    为了实现震荡,首先编写一个延时函数,保证定时震荡
/**
  * @brief  蜂鸣器私有延时函数,延时500us
  * @param  无
  * @retval 无
  */
void Buzzer_Delay500us()		//@12.000MHz
{
	unsigned char i;

	_nop_();
	i = 247;
	while (--i);
}
  1. 编写发声函数
    让蜂鸣器在规定时间内发声
//蜂鸣器端口:
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:蜂鸣器唱歌——天空之城

  1. 自动重装载值(TH0TL0)计算
    在8051系列微控制器中,定时器0的自动重装载值(Auto-Reload Value)用于设置定时器的计时初值。定时器0是一个8位定时器,其计时范围为0到255。当定时器0计数器达到255时,会自动将计数器重置为自动重装载值,并触发定时器0溢出中断。
计算自动重装载值(TH0,TL0)的方法取决于所需的定时周期和时钟频率。下面是一个示例计算步骤:

- 确定所需的定时周期。假设我们希望定时器01毫秒触发一次中断。
- 确定系统的时钟频率。假设系统的时钟频率为12.000MHz。
- 计算定时器的计数周期。由于定时器0是一个8位定时器,它可以计数的最大值为255。因此,定时器的计数周期为256- 计算所需的计数值。要实现1毫秒的定时周期,需要确定每个计数周期的时间。根据时钟频率和计数周期,可以计算出每个计数周期的时间(以秒为单位)。
- 计数周期时间 = 1 / (时钟频率 / 计数周期) = 1 / (12.000MHz / 256)21.33纳秒
注意:以上计算假设时钟频率和计数周期的单位相同,如均为Hz或均为MHz。
- 计算自动重装载值。根据所需的定时周期,可以计算出需要多少个计数周期才能达到该定时周期。然后,将计数周期数减去1,得到自动重装载值。根据上面的例子,每毫秒需要多少个计数周期:
计数周期数 = (1毫秒 / 计数周期时间) = (1毫秒 / 21.33纳秒)46882个计数周期
自动重装载值 = 计数周期数 - 146882 - 1 = 46881
- 因为定时器0是一个8位定时器,所以自动重装载值需要分为高8位(TH0)和低8位(TL0)。
TH0 = 自动重装载值 / 256 = 46881 / 256183
TL0 = 自动重装载值 % 256 = 46881 % 256 = 105

这样,将TH0设置为183,TL0设置为105,定时器0将每1毫秒触发一次中断。

  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,	//高频
};
  1. 编写 定时器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毫秒触发一次中断,从而执行相应的中断服务函数。

  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 口状态被翻转,从而控制蜂鸣器的发声和停止。具体的频率和频率表的定义和赋值在代码中并未给出,你可以根据具体的需求自行添加或修改相应的代码。

  1. 编写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()函数实现了根据乐谱数组中的音符和时值控制蜂鸣器的播放。不断循环播放乐谱中的音符,直到遇到终止标志为止。

  1. 定义乐谱即可
    这里放一首天空之城

//乐谱
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	//终止标志
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YunB西风英

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值