——基于STC11F04E的蜂鸣器控制
青岛科技大学 信息科学技术学院 集成162 Listen C
1. 任务需求:制作一个音乐播放器,能够播放生日快乐歌曲
2. 制作原理:基于微控制器通过产生不同频率的方波使蜂鸣器发出特定音调的声音。
3. 功能要求:能够切换曲目(上一曲,下一曲),可暂停(暂停键)。
4. 任务实践:需要了解声波产生原理,掌握单片机控制方法,以及蜂鸣器原理。
(一)声波的产生
种类:
按频率分类,频率低于20Hz的声波称为次声波;频率20Hz~20kHz的声波称为可闻声;频率20kHz~1GHz的声波称为超声波;频率大于1GHz的声波称为特超声或微波超声。
因为51单片机的时钟频率为12MHz,传统的51单片机一个机器周期为12个时钟周期,也就是频率为1MHz,记一次数的时间为1/1Ms,即1us,故需要根据声音频率计算音阶表:
其中半周期指的是需要装载的初值,即(65536-半周期)/256与(65536-半周期)%256.这里可以参阅《例说51单片机(C语言版)(第三版)》第9章声音的产生,不在过多累述。
关于半周期与频率的关系,我们知道,单片机只能提供方波信号,固定周期的方波频率包含一半高电平一半低电平。而声波为正弦波,我们把声波的上峰值等效为高电平,下峰值等效为低电平,这样我们的蜂鸣器就能发出声响了。而单片机中,我们通过一个定时器中断来给蜂鸣器的电平取反,这就需要用到半周期的时间。上文提到,中断记一次数,时间为1us。1/声波频率为声波一周期所需时间,再除以2为半周期时间。别忘了这里的时间单位是秒,而我们单片机计时的时间单位为毫秒,1us=10-3ms=10-6s,即如果要换算为us,我,们还要再乘以106.故可得出公式:
半周期=1/频率/2*106
序号是本人存在数组中的位置,后文会提到。
(二)单片机的控制
这里,我用的单片机为STC11F04E,是宏晶公司增强型单片机,其速度大概是传统51的12倍,并拥有内部晶振,无需外接。当然虽然速度快了,为了兼容传统51,其定时器的时间默认还是按照传统51来设置,如果想要提速可以自行查看其数据手册。我们可以注意到,在计算半周期的时候,算出了小数位,都四舍五入掉了,如果提速,固然会精确一点,但是中断速度快了12倍,也注定更加消耗内部资源。权衡利弊在这里我还是采用了传统51的速度。
关于为什么选择这款单片机,首先,其体积小,不占太大空间。其次,我们的任务只需要一个IO来控制蜂鸣器,3个IO做按键,没有必要用4*8个IO的89C52,即使拓展矩阵键盘做电子琴,也仅仅再加8个IO,11F04E刚好够用,还剩下几个IO借个LED啥的都行。就算不接键盘接个数码管显数也够用,甚至说,用74HC595做驱动,P3.0,P3.1,P3.7控制595,P1做矩阵键盘,所有IO恰好都用上。第三,11F04E内部存储大呀,不像89C52,塞了两三首曲子就满了(好吧我承认谱子有点长)。关键有EEPROM可以掉电存储,感兴趣的朋友可以尝试一下,也就是说从后期完善的角度来看用这款单片机还是很有拓展空间的。
其实,当时我想用的并不是这一款,而是STC15F104E,只有8个脚的单片机,比它还小。奈何整了一晚上,发现其时钟并不稳定,电平的上升沿和下降沿不是直的,而是在缓慢下降,频率越低情况越明显,直接导致蜂鸣器发出的声音不对头,最后用示波器观察波形才发现的这个问题……
关于蜂鸣器,我采用的是5V无源蜂鸣器,因为无源蜂鸣器产生不同频率的音阶相对简单,结果就是需要不断给蜂鸣器提供频率。如果仅仅考虑让蜂鸣器响而不考虑音阶,选择有源蜂鸣器还是比较好用的。手头的蜂鸣器说句良心话质量不咋地,一拆就拆开了,声音还比较糙。好在不是送妹子不用考虑这么多细节。
关于单片机的控制,上文提及,用定时器来装载半周期的初值,来控制音阶。而音乐还有一个关键因素是节拍。由于本人在音乐方面白痴一只,搞不懂乱七八糟的符号,就知道1234啥的,知道这些就够了。本人竭尽全力去查节拍,结果百度来了一句节拍没有固定的时间,大概听上去舒服就行……我可能是上了个假百度,作为一名理工男最痛恨的就是不给精确时间。好吧你不给我就随便延时了。不要问我节拍延时啥原理,比着书瞎敲的,听着时间够就行。也就是说,先打开定时器,再节拍延时,再关闭定时器,这样曲子就能播放了。不懂定时器中断的请自行百度,简单说就是根据记一次数的时间去确定计数的次数,根据你选择的模式,记满进入中断函数,在这里我们用的是模式1,进入函数的操作是对蜂鸣器取反。当然别忘记重新装载初值。在这里感兴趣的朋友可以尝试用另一个定时器去控制节拍,这样主程序就能干些其他的事情了。
另外,通过对三个按键检测,来判定用户操作。其中按键扫描的原理不在累述,这里是松开扫描。因为是独立按键没什么难度可言,在节拍函数末尾加入Scan()函数来判断是否有选择操作。
乐谱采用头文件的形式存储,有音调和节拍两个数组。其中音调记录的是半周期数组的标号。跟据这些标号用户也可以自行编写歌谱。
蜂鸣器电路用的是三极管驱动,因为单片机的电流太小了导致蜂鸣器音量小,所以我选择了三极管做开关电路。在其连接处接个电位器,即可控制蜂鸣器的音量啦,哈哈。不过最好不要一只让音量处于最大状态,毕竟它还起着限流作用。
所需原材料:
STC11F04E x1
独立按键x4
三极管NPN9017 x1
5V无源蜂鸣器x1
50K电位器x1
220Ω电阻x2
慢闪LED x2
20脚底座x1
双面锡洞洞板x1
接线端公、母x1
Usb、公头x1
排针若干
导线若干
第一次用AD画原理图,有很多不足之处望大家谅解!
(一)文件结构
main.c 主函数文件
delay.c 延时函数文件
it.c 中断处理文件
play.c 播放音乐文件
key.c 按键输入文件
USER_Config.h 用户配置头文件
Interrupt.h 中断处理头文件
delay.h 延时函数头文件
play.h 音乐播放头文件
key.h 按键输入头文件
songs.h 乐谱头文件,不一一列出
(二)主要程序
USER_Config.h(其他头文件略)
/*----------------------------------------------
USER HEARD 2017/6/21
----------------------------------------------*/
#ifndef _USERCONFIG_H_
#define _USERCONFIG_H_
/* 定义数据类型 */
#define uchar unsigned char
#define uint unsigned int
#define NOP() _nop_()
/* 包含头文件 */
#include
#include
#include "interrupt.h"
#include "delay.h"
#include "play.h"
#include "key.h"
/* 蜂鸣器 */
sbit Beep = P3^5;
/* 按键 */
sbit Key_Last = P3^2;
sbit Key_Pause = P3^3;
sbit Key_Next = P3^4;
#endif
main.c
/*--------------------------------------------------
简易播放器--基于STC15F104E的蜂鸣器
By Listen C
2017/6/21
---------------------------------------------------*/
#include
#include "USER_Config.h"
/* 音乐 */
#include"tower.h"
#include"lucky.h"
#include"fly.h"
#include"dance.h"
#include"bells.h"
#include"songs.h"
/* 定义音阶初值 */
extern uchar select;
extern uint i;
/* 主函数 */
void main(void)
{
IT_Init();//中断初始化
while(1)
{
switch(select)
{
case 0: play(song1,Beat1);break;
case 1: play(song_lucky,Beat_lucky);break;
case 2: play(song_fly,Beat_fly);break;
case 3: play(song_tower,Beat_tower);break;
case 4: play(song_dance,Beat_dance);break;
case 5: play(song_bells,Beat_bells);break;
case 6: play(song4,Beat4);break;
case 7: play(song5,Beat5);break;
case 8: play(song2,Beat2);break;
case 9: play(song3,Beat3);break;
}
Beep=1;
}
}
play.c
/*--------------------------------------------------
音乐播放文件
By Listen C
2017/6/22
---------------------------------------------------*/
#include "USER_Config.h"
uchar select = 0;
extern uchar keys;
extern uint code song_tower[];
extern uint code Beat_tower[];
extern uchar pause;
uint Tone_H,Tone_L;
uint i;
uint j;
/* 定义音阶 */
uint code Tone[]={
1012, 956, 852, 759, 716, 638,
//-7(0) 1 2 3 4 5
568, 506, 478, 426, 379, 536, 10,
// 6 7 +1(8) +2(9) +3(10)6#(11) 0(12)
1908, 1701, 1515, 1433, 1276, 1136,
//-1(13)-2(14)-3(15)-4(16)-5(17)-6(18)
358, 319, 284, 253, 1805, 1608, 1351,
//+4(19)+5(20)+6(21)+7(22)-1#(23)-2#(24)-4#(25)
1205, 1078, 903, 804, 676, 602,
//-5#(26)-6#(27) 1#(28) 2#(29) 4#(30) 5#(31)
451, 402, 338, 301, 268
//+1#(32)+2#(33)+4#(34)+5#(35)+6#(36)
};
/* 播放音乐 */
void play(uchar *song,uchar *Beat)
{
i=0;
while(select==keys)
{
if(song[i]==115)
i=0;
Tone_H=(65536-Tone[song[i]])/256;
Tone_L=(65536-Tone[song[i]])%256;
TH0=Tone_H;
TL0=Tone_L;
if(pause==0)
{
TR0=1;
Beat_125(Beat[i]);
TR0=0;
i++;
}
if(Key_Last==0||Key_Pause==0||Key_Next==0)
{
Scan();
}
}
select=keys;
}
It.c
/******************************************
中断服务程序
by LZK 2017/6/17
******************************************/
#include "USER_Config.h"
extern uint Tone_H,Tone_L;
extern uint code Tone[];
uchar flag = 0;
/* 中断初始化 */
void IT_Init()
{
// AUXR = 0x00;
Beep = 1;
TMOD = 0x01;
ET0 = 1;
EA = 1;
}
/* 播放音阶 */
void Tone_Timer(void) interrupt 1
{
TH0 = Tone_H;
TL0 = Tone_L;
Beep = ~Beep;
}
Key.c
/*----------------------------------
按键判断
By Listen C 2017/6/22
-----------------------------------*/
#include "USER_Config.h"
#define Max_Num 9
uchar keys = 0;
uchar pause = 0;
/* 按键输入 */
void Scan()
{
delay_ms(10);//延时10ms进行消抖
if(Key_Pause==0)//按下暂停键
{
while(Key_Pause==0);
delay_ms(10);
pause = ~pause;
}
if(Key_Last==0)//按下上一曲
{
while(Key_Last==0);
keys=keys>0?keys-1:Max_Num;
}
if(Key_Next==0)//按下上一曲
{
while(Key_Next==0);
keys=keys
delay.c
/*************************************
延时函数库 By LZK
频率:11.0592MHz
*************************************/
#include "USER_Config.h"
/* 延时x*8.77us */
void delay_us(uint x)
{
uint i,j;
for(i=0;i
song.h(仅举一例)
/*--------------------------------------------------------------------------
LUCKY.H
小幸运 music for 8051.
--------------------------------------------------------------------------*/
#ifndef __LUCKY_H__
#define __LUCKY_H__
/************************************* 小幸运 ***********************************/
unsigned char code song_lucky[]={
1,5,10,1,5, 2,30,8,2,4, 17,0,4,17,0, 1,2,3,5,1,3,
3/*0*/,3,3,5,5,8,8,7, 7,6,3,6, 6/*0*/,6,6,7,7,10,10,7, 7,5,3,5,
3/*0*/,3,3,5,5,8,8,7, 7,6,3,6,6,7, 6,7,10,9,8,
3/*0*/,3,3,5,5,8,8,7, 7,6,3,6, 6/*0*/,6,6,7,7,10,10,7, 7,5,3,5,
3/*0*/,3,3,5,5,8,8,7, 7,6,3,6,6,7, 6,7,10,9,8,
10,9,8,7, 6,6,6,6,6,10,9,9, 9,8,7,6, 5,5,5,3,5,9,8,8, 8,8,5,5,1,
3,2,6,6, 6,6,6,8,6,8,6, 8,8,8,8,10,9,9,
5,10,9,8,9, 10,5,9,10,5,9,10, 9,9,10,19,10,9,7, 8,3,6,8,3,6,7,
7,7,10,20,10,8,7, 6,19,19, 20/*0*/,20,19,10, 5,10,10,19/*0*/,19,10,8,
34,9,9,9/*0*/,9,8,10, 9,8,10,9,8,8,
10,5,9,10,5,9,10, 9,9,10,19,10,9,7, 8,3,6,8,3,6,7, 7,7,10,20,10,8,7,
6,19,19,20/*0*/,20,19,10, 5,10,10, 19/*0*/,19,10,8, 34,9,9, 10/*0*/,10,8,8,10,9,8,
115};
unsigned char code Beat_lucky[]={
4,4,4,4,16, 4,4,4,4,16, 4,4,4,4,16, 4,2,2,4,4,16,
4,4,4,4,4,4,4,4, 4,4,4,20, 4,4,4,4,4,4,4,4, 4,4,4,20,
4,4,4,4,4,4,4,4, 4,4,4,12,4,8, 4,4,8,8,36,
4,4,4,4,4,4,4,4, 4,4,4,20, 4,4,4,4,4,4,4,4, 4,4,4,20,
4,4,4,4,4,4,4,4, 4,4,4,12,4,8, 4,4,8,8,20,
4,2,6,4, 4,4,4,4,4,4,4,20, 4,2,6,4, 4,4,4,4,4,4,4,16, 4,4,4,4,4,
8,4,16,8, 4,4,2,6,4,4,4, 4,4,4,4,4, 8,16,
4,4,2,6,4, 4,4,4,8,4,4,8, 4,4,2,6,4,4,4, 4,4,4,8, 4,4,8,
4,4,2,6,4,4,4, 4,2,10,4,4,4,4, 4,2,10,4,4,4,4,
4,2,10, 4,4,4,8, 4,4,8,4,4,4,
4,4,4,8,4,4,8, 4,4,2,6,4,4,4, 4,4,4,8,4,4,8, 4,4,2,6,4,4,4,
4,2,10,4,4,4,4, 4,2,10,4,4,4,4, 4,2,26, 4,4,4,4,6,6,26
};
#endif
按照上文提及的,本作品还有许多地方可以完善。不过对于朋友过生日嘛,已经足够了,一晚上做出来第二天给他送去。其实如果可以的话做个壳效果会好点,可惜我一搞电路的也没处捣鼓3D打印机做个壳,还有就是渣渣蜂鸣器质量太差了,就将就将就吧,嘻嘻。
最后,附上共享链接,大家一起学习交流
链接:http://pan.baidu.com/s/1jH8oJxO 密码:d3kc