蜂鸣器实验
蜂鸣器简单地说,就是电磁线圈和磁铁对振动膜的作用。
单片机的是无源蜂鸣器,不能一直充电,需要外部控制器发送震荡信号,可以改变频率产生不同的音色、音调。
大多数有源蜂鸣器则没有这个效果,有源蜂鸣器外观与之相同,内部自带震荡源,接上电就能响,但不能改变频率。
我们知道三极管的作用是不用单片机自己直接驱动单片机。
另一种方法是步进电机。
ULN 2003,高电压 高电流驱动器,给信号就被驱动。IN 取反输出 OUT。
简谱
首先整个谱大概分为几个区。大字组、小字组、小字1组、小字2组。每个组之间差8度,每相邻的两个键(如黑白)差半音,相邻的两个同色键差一个全音。
几个白键的表示方法就是下面的简谱,差半音的黑键用左上角的#表示升半音,b表示降半音。
演奏两大要素:音高和时值。
谱上一个数字是1/4 音符,二分是其两倍,数字加个横线 - 。全音符就是(2 - - -)。这个线叫增时线。
八分是其1/2,数字下加一条线(2).再/2就再加一条,叫减时线。
试着识一个完整的谱:
4/4:以四分音符为一拍,每小节有四拍。
第二节 1 ˙ \dot{1} 1˙ · 上面的点我们知道代表高音,后面的点代表:前一位音符延长1/2长度,即四分音符+1/2的四分音符。也就是3/8哈哈哈。
看第一节,一般连着两个八分音符就把 underline 连起来。但是这种哪怕是一个音,中间也要先断开再重响。比如右上角的3 3。
升音和降音在本小节中有效。如第三行的 7 #4 4 7 ,两个4都是升音。
不过如果顶端画了延音线,就是连起来的不用断开。如中间的 77 ^ \widehat{7 7} 77 ,拆开写是为了好读谱。
接下来就是如何把谱转化为单片机代码。左上角 1=c 说明是c调的。d大调会出现黑键,c调只有白键。
音具体是怎么定义的?首先以中音a为基准,高音a是其2倍,低音a是其1/2。
中间每次升音都是等比数列递增的,即*2的1/12次方
使用蜂鸣器
响起来很简单:不断反转 P1^5 口(是不是这个口得看自己的板子型号)。
void main()
{
u16 i=2000;//决定时值
while(1){
while(i--)
{
BEEP=!BEEP;
delay10Us(100);//决定音高
}
i=2000;
BEEP=0;
}
}
时值还好确认,音高怎么说?
首先我们有上图的音符与频率对照表。我们把频率转化为周期,即1/频率。这里周期单位是us。
然后周期时长转化为机器周期,即记一个数需要的时间。我们看看需要多少机器周期。
1机器周期=12时钟周期,时钟周期=1/单片机晶振。比如对于我的11.0592MHZ 晶振,机器周期=12/11.0592MHZ (单位:us)。
据此把“需要切换的周期时长”转化为“需要切换的周期需要执行几次指令”。即周期/机器周期。如果是12MHZ 晶振这一步相当于没有。
然后电平从低到高,从高到低才是一个周期。所以实际电平反转一次的周期是周期的一半。
我们知道定时器原理是 TH TL 加至65536触发中断。因此重装载值(定时器初值)=65536-取整值。
音符 | 频率 | 周期 | 需要的机器周期数 | 需要的机器周期数/2 | 取整 | 重装载值 |
---|---|---|---|---|---|---|
1 | 262 | 3816.794 | 3517.557252 | 1758.778626 | 1759 | 63777 |
1# | 277 | 3610.108 | 3327.075812 | 1663.537906 | 1664 | 63872 |
2 | 294 | 3401.361 | 3134.693878 | 1567.346939 | 1567 | 63969 |
2# | 311 | 3215.434 | 2963.344051 | 1481.672026 | 1482 | 64054 |
3 | 330 | 3030.303 | 2792.727273 | 1396.363636 | 1396 | 64140 |
4 | 349 | 2865.33 | 2640.687679 | 1320.34384 | 1320 | 64216 |
4# | 370 | 2702.703 | 2490.810811 | 1245.405405 | 1245 | 64291 |
5 | 392 | 2551.02 | 2351.020408 | 1175.510204 | 1176 | 64360 |
5# | 415 | 2409.639 | 2220.722892 | 1110.361446 | 1110 | 64426 |
6 | 440 | 2272.727 | 2094.545455 | 1047.272727 | 1047 | 64489 |
6# | 466 | 2145.923 | 1977.682403 | 988.8412017 | 989 | 64547 |
7 | 494 | 2024.291 | 1865.587045 | 932.7935223 | 933 | 64603 |
1 | 523 | 1912.046 | 1762.141491 | 881.0707457 | 881 | 64655 |
1# | 554 | 1805.054 | 1663.537906 | 831.7689531 | 832 | 64704 |
2 | 587 | 1703.578 | 1570.017036 | 785.0085179 | 785 | 64751 |
2# | 622 | 1607.717 | 1481.672026 | 740.8360129 | 741 | 64795 |
3 | 659 | 1517.451 | 1398.482549 | 699.2412747 | 699 | 64837 |
4 | 698 | 1432.665 | 1320.34384 | 660.1719198 | 660 | 64876 |
4# | 740 | 1351.351 | 1245.405405 | 622.7027027 | 623 | 64913 |
5 | 784 | 1275.51 | 1175.510204 | 587.755102 | 588 | 64948 |
5# | 831 | 1203.369 | 1109.025271 | 554.5126354 | 555 | 64981 |
6 | 880 | 1136.364 | 1047.272727 | 523.6363636 | 524 | 65012 |
6# | 932 | 1072.961 | 988.8412017 | 494.4206009 | 494 | 65042 |
7 | 988 | 1012.146 | 932.7935223 | 466.3967611 | 466 | 65070 |
1 | 1046 | 956.0229 | 881.0707457 | 440.5353728 | 441 | 65095 |
1# | 1109 | 901.7133 | 831.018936 | 415.509468 | 416 | 65120 |
2 | 1175 | 851.0638 | 784.3404255 | 392.1702128 | 392 | 65144 |
2# | 1245 | 803.2129 | 740.2409639 | 370.1204819 | 370 | 65166 |
3 | 1318 | 758.7253 | 699.2412747 | 349.6206373 | 350 | 65186 |
4 | 1397 | 715.8196 | 659.6993558 | 329.8496779 | 330 | 65206 |
4# | 1480 | 675.6757 | 622.7027027 | 311.3513514 | 311 | 65225 |
5 | 1568 | 637.7551 | 587.755102 | 293.877551 | 294 | 65242 |
5# | 1661 | 602.047 | 554.846478 | 277.423239 | 277 | 65259 |
6 | 1760 | 568.1818 | 523.6363636 | 261.8181818 | 262 | 65274 |
6# | 1865 | 536.193 | 494.155496 | 247.077748 | 247 | 65289 |
7 | 1976 | 506.0729 | 466.3967611 | 233.1983806 | 233 | 65303 |
使用方法:TH=重装载值/256,TL=重装载值%256.
音高从低到高逐位响起代码:
#include "reg52.h"
#include "Delay.h"
#include "Timer0.h"
sbit beep=P1^5;
unsigned int beep_table[]={//可以加个0代表不响的0
63777,63872,63969,64054,64140,64216,64291,64360,64426,64489,64547,64603,
64655,64704,64751,64795,64837,64876,64913,64948,64981,65012,65042,65070,
65095,65120,65144,65166,65186,65206,65225,65242,65259,65274,65289,65303
};
unsigned char beep_select=0;
void main(){
unsigned char i;
timer0Init();
while(1){
beep_select++;
delayMs(50);//时值
}
}
void timer0Interrupt() interrupt 1
{
TH0 = beep_table[beep_select]/256; // 因为触发中断时,TH TL 归零,所以记得赋初值!
TL0 = beep_table[beep_select]%256;
beep=!beep;
}
编曲:
根据乐谱写一个数组。
unsigned int little_star[]={12, 12, 19, 19,
21, 21, 19, //增时线
17, 17, 16, 16,
14, 14, 12,
19, 19, 17, 17,
16, 16, 14,
19, 19, 17, 17,
16, 16, 14,
12, 12, 19, 19,
21, 21, 19,
17, 17, 16, 16,
14, 14, 12
};
遍历数组,得到的音高再去 beep_table 中获取重装载值。
TH0 = beep_table[little_star[beep_select]]/256; // 因为触发中断时,TH TL 归零,所以记得赋初值!
TL0 = beep_table[little_star[beep_select]]%256;
但是播放起来都是连着的,听起来效果并不好。可以每次播完一个音先关闭中断并延时一段时间,再继续播放。
while(1){
beep_select++;
delayMs(50);
TR0=0;
delayUs(1);
TR0=1;
}
増时线如何处理?中间是不断开一直想的,因此需要几个特定的音符delay时间更长一些。怎么区分哪些音符加长哪些不加呢?
最好还是存储乐谱时搞一个二维数组(逻辑上物理上都可以),既能存储音高,也能存储时值。
unsigned int little_star[]={12, 4,
12, 4,
19, 4,
19, 4,
21, 4,
21, 4,
19, 8, //增时线
17, 4,
17, 4,
16, 4,
16, 4,
14, 4,
14, 4,
12, 8,
19, 4,
19, 4,
17, 4,
17, 4,
16, 4,
16, 4,
14, 8,
19, 4,
19, 4,
17, 4,
17, 4,
16, 4,
16, 4,
14, 8,
12, 4,
12, 4,
19, 4,
19, 4,
21, 4,
21, 4,
19, 8,
17, 4,
17, 4,
16, 4,
16, 4,
14, 4,
14, 4,
12, 8,
0xFF,4//终止标志防越界
};
如果数组大小超限,在魔术棒-Target-Memory Model 中选择第三个。不过这只是治标不治本,因为 RAM 只有512字节所以存不下太长。可以在定义数组时加上关键词 code 来存在 ROM 8K 的闪存中。不过这样的数组是只读的。
当然这样找索引比较麻烦。最好是索引全部重新宏定义。
//音符与索引对应表,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