目录
- 系列文章目录
- 前言
- 一、效果展示
- 二、原理分析
- 三、各模块代码
- 1、音乐数据头文件
- 2、定时器0
- 3、定时器1
- 四、主函数
- 附录
系列文章目录
前言
之前有做过一个生日礼物:
基于51单片机的心形流水灯+无源蜂鸣器播放音乐
能播放流水灯,同时播放生日快乐歌,现在做一个只有音乐播放的。
用的是普中A2开发板,单片机是STC89C52RC,用到了板上的无源蜂鸣器。
【频率】12T@11.0592MHz
效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。
一、效果展示
二、原理分析
1、声音的产生
声音的产生是由于物体的振动,例如,人说话是靠声带振动。无源蜂鸣器也是一样,通电的时候,电流流过蜂鸣器内部的线圈,线圈产生磁场,把内部的永磁体吸过来,断电的时候,线圈无磁场,永磁体回到原来位置,永磁铁带动振动薄膜来回运动。如果通电0.5ms,断电0.5ms,再通电0.5ms,如此反复,则振动的周期是1ms,就会发出1KHz的声音,改变通电或断电的时间,就可以改变频率了。
2、音符、时值
不同的音符对应不同的频率,不同的频率对应不同的定时器初值(通过定时器中断函数让无源蜂鸣器的引脚的电平翻转),这个需要提前计算好数据。不懂的建议看一下江协科技的51单片机入门教程,讲得很详细。
时值就是每个音符持续的时间,以《阳光彩虹小白马》为例,♩=96,表示1分钟有96个四分音符,即每个四分音符的时长为60/96s=0.625s=625ms,即16分音符的时长为625ms/4=156.25ms,取整为156ms(由于音符之间有短暂的停顿和代码运行误差,节奏偏慢,本代码中改成了145ms),以此为最小单位(因为简谱中有16分音符)。
定时器0控制时值,定时器1控制音符,并且要设置定时器1的优先级较高,否则无源蜂鸣器的发声会收到影响。
用这两个定时器就可以控制无源蜂鸣器发出音乐了,主函数的主循环中可以执行其他代码。
文末有《阳光彩虹小白马》的简谱。
3、注意事项
相邻两个音符之间需要有一定的间隔,否则如果两个相邻的音符是一样的话,就分不出是两个音符了。代码中设置了5ms的时间间隔,这个也是上面所说的节奏偏慢的原因之一。
三、各模块代码
1、音乐数据头文件
h文件
#ifndef __MUSIC_H__
#define __MUSIC_H__//十六分音符的时长(ms)
//一分钟96个四分音符,根据计算,十六分音符的时长约为156ms(156.25ms)
//实测播放偏慢,所以将时长改小一点,改成145ms
# define Duration 145//音符与索引对应表,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 code FreqTable[]={
0, //休止符,即进入定时器1中断程序后之后,蜂鸣器电平不翻转
63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64524,
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[]={//两个一组,分别是音符和时值
//音符(频率对应的定时器重装值)
//时值(十六分音符时长的倍数)/*《阳光彩虹小白马》*/
//原来是G调,这里按C调播放0, //第0个数据为0,目的是主函数中开启中断后,能立刻进入定时器0的中断函数进行频率和时值的赋值//怎么能
P,4,P,4,P,2,L5,2,L6,2,M1,2,//哭呢,一切会
M5,2,M3,6,P,2,M5,2,M2,2,M1,2,//好哒,一切都
M3,2,M2,6,P,2,M5,2,M2,2,M1,2,//去吧,你就得
M2,2,M2,6,P,2,M3,2,M1,2,L6,2,//想着。既然没
M2,2,M1,6,P,2,L5,2,L6,2,M1,2,//办法,还很他
M5,2,M3,6,P,2,M5,2,M2,2,M1,2,//干嘛,还管它
M3,2,M2,6,P,2,M5,2,M2,2,M1,2,//干嘛,心里要
M3,2,M2,6,P,2,M3,2,L6,2,M1,2,//记得。你是
M1,2,M1,6,P,4,P,2,M3,1,M4,1,//内内个内内内个内个内内
M5,2,M5,1,M3,1,M5,2,M5,2,M5,1,M3,1,M5,1,M6,1,M5,2,M5,2,//内内个内内内个内个内内
M5,2,M5,1,M3,1,M5,2,M5,2,M5,1,M3,1,M5,1,M6,1,M5,2,M5,2,//阳光彩虹小白马,
M5,2,M3,2,M2,2,M1,2,L5,2,M1,2,M1,4,//滴滴哒滴滴哒。
M3,2,M3,1,M2,1,M3,2,M2,2,M1,6,P,2,0xFF, //终止标记};#endif
2、定时器0
h文件
#ifndef __TIMER0_H__
#define __TIMER0_H__void Timer0_Init(void);#endif
c文件
#include <REGX52.H>/*** 函 数:定时器0初始化* 参 数:无* 返 回 值:无*/
void Timer0_Init(void)
{
// AUXR&=0x7F; //定时器时钟12T模式(STC89C52RC是12T单片机,无需设置)TMOD&=0xF0; //设置定时器模式(高四位不变,低四位清零)TMOD|=0x01; //设置定时器模式(通过低四位设为16位不自动重装)TL0=0x18; //设置定时初值,定时1ms,12T@11.0592MHzTH0=0xFC; //设置定时初值,定时1ms,12T@11.0592MHzTF0=0; //清除TF0标志TR0=1; //定时器0开始计时ET0=1; //打开定时器0中断允许EA=1; //打开总中断PT0=0; //当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}/*定时器中断函数模板
void Timer0_Routine() interrupt 1 //定时器0中断函数
{static unsigned int T0Count; //定义静态变量TL0=0x18; //设置定时初值,定时1ms,12T@11.0592MHzTH0=0xFC; //设置定时初值,定时1ms,12T@11.0592MHzT0Count++;if(T0Count>=1000){T0Count=0;}
}
*/
3、定时器1
h文件
#ifndef __TIMER1_H__
#define __TIMER1_H__void Timer1_Init(void);#endif
c文件
#include <REGX52.H>/*** 函 数:定时器1初始化* 参 数:无* 返 回 值:无*/
void Timer1_Init(void)
{
// AUXR&=0xBF; //定时器时钟12T模式(STC89C52RC是12T单片机,无需设置)TMOD&=0x0F; //设置定时器模式(低四位不变,高四位清零)TMOD|=0x10; //设置定时器模式(通过高四位设为16位不自动重装的模式)TL1=0x66; //设置定时初值,定时1ms,12T@11.0592MHzTH1=0xFC; //设置定时初值,定时1ms,12T@11.0592MHzTF1=0; //清除TF1标志TR1=1; //定时器1开始计时ET1=1; //打开定时器1中断允许EA=1; //打开总中断PT1=1; //当PT1=0时,定时器1为低优先级,当PT1=1时,定时器1为高优先级
}/*定时器中断函数模板
void Timer1_Routine() interrupt 3 //定时器1中断函数
{static unsigned int T1Count; //定义静态变量TL1=0x66; //设置定时初值,定时1ms,12T@11.0592MHzTH1=0xFC; //设置定时初值,定时1ms,12T@11.0592MHzT1Count++;if(T1Count>=1000){T1Count=0;}
}
*/
四、主函数
main.c
/*by甘腾胜@20250330
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】无源蜂鸣器
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
*/#include <REGX52.H>
#include "Music.h"
#include "Timer0.h"
#include "Timer1.h"sbit Buzzer=P2^5;unsigned char FreqSelect; //频率选择变量
unsigned int MusicSelect; //音乐选择变量,用来选择音符的频率和时长
bit TimeIntervalFlag=1; //控制音符之间短暂间隔(5ms)的标志/*** 函 数:主函数(有且仅有一个)* 参 数:无* 返 回 值:无* 说 明:主函数是程序执行的起点,负责执行整个程序的主要逻辑*/
void main()
{P0=0x00; //防止开发板的数码管亮Timer0_Init(); //定时器0初始化Timer1_Init(); //定时器1初始化while(1){}
}/*** 函 数:定时器0中断函数* 参 数:无* 返 回 值:无*/
void Timer0_Routine() interrupt 1
{static unsigned int T0Count1,T0Count2; //定义静态变量TL0=0x18; //设置定时初值,定时1ms,12T@11.0592MHzTH0=0xFC; //设置定时初值,定时1ms,12T@11.0592MHzT0Count1++;T0Count2++;if(T0Count1>=Music[MusicSelect]*Duration) //控制每个音符持续的时长{ //如果上一个音符的时长到了(即如果时间=倍数*十六分音符的时长),则进入此函数,重新赋值T0Count1=0;//如果是终止标志0xFF,则音乐选择变量清零,重新开始播放if(Music[MusicSelect+1]==0xFF){MusicSelect=0;}MusicSelect++; //音乐选择变量自增,此时对应Music数组里音符频率的索引FreqSelect=Music[MusicSelect]; //将音符的频率的索引赋值给频率选择变量FreqSelect//音乐选择变量自增,此时对应Music数组里↑上面频率的时长数据(十六分音符时长的倍数)//用来控制从现在到下次进入此函数的时间长度MusicSelect++;TimeIntervalFlag=1; //控制音符之间短暂间隔(5ms)的标志置1TR1=0; //定时器1暂停计时,蜂鸣器不发声T0Count2=0; //T0Count2清零}if(T0Count2>=5 && TimeIntervalFlag==1) //5ms{T0Count2=0;TimeIntervalFlag=0; //控制音符之间短暂间隔(5ms)的标志置0,在置1前不会进入此函数TR1=1; //定时器1继续计时,蜂鸣器继续发声}
}/*** 函 数:定时器1中断函数* 参 数:无* 返 回 值:无*/
void Timer1_Routine() interrupt 3
{if(FreqTable[FreqSelect]) //如果不是休止符{/*取对应频率值的重装载值到定时器*/TL1 = FreqTable[FreqSelect]%256; //设置定时初值TH1 = FreqTable[FreqSelect]/256; //设置定时初值Buzzer=!Buzzer; //翻转蜂鸣器IO口}
}
附录
《阳光彩虹小白马》简谱