一:
使用无源蜂鸣器进行音乐播放有两种方式,一种是通过PWM调节不同占空比达到音调的不同实现,一种是通过定时器驱动蜂鸣器IO口,利用定时器同样达到输出方波的效果。这里使用PWM进行演示。
首先需要明白使用PWM播放音乐的原理,利用占空比达到对应的音调,然后需要知道每个音调的持续时间。这就需要简单理解歌曲简谱,以新年好为例:
二:
简单简谱知识,左上角看出每小节3/4拍,两个竖线为一小节。音调有中高低区别,单个数字显示中音,数字下方有点代表低音,数字上方有点代表高音。区分节拍,全拍:数字 - - -;半拍:数字 -;四分子一拍:数字;八分之一拍:数字下面一条横线;十六分音符:数字下面两条横线。
如何知道每个音符对应的PWM占空比,利用下面的图:
忽略带#号的,我们需要通过调整PWM输出频率即可控制蜂鸣器发出不同的音调,实现通过蜂鸣器播放音乐。
例如,分频后频率为1MHz,且,频率已知,因此可以算出所需的T的值。例如低音1的
。
从而我们可以得到下列配置:
const uint16_t tone[21] = {3817,3401,3030,2865,2551,2272,2024,//低音0-6
1912,1703,1517,1432,1275,1136,1012,//中音7-13
956,851,758,715,637,568,506};//高音 14-20
根据简谱和上方配置将新年好的曲子按顺序播放:
const uint8_t music_tone[30] = {7,7,7,4,9,9,9,7,7,9,11,11,10,9,8,8,9,10,10,9,8,9,7,7,9,8,4,6,8,7};
例如第一个7代表简谱中的第一个数字1,为中音,指向tone[21]的第八位。
通过上面的说明可以知道这个简谱每个音符需要持续的时间,以四分音符为例,假设四分音符持续时间单位为4,那八分音符持续时间则为2,二分音符持续时间为8等,一一对应可以得到下面配置:
const uint8_t music_timer[30] = {2,2,4,4,2,2,4,4,2,2,4,4,2,2,8,2,2,4,4,2,2,4,4,2,2,4,4,2,2,8};
完整简谱配置如下:
const uint16_t tone[21] = {3817,3401,3030,2865,2551,2272,2024,//低音0-6
1912,1703,1517,1432,1275,1136,1012,//中音7-13
956,851,758,715,637,568,506};//高音 14-20
const uint8_t music_tone[30] = {7,7,7,4,9,9,9,7,7,9,11,11,10,9,8,8,9,10,10,9,8,9,7,7,9,8,4,6,8,7};
const uint8_t music_timer[30] = {2,2,4,4,2,2,4,4,2,2,4,4,2,2,8,2,2,4,4,2,2,4,4,2,2,4,4,2,2,8};
// 音符的频率数组(以 Hz 为单位)
接下来配置定时器PWM输出,相关说明不作解释:
void TIM4_PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
// GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5
//设置该引脚为复用输出功能,输出TIM4 CH3的PWM脉冲波形 GPIOB.8
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //TIM_CH4----------------------
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO
//初始化TIM4(根据)
// TIM_TimeBaseStructure.TIM_Period = 3816-1; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Period = 1;
TIM_TimeBaseStructure.TIM_Prescaler =72-1; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM3 Channel2 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = 0;//设置比较值比较直的范围决定了占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性:TIM输出比较极性高
TIM_OC3Init(TIM4, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM4 OC2
TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable); //使能TIM4在CCR2上的预装载寄存器与输出端口有关
TIM_Cmd(TIM4, ENABLE); //使能TIM4
}
void set_pwm(uint16_t Period,uint16_t Pulse)
{
TIM_SetAutoreload(TIM4,Period);
TIM_SetCompare3(TIM4,Pulse);
}
主函数main.c里:除了初始化外,需要添加的代码:
for(i = 0;i < 30; i++)
{
set_pwm(tone[music_tone[i]],tone[music_tone[i]]/15);
delay_ms(music_timer[i]*90.5);
}
play_flag = 0;
TIM_SetCompare3(TIM4,0);
delay_ms(500);
tone[music_tone[i]]/15,将声音调小也许可以避免声音太大无法分清每个音符,导致声音模糊。