零之前言

最近想用无源蜂鸣器来播放曲子,但是看了好多博客讲的都是马马虎虎,没有讲的太清楚,所以我只好自己重新学习了一下,音乐发声的原理(因为硬件基础够啦QAQ)和简谱。

一.发声原理

原理就是这个:人之所以能听见声音,是因为声音在震动。那么不同的震动频率带给我们的就是不同的声调。所以我们只需要知道每个音调的发声频率就可以用单片机模拟出它的音调。

二.频率与简谱

1.频率

这是一张标准的音高与频率的关系对照表:
在这里插入图片描述
我们只需要记住这一点,其中的就是八度音阶,也就是我们的XX调,比如你熟悉的G大调就是我们的音级,也就是我们熟悉的do re mi
我们可以简单的理解为:有很多很多台阶,我们认为规定8个台阶为一层。
我们在演奏的时候会规定基准调,就类似于零重力势能点、零电位、零电势点的选择。假设我们规定了某个调是基准调,那么这个简谱里的1就是这个调,然后234依次提高。比如我们规定了440Hz是基准调,那么这个谱子里的2就是466Hz、3就是493Hz……

2.简谱

如何认识简谱才是最难的。建议大家百度仔细了解,如果来不及仔细了解就看这个20多分钟的视频:【零基础学乐理】第一课:什么是简谱。如果更来不及就看我简单说说吧。
放出一张我要用来讲解的简谱:在这里插入图片描述

①音调


音调如同上图,假设我们的1上面有一个点,那它就比没有点的1高八阶(一度)。如果下面有一个点,那它就低一度。比如假设我们的的C调do 1 是261.6Hz 那么 它头上有个点就是523.2Hz,它下面一个点就是130.8Hz。两个点就再高(低)一度。

②调号

我们简谱左上角会有一个类似于1=D1=C这样的东西是用来确定基准调的。
假设我们的1=C,那么我们的基准调就是261Hz 。1=D 就是294Hz。所以我们不懂乐理的外行人需要根据左上角的标号百度一下我们的基准调。而且有些特殊的基准调对于某些音符需要升降音,所以建议百度。

③节拍+音符

节拍数就是我们左上角"分数",比如2/4我们读作"以一个4分音符为1拍,每个小节两拍"。
每一节结束后因该是有一段小竖线作为我们的小节线。
音符就是每一个数字,但是数字的标记不同,就代表他发的音的长短不同。例如:1 这是一个4分音符,假设它发1秒的音;1就是8分音符,它发0.5秒的音;1-就是2分音符,它发2秒的音。根据一个音符下的-或者 的数量不同,依次加倍减半。
例如小红帽:它是2/4拍的。我们每一个小节算出来应该是2个4分音符。所以我们来算算?
1 2 3 4 | 5 3 1 | 你看满不满足这个规则?

三.写代码

1.代码思路:

一秒钟震动很多次,那么一次我们需要的时间就是1/频率。那么我们就算出了其周期。那么我们让一半周期高电平,一半周期低电平,我们就得到了其一次震动。我们再用周期x频率=1s就可以控制每个调的时间。

for(i = 0; i < hz; i++) //这是一秒一个音 此时整乘除hz可以扩大缩小一拍的时间 
{
	u32 time = 1000000 / 2 / hz;

	buzzer(); //use the buzzer
	delay_us(time);	
	not_buzzer(); // not use the buzzer
	delay_us(time);	
}

2.扒谱

我们需要:一个音符的音调,的一个音符的时间,这个音调的频率。
还是以小红帽的前五小节为例:
在这里插入图片描述
它是 1 = D 那么我们按1 2 3 4 5 6 7 i来记录频率:494 523 587 659 698 784 880 988
时间:==尤其是时间,我们虽然是4分音符为1拍,但是我们里面有8分音符,所以我们因该找全曲最快的音符。==此小段那就是8分音符为1,4分音符就为2了呗。
三要素找齐了:
包括音调的所有频率:293 330 350 392 440 494 523 587(1-8)
乐谱音调:1 2 3 4 5 3 1 8 6 4 5 5 3 1 2 3 4
音调对应时间(与上面一一对应)1 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 1

3.真代码

#include "stm32f10x.h"
#include "sys.h"

u32 play_hz[] = {0, 293, 330, 350, 392, 440, 494, 523, 587};
u32 play_tone[] = {1, 2, 3, 4, 5, 3, 1, 8, 6, 4, 5, 5, 3, 1, 2, 3, 4};
u32 play_time[] = {1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1}; 

void play(u32 sta)  // 这是播放函数
{
	u32 hz, tone;
	u32 i, j;
	tone = play_tone[sta]; 		//由当前播放的位置获取声调
	hz = play_hz[tone]; 		//由声调获取频率
	for(j = 0; j < play_time[sta]; j++)
	{
		//通过控制下面的i < hz 右侧的值来控制单个音节的播放时间
		for(i = 0; i < hz / 3; i++) // 循环体内运行一次的周期=1s÷频率,所以整个循环体就是1s,就是一个音节1s
		{
			GPIO_ResetBits(GPIOA, GPIO_Pin_4);
			delay_us(500000 / hz);
			GPIO_SetBits(GPIOA, GPIO_Pin_4);
			delay_us(500000 / hz);	
		}
	}
}

int main(void)
{ 
	u32 a = 0;
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); 
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;	         
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;      
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    
	GPIO_Init(GPIOA, &GPIO_InitStructure);				  	 
	
	delay_init();//这个函数自备
	
	while(1)
	{
		for(a = 0; a < sizeof(play_tone) / sizeof(play_tone[0]); a++)
		{
			play(a);
		} 

	}
}

四.就酱

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐