学习目标

  1. 理解原理图
  2. 了解蜂鸣器分类
  3. 实现蜂鸣器的简单发声
  4. 了解简单的乐理知识
  5. 实现音符播放
  6. 实现音乐播放

    学习内容

    蜂鸣器

    蜂鸣器是一种能够产生固定频率的声音的电子元件。它通常由振膜、震荡器、放大器和声音反馈电路等部分组成。振膜是蜂鸣器中最核心的部分,它能够将电信号转换为机械振动,产生声音。震荡器提供稳定的电信号,用于驱动振膜产生振动。放大器用于放大电信号的幅度,以便产生足够的声音。声音反馈电路可以提供反馈信号,帮助系统稳定。
    蜂鸣器广泛应用于电子设备中,例如电子钟、警报器、电子琴等。它们的声音频率通常在1 kHz到10 kHz之间,具有尖锐而刺耳的特点。蜂鸣器的种类很多,例如电磁式蜂鸣器、压电式蜂鸣器、有源蜂鸣器、无源蜂鸣器等等。不同类型的蜂鸣器具有不同的特点和应用场景。
    电子爱好者和开发者通常会使用蜂鸣器作为一种简单而有效的提示器件。例如,在嵌入式系统中,可以通过控制蜂鸣器发出不同的声音来实现提示、警报、提醒等功能。一些开发板和单片机也通常带有蜂鸣器接口,方便开发者使用。
    通常我们在开发中用到最多的是 有源蜂鸣器和无源蜂鸣器。有源的直接接电源即可发声。无源的需要连接一个变化频率的电源上,才能发出声音。

    原理图

    70.png
  • 采用P0.0引脚控制三极管的导通

肖特基二极管:
当蜂鸣器在工作时,会产生电磁感应。
当电源关闭或蜂鸣器停止振动时,会产生一个瞬态的电压峰值,这会产生反向电流,可能会对电路及蜂鸣器造成损害或影响其寿命。肖特基二极管可以通过其低的正向电压降和快速反向恢复特性,有效地防止反向电流损害电路。
此外,肖特基二极管的快速开关特性也能够减小蜂鸣器电路中的开关噪声和干扰,提高电路的稳定性和可靠性。因此,在蜂鸣器电路中加入肖特基二极管是一种常见的电路保护和稳定化措施。

肖特基二极管的作用

  1. 快速开关:肖特基二极管具有快速的反向恢复特性,可以快速地从导通到截止转变,因此它通常用于高频开关电路中。
  2. 低正向压降:与普通二极管相比,肖特基二极管具有更低的正向电压降,因此在需要低功耗和高效率的电路中使用时,肖特基二极管可以降低电路中的功耗和热损失。
  3. 防反向漏电流:由于肖特基二极管是由金属和半导体接触组成的,因此在正向偏置时,不会发生少数载流子注入的现象,从而降低了漏电流。
  4. 温度特性好:由于金属与半导体接触,所以肖特基二极管具有良好的温度特性。在高温环境下,肖特基二极管的电性能仍能保持稳定。

因此,肖特基二极管在高频开关电路、低功耗电路和功率电子等领域中得到了广泛的应用。

三极管并联电阻

在三极管的放大电路中,通常会并联一个电阻,这个电阻被称为集电极负载电阻。
这个集电极负载电阻的作用是:

  1. 稳定直流工作点:集电极负载电阻可以使三极管的直流工作点更加稳定。由于三极管是非线性器件,其直流放大倍数随着工作点的改变而变化。通过加入集电极负载电阻,可以限制直流工作点的漂移,保证放大电路的直流稳定性。
  2. 改善交流性能:集电极负载电阻还可以改善放大电路的交流性能。通过控制集电极电流,可以改变三极管的放大倍数,从而实现对输入信号的放大。同时,集电极负载电阻还可以限制输出幅度,避免过度放大造成信号失真。
  3. 防止三极管损坏:当输入信号过大时,三极管的集电极电压可能会超过其最大耐压值,从而造成三极管损坏。通过加入集电极负载电阻,可以限制输出幅度,避免超过三极管的最大耐压值,从而保护三极管。

因此,三极管放大电路中并联一个集电极负载电阻是一种常见的电路设计技巧,可以提高电路的性能和稳定性,同时保护三极管免受过电压损坏。

在三极管放大电路中,集电极负载电阻的阻值会影响电路的放大倍数、直流工作点以及输出电阻等性能。
通常情况下,集电极负载电阻的阻值需要根据具体的电路设计要求来确定。一般来说,阻值不应过大或过小,一般取值范围在几百欧姆到几千欧姆之间。
如果集电极负载电阻的阻值太大,会导致放大倍数过低,使得电路的放大效果不理想。另外,由于三极管的输出电阻较小,集电极负载电阻的阻值过大还会导致电路的输出电阻过大,降低电路的输出功率。
如果集电极负载电阻的阻值太小,会导致放大倍数过高,使得电路容易失真或产生饱和现象。同时,由于直流工作点的不稳定性,集电极负载电阻的阻值过小还会导致直流工作点的漂移,降低电路的直流稳定性。
因此,在实际电路设计中,需要根据具体要求综合考虑电路性能和稳定性等因素,选取适当的集电极负载电阻阻值。

测试发声

  1. #include "config.h"
  2. #include "GPIO.h"
  3. #include "delay.h"
  4. #define BUZZER P00
  5. void GPIO_config(void) {
  6. GPIO_InitTypeDef GPIO_InitStructure; //结构定义
  7. GPIO_InitStructure.Pin = GPIO_Pin_0; //指定要初始化的IO,
  8. GPIO_InitStructure.Mode = GPIO_OUT_PP; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
  9. GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);//初始化
  10. }
  11. int main() {
  12. GPIO_config();
  13. EA = 1;
  14. while(1) { // 每个循环约4ms,每秒1000ms/4ms = 200次,即频率为200Hz
  15. P00 = 1;
  16. delay_ms(2);
  17. P00 = 0;
  18. delay_ms(2);
  19. }
  20. }

通过控制delay_ms的时间,控制发声的频率,来观察蜂鸣器的发声情况。

乐理知识

乐理知识从专业角度来说,包含了很多内容,包括音高、音阶、节奏、和声、旋律、调性、节拍等等方面的知识。

补充知识,不做要求。

  1. 音高:音高是音乐中的一个基本元素,指的是声音高低的程度。常用的表示音高的符号是音符,不同的音高可以使用不同的音符来表示。
  2. 音阶:音阶是一组按照音高顺序排列的音符组成的序列。常用的音阶包括了大调音阶和小调音阶等。
  3. 节奏:节奏是指音乐中的强弱、快慢、持续时间等方面的时间关系。节奏可以通过节拍器或其他的打击乐器来表现。
  4. 和声:和声是指多个声音同时进行时的相互关系。和声可以表现出不同的和声效果,如和弦、和声进程等。
  5. 旋律:旋律是指音乐中的主旋律,是由一系列按照音高顺序排列的音符组成的。旋律可以使用不同的节奏来表现出不同的效果。
  6. 调性:调性是指音乐中的调性关系。常用的调性包括了大调和小调等。
  7. 节拍:节拍是指音乐中的基本的时间单位,用于表示节奏的强弱、快慢等方面的特征。节拍通常使用不同的时间符号来表示。
  8. 同音重复:同音重复是指在不同的位置或时间上出现相同的音符或音高。

在此呢,我们不研究更全面更深入的乐理知识,我们从我们的常识方面入手,了解简单的发声即可。

哆来咪发唆拉西哆

哆来咪发唆拉西哆(Do-Re-Mi-Fa-So-La-Ti-Do)是音乐中的一个音阶记号,也是西方音乐中最基本的一个音阶。它由八个不同的音符组成,分别是:Do、Re、Mi、Fa、So、La、Ti、Do。这些音符分别代表了一个八度内的不同音高。
在音乐教学中,哆来咪发唆拉西哆常常被用来作为基础训练的内容。通过唱出哆来咪发唆拉西哆,可以帮助学生了解不同音符之间的音高关系,掌握音乐中的基本音程和旋律。同时,哆来咪发唆拉西哆也是很多歌曲的基础,学会了这个音阶,就可以更好地理解和演唱这些歌曲。
哆来咪发唆拉西哆可以用不同的乐谱表示方式来呈现。以下是常见的两种表示方式:

  1. 数字表示法:数字表示法将每个音符用数字来代表,Do为1,Re为2,Mi为3,Fa为4,So为5,La为6,Ti为7,Do(高八度)为8。因此,哆来咪发唆拉西哆的数字表示法为:1 2 3 4 5 6 7 8。
  2. 符号表示法:符号表示法用特定的符号来表示每个音符,包括大写字母(如C、D、E、F、G、A、B)、升降符号(如#、b)和八度符号(如’)。哆来咪发唆拉西哆的符号表示法为:C D E F G A B C’。

需要注意的是,不同的乐器和音高区间可能使用不同的记谱方式,但哆来咪发唆拉西哆作为最基本的音阶,通常都可以用以上两种方式表示。

十二平均律

十二平均律是现代西方音乐中最广泛使用的音高系统,它的作用可以从以下几个方面来理解:

  1. 方便协调和配合:由于十二平均律将八度音程划分成12个等分,每个等分的音高间隔相等,不同的调式可以使用相同的音高间隔,因此方便不同乐器、不同声部之间的协调和配合。
  2. 增加音乐的表现力:十二平均律中的半音音程比纯律(一种古老的音高系统)中的半音更小,因此可以创造更多的音高变化,增加音乐的表现力。
  3. 适应和反映现代音乐的需求:现代音乐中常常出现的复杂和离奇的调性变化,需要更加灵活和多变的音高体系,而十二平均律可以提供这种灵活性和多变性。

总之,十二平均律作为一种现代音乐基础的音高系统,为不同音乐风格和流派的发展提供了有力的支持,成为现代音乐的不可或缺的一部分。

专业的术语理解起来比较抽象,对于乐理不是很了解的可以这样理解:

  1. 我们将音乐的音高分为12个等分。类似我们拼音中的4声(类比说法,还是有区别的)
  2. 我们在12个音高中对应了我们的哆来咪发唆拉西哆
  3. 要发出不同的音高,需要不同的频率来发声。

维基百科 十二平均律
12.png
结合十二平均律和哆来咪发唆拉西哆的符号表示法,我们可以得到下表

音高 数字表示 符号表示 频率
1 C 蜂鸣器⭐ - 图3
2 D 蜂鸣器⭐ - 图4
3 E 蜂鸣器⭐ - 图5
4 F 蜂鸣器⭐ - 图6
5 G 蜂鸣器⭐ - 图7
6 A 蜂鸣器⭐ - 图8
西 7 B 蜂鸣器⭐ - 图9
8 C’ 蜂鸣器⭐ - 图10

此处的频率,就是我们的发声频率。

乐理补充内容

76.png
上表为 十二平均律基础率表与频率计算对照表:
哆来咪发唆拉西哆 分别对应 C``D``E``F``G``A``B``C下一组
上面分为大字二组``大字一组``大字组``小字组``小字一组``小字二组``小字三组``小字四组
其实可以观察,他们的频率都是翻倍的。这些可以理解为音高不同。(有时候我们说唱歌时调子起高了,就是选了一组频率比较高的发声)
通常有些频率单片机通过定时或者PWM不容易做到。

更完整的八度音阶Octave和音符对照表Note:

Octave→
Note↓
0 1 2 3 4 5 6 7 8 9
C 16.352 (−48) 32.703 (−36) 65.406 (−24) 130.81 (−12) 261.63 (0) 523.25 (+12) 1046.5 (+24) 2093.0 (+36) 4186.0 (+48) 8372.0 (+60)
C♯/D♭ 17.324 (−47) 34.648 (−35) 69.296 (−23) 138.59 (−11) 277.18 (+1) 554.37 (+13) 1108.7 (+25) 2217.5 (+37) 4434.9 (+49) 8869.8 (+61)
D 18.354 (−46) 36.708 (−34) 73.416 (−22) 146.83 (−10) 293.66 (+2) 587.33 (+14) 1174.7 (+26) 2349.3 (+38) 4698.6 (+50) 9397.3 (+62)
D♯/E♭ 19.445 (−45) 38.891 (−33) 77.782 (−21) 155.56 (−9) 311.13 (+3) 622.25 (+15) 1244.5 (+27) 2489.0 (+39) 4978.0 (+51) 9956.1 (+63)
E 20.602 (−44) 41.203 (−32) 82.407 (−20) 164.81 (−8) 329.63 (+4) 659.26 (+16) 1318.5 (+28) 2637.0 (+40) 5274.0 (+52) 10548 (+64)
F 21.827 (−43) 43.654 (−31) 87.307 (−19) 174.61 (−7) 349.23 (+5) 698.46 (+17) 1396.9 (+29) 2793.8 (+41) 5587.7 (+53) 11175 (+65)
F♯/G♭ 23.125 (−42) 46.249 (−30) 92.499 (−18) 185.00 (−6) 369.99 (+6) 739.99 (+18) 1480.0 (+30) 2960.0 (+42) 5919.9 (+54) 11840 (+66)
G 24.500 (−41) 48.999 (−29) 97.999 (−17) 196.00 (−5) 392.00 (+7) 783.99 (+19) 1568.0 (+31) 3136.0 (+43) 6271.9 (+55) 12544 (+67)
G♯/A♭ 25.957 (−40) 51.913 (−28) 103.83 (−16) 207.65 (−4) 415.30 (+8) 830.61 (+20) 1661.2 (+32) 3322.4 (+44) 6644.9 (+56) 13290 (+68)
A 27.500 (−39) 55.000 (−27) 110.00 (−15) 220.00 (−3) 440.00 (+9) 880.00 (+21) 1760.0 (+33) 3520.0 (+45) 7040.0 (+57) 14080 (+69)
A♯/B♭ 29.135 (−38) 58.270 (−26) 116.54 (−14) 233.08 (−2) 466.16 (+10) 932.33 (+22) 1864.7 (+34) 3729.3 (+46) 7458.6 (+58) 14917 (+70)
B 30.868 (−37) 61.735 (−25) 123.47 (−13) 246.94 (−1) 493.88 (+11) 987.77 (+23) 1975.5 (+35) 3951.1 (+47) 7902.1 (+59) 15804 (+71)

乐理应用

Timer测试发声

我们通过timer进行 【哆来咪发唆拉西哆】 测试,timer的延时比较准确。

  1. #include "Config.h"
  2. #include "GPIO.h"
  3. #include "Delay.h"
  4. #include "Timer.h"
  5. #include "NVIC.h"
  6. #define BUZZER P00
  7. // C` D` E` F` G` A` B` C``
  8. u16 hz[] = {1047, 1175, 1319, 1397, 1568, 1760, 1976, 2093};
  9. void GPIO_config() {
  10. P0_MODE_OUT_PP(GPIO_Pin_0);
  11. }
  12. void Timer_config(u16 hz_value)
  13. {
  14. TIM_InitTypeDef TIM_InitStructure; //结构定义
  15. //定时器0做16位自动重装, 中断频率为1000HZ
  16. TIM_InitStructure.TIM_Mode = TIM_16BitAutoReload; //指定工作模式, TIM_16BitAutoReload,TIM_16Bit,TIM_8BitAutoReload,TIM_16BitAutoReloadNoMask
  17. TIM_InitStructure.TIM_ClkSource = TIM_CLOCK_1T; //指定时钟源, TIM_CLOCK_1T,TIM_CLOCK_12T,TIM_CLOCK_Ext
  18. TIM_InitStructure.TIM_ClkOut = DISABLE; //是否输出高速脉冲, ENABLE或DISABLE
  19. TIM_InitStructure.TIM_Value = 65536UL - (MAIN_Fosc / (hz_value * 2)); //初值,
  20. TIM_InitStructure.TIM_Run = ENABLE; //是否初始化后启动定时器, ENABLE或DISABLE
  21. Timer_Inilize(Timer0,&TIM_InitStructure); //初始化Timer0 Timer0,Timer1,Timer2,Timer3,Timer4
  22. NVIC_Timer0_Init(ENABLE,Priority_0); //中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
  23. }
  24. /**
  25. 举例:如下是3个完整周期
  26. -- -- --
  27. | | | | |
  28. -- -- --
  29. **/
  30. void timer0_func() {
  31. BUZZER = ~BUZZER;
  32. }
  33. void main() {
  34. u8 idx = 0;
  35. GPIO_config();
  36. // Timer_config();
  37. EA = 1;
  38. // 有源蜂鸣器,才可以直接通过高电平响起
  39. // BUZZER = 1;
  40. // 20-20000Hz
  41. // 舒适: 1000-4000Hz
  42. while(1) {
  43. Timer_config(hz[idx]);
  44. if(++idx > 7){
  45. idx = 0;
  46. }
  47. delay_ms(250);
  48. delay_ms(250);
  49. delay_ms(250);
  50. delay_ms(250);
  51. }
  52. }

PWM测试发声

最准确的方式我们还可以选择PWM进行控制,这个也是常用的方式。

PWM PWM通道 对应引脚
PWMB PWM5 P0.0
P1.7
P2.0
PWM6 P0.1
P2.1
P5.4
PWM7 P0.2
P2.2
P3.3
PWM8 P0.3
P2.3
P3.4
  1. #include "Config.h"
  2. #include "GPIO.h"
  3. #include "Delay.h"
  4. #include "STC8H_PWM.h"
  5. #include "Switch.h"
  6. #include "NVIC.h"
  7. #define BUZZER P00
  8. // C D E F G A B C`
  9. u16 hz[] = {523, 587, 659, 698, 784, 880, 988, 1047};
  10. // C` D` E` F` G` A` B` C``
  11. //u16 hz[] = {1047, 1175, 1319, 1397, 1568, 1760, 1976, 2093};
  12. void GPIO_config() {
  13. P0_MODE_OUT_PP(GPIO_Pin_0);
  14. }
  15. //#define PERIOD (MAIN_Fosc / 1000)
  16. //PWMx_Duty dutyB;
  17. void PWM_config(u16 hz_value)
  18. {
  19. PWMx_InitDefine PWMx_InitStructure;
  20. u16 Period = MAIN_Fosc / hz_value;
  21. // 配置PWM5
  22. PWMx_InitStructure.PWM_Mode = CCMRn_PWM_MODE1; //模式, CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2
  23. PWMx_InitStructure.PWM_Duty = (u16)(Period * 0.5); //PWM占空比时间, 0~Period
  24. PWMx_InitStructure.PWM_EnoSelect = ENO5P; //输出通道选择, ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8P
  25. PWM_Configuration(PWM5, &PWMx_InitStructure); //初始化PWM, PWMA,PWMB
  26. // 配置PWMB
  27. PWMx_InitStructure.PWM_Period = Period - 1; //周期时间, 0~65535
  28. PWMx_InitStructure.PWM_DeadTime = 0; //死区发生器设置, 0~255
  29. PWMx_InitStructure.PWM_MainOutEnable= ENABLE; //主输出使能, ENABLE,DISABLE
  30. PWMx_InitStructure.PWM_CEN_Enable = ENABLE; //使能计数器, ENABLE,DISABLE
  31. PWM_Configuration(PWMB, &PWMx_InitStructure); //初始化PWM通用寄存器, PWMA,PWMB
  32. // 切换PWM通道
  33. PWM5_SW(PWM5_SW_P00); //PWM5_SW_P20,PWM5_SW_P17,PWM5_SW_P00,PWM5_SW_P74
  34. // 初始化PWMB的中断
  35. NVIC_PWM_Init(PWMB,DISABLE,Priority_0);
  36. }
  37. /**
  38. 举例:如下是3个完整周期
  39. -- -- --
  40. | | | | |
  41. -- -- --
  42. **/
  43. void main() {
  44. u8 idx = 0;
  45. // 扩展寄存器使能
  46. EAXSFR();
  47. GPIO_config();
  48. EA = 1;
  49. // 有源蜂鸣器,才可以直接通过高电平响起
  50. // BUZZER = 1;
  51. // 20-20000Hz
  52. // 舒适: 1000-4000Hz
  53. while(1) {
  54. PWM_config(hz[idx]);
  55. if(++idx > 7) {
  56. idx = 0;
  57. }
  58. delay_ms(250);
  59. delay_ms(250);
  60. delay_ms(250);
  61. delay_ms(250);
  62. }
  63. }

PWM驱动封装

可以将蜂鸣器的代码进行封装,这样方便以后调用

  1. #ifndef __BUZZER_H__
  2. #define __BUZZER_H__
  3. #include "Config.h"
  4. // 初始化蜂鸣器
  5. void Buzzer_init();
  6. // 按照指定频率播放
  7. void Buzzer_play(u16 hz_val);
  8. // 停止播放
  9. void Buzzer_stop();
  10. #endif
  1. #include "Buzzer.h"
  2. #include "GPIO.h"
  3. #include "STC8H_PWM.h"
  4. #include "Switch.h"
  5. #include "NVIC.h"
  6. // C D E F G A B C`
  7. //u16 hz[] = {523, 587, 659, 698, 784, 880, 988, 1047};
  8. // C` D` E` F` G` A` B` C``
  9. u16 hz[] = {1047, 1175, 1319, 1397, 1568, 1760, 1976, 2093};
  10. static void GPIO_config(void) {
  11. GPIO_InitTypeDef GPIO_InitStructure; //结构定义
  12. GPIO_InitStructure.Pin = GPIO_Pin_0; //指定要初始化的IO,
  13. GPIO_InitStructure.Mode = GPIO_OUT_PP; //指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
  14. GPIO_Inilize(GPIO_P0, &GPIO_InitStructure);//初始化
  15. }
  16. void Buzzer_init(){
  17. EAXSFR();
  18. GPIO_config();
  19. }
  20. void Buzzer_beep(u8 hz_val_index){ // 1,2,3,4 ... 7
  21. u16 hz_val = hz[hz_val_index - 1];
  22. Buzzer_play(hz_val);
  23. }
  24. void Buzzer_play(u16 hz_val){
  25. u16 Period = MAIN_Fosc / hz_val;
  26. PWMx_InitDefine PWMx_InitStructure;
  27. // PWM5
  28. PWMx_InitStructure.PWM_Mode = CCMRn_PWM_MODE1; //模式, CCMRn_FREEZE,CCMRn_MATCH_VALID,CCMRn_MATCH_INVALID,CCMRn_ROLLOVER,CCMRn_FORCE_INVALID,CCMRn_FORCE_VALID,CCMRn_PWM_MODE1,CCMRn_PWM_MODE2
  29. PWMx_InitStructure.PWM_Duty = (int)(Period * 0.005); //PWM占空比时间, 0~Period
  30. PWMx_InitStructure.PWM_EnoSelect = ENO5P; //输出通道选择, ENO1P,ENO1N,ENO2P,ENO2N,ENO3P,ENO3N,ENO4P,ENO4N / ENO5P,ENO6P,ENO7P,ENO8P
  31. PWM_Configuration(PWM5, &PWMx_InitStructure); //初始化PWM, PWMA,PWMB
  32. // PWMB
  33. PWMx_InitStructure.PWM_Period = Period - 1; //周期时间, 0~65535
  34. PWMx_InitStructure.PWM_DeadTime = 0; //死区发生器设置, 0~255
  35. PWMx_InitStructure.PWM_MainOutEnable= ENABLE; //主输出使能, ENABLE,DISABLE
  36. PWMx_InitStructure.PWM_CEN_Enable = ENABLE; //使能计数器, ENABLE,DISABLE
  37. PWM_Configuration(PWMB, &PWMx_InitStructure); //初始化PWM通用寄存器, PWMA,PWMB
  38. PWM5_SW(PWM5_SW_P00); //PWM5_SW_P20,PWM5_SW_P17,PWM5_SW_P00,PWM5_SW_P74
  39. NVIC_PWM_Init(PWMB,DISABLE,Priority_0);
  40. }
  41. void Buzzer_stop(){
  42. PWMx_InitDefine PWMx_InitStructure;
  43. PWMx_InitStructure.PWM_MainOutEnable= DISABLE; //主输出使能, ENABLE,DISABLE
  44. PWMx_InitStructure.PWM_CEN_Enable = DISABLE; //使能计数器, ENABLE,DISABLE
  45. PWM_Configuration(PWMB, &PWMx_InitStructure); //初始化PWM, PWMA,PWMB
  46. }

乐谱

我们在此讨论的乐谱,只包含两个部分,一个是哆来咪发唆拉西哆,另外一个就是节拍,在发声过程中,节拍是指一个音的发音时长。
以两只老虎为例:http://www.jianpu.cn/pu/33/33945.htm
71.png
乐谱中的 1234567,就是我们所说的 哆来咪发唆拉西。
接下来我们理解节拍。

时长约定

73.png
1231有4个音符,每个音符默认是4个单位。具体一个单位时长是多少,后面我们可以定义具体值。

音符后的-

75.png
通常表示休止符,用来控制乐曲的节奏和节拍,表示停顿一个节拍,也就是4个单位时间.
上图为例,345后面是一个横杠,表示上一个音持续发音,则345分别对应的音长为4个单位4个单位8个单位

音符的下划线

74.png
一条下划线表示,时间缩短一半。
上图为例:

  1. 后面31没有任何修饰,每个占据4个单位;
  2. 56下面是一条长横线,其中6下面还有一条短杠。6的时长是一半的一边,则是1个单位
  3. 56下面是一条长横线,5的时长理论上就是一半,为2个单位。(但是后面有个点)

    音符后跟圆点

    74.png
    在简谱中,音符后面跟一个圆点表示这个音符的时值被延长了一半。例如,一个四分音符加上一个圆点,表示时值相当于一个四分音符加一个八分音符的时值。这个符号称为“附点”,它可以使节奏更加灵活。
    以上图为例:

  4. 56下面是一条长横线,5的时长理论上就是一半,为2个单位,但是后面有个圆点,表示在现有基础上增加一半,也就是3个单位

    两只老虎的音长

    | 简谱 | 1``2``3``1 1``2``3``1 3``4``5``- 3``4``5``- | | —- | —- | | 音长 | 4``4``4``4 4``4``4``4 4``4``8 4``4``8 | | 词 | 两``只``老``虎 两``只``老``虎 跑``得``快 跑``得``快 | | 简谱 | 5``6``5``4``3``1 5``6``5``4``3``1 1``5``1 1``5``1 | | 音长 | 3``1``3``1``4``4 3``1``3``1``4``4 4``4``8 4``4``8 | | 词 | 一``只``没``有``眼``睛 一``只``没``有``耳``朵 真``奇``怪 真``奇``怪 |

音符0

在音乐中,音符0一般表示一种特殊的音符,称为休止符或停顿符,通常用来表示音乐的停顿或静默部分。休止符可以有不同的时值,例如四分之一休止符、八分之一休止符等。在简谱中,通常用一个数字0来表示休止符,放在对应的位置上,表示在该位置上没有音符需要演奏。

音符上弧形

两个音之间没有暂停间隔,连音音符

抽象封装

将乐谱分为简谱和音长进行封装:

  1. u8 notes[] = {
  2. 1, 2, 3, 1, 1, 2, 3, 1, 3, 4, 5, 3, 4, 5,
  3. 5, 6, 5, 4, 3, 1, 5, 6, 5, 4, 3, 1, 1, 5, 1, 1, 5, 1
  4. };
  5. u8 durations[] = {
  6. 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 4, 4, 8,
  7. 3, 1, 3, 1, 4, 4, 3, 1, 3, 1, 4, 4, 4, 4, 8, 4, 4, 8
  8. };

对Buzzer的封装定义更加简谱化,不需要考虑发声频率。

  1. void Buzzer_beep(u8 note);
  2. u16 FREQS[] = {523, 587, 659, 698, 784, 880, 988, 523 * 2};
  3. void Buzzer_beep(u8 note) {
  4. ...
  5. u16 hz = FREQS[note - 1];
  6. u16 period = MAIN_Fosc / hz - 1;
  7. ...
  8. }

调用时,就能完整的播放一首曲子了:

  1. len = sizeof(notes) / sizeof(u8);
  2. for(i = 0; i < len;i++) {
  3. Buzzer_beep(notes[i]);
  4. d = durations[i] * 100;
  5. while(d--) delay_ms(1);
  6. Buzzer_stop();
  7. }

几首曲子

http://www.jianpu.cn/pu/11/111217.htm
http://www.jianpu.cn/pu/26/266965.htm

天空之城源码

  1. #define L1 1
  2. #define L2 2
  3. #define L3 3
  4. #define L4 4
  5. #define L5 5
  6. #define L6 6
  7. #define L7 7
  8. #define N0 0
  9. #define N1 L1 + 7
  10. #define N2 L2 + 7
  11. #define N3 L3 + 7
  12. #define N4 L4 + 7
  13. #define N5 L5 + 7
  14. #define N6 L6 + 7
  15. #define N7 L7 + 7
  16. #define H1 N1 + 7
  17. #define H2 N2 + 7
  18. #define H3 N3 + 7
  19. #define H4 N4 + 7
  20. #define H5 N5 + 7
  21. #define H6 N6 + 7
  22. #define H7 N7 + 7
  23. u8 notes[] = {
  24. N6, N7, H1, N7, H1, H3, N7, N3, N3, N6, N5, N6, H1, N5, N0, N3, N3, N4, N3, N4, H1,
  25. N3, N0, H1, H1, H1, N7, N4, N4, N7, N7, N0, N6, N7, H1, N7, H1, H3, N7, N0, N3, N3, N6, N5, N6, H1,
  26. N5, N0, N3, N4, H1, N7, N7, H1, H2, H2, H3, H1, N0, H1, N7, N6, N6, N7, N5, N6, N0, H1, H2, H3, H2, H3, H5,
  27. H2, N0, N5, N5, H1, N7, H1, H3, H3, N0, N0, N6, N7, H1, N7, H2, H2, H1, N5, N5, N0, H4, H3, H2, H1,
  28. H3, H3, N0, H3, H6, H5, H5, H3, H2, H1, N0, H1, H2, H1, H2, H2, H5, H3, N0, H3,
  29. H6, H5, H3, H2, H1, N0, H1, H2, H1, H2, H2, N7, N6, N0,
  30. };
  31. u8 durations[] = {
  32. 2, 2, 6, 2, 4, 4, 12, 2, 2, 6, 2, 4, 4, 8, 4, 2, 2, 6, 2, 2, 6,
  33. 8, 2, 2, 2, 2, 6, 2, 4, 4, 8, 4, 2, 2, 6, 2, 4, 4, 8, 4, 2, 2, 6, 2, 4, 4,
  34. 12, 2, 2, 4, 2, 2, 4, 4, 2, 2, 2, 4, 4, 4, 2, 2, 2, 4, 4, 8, 4, 2, 2, 6, 2, 4, 4,
  35. 8, 4, 2, 2, 2, 2, 4, 4, 8, 4, 4, 2, 2, 4, 4, 2, 2, 6, 2, 4, 4, 4, 4, 4, 4,
  36. 16, 8, 4, 4, 8, 4, 4, 2, 2, 4, 2, 2, 4, 2, 2, 2, 4, 8, 4, 4,
  37. 8, 8, 2, 2, 8, 2, 2, 4, 2, 2, 2, 4, 8, 4,
  38. };
  1. u16 FREQS[] = {
  2. 523 * 1, 587 * 1, 659 * 1, 698 * 1, 784 * 1, 880 * 1, 988 * 1,
  3. 523 * 2, 587 * 2, 659 * 2, 698 * 2, 784 * 2, 880 * 2, 988 * 2,
  4. 523 * 4, 587 * 4, 659 * 4, 698 * 4, 784 * 4, 880 * 4, 988 * 4,
  5. 523 * 8, 587 * 8, 659 * 8, 698 * 8, 784 * 8, 880 * 8, 988 * 8,
  6. };

练习题

  1. 实现一个键盘小钢琴
  2. 尝试播放几首曲子