本帖最后由 xld0932 于 2022-4-11 12:09 编辑
#申请原创# @21小跑堂
常见的蜂鸣器有2种:有源蜂鸣器和无源蜂鸣器,这边的源并非指电源,而是指震荡源。有源蜂鸣器内部自带有震荡源,所以只要一通上电,蜂鸣器就会发出响声;而无源蜂鸣器内部不带震荡源,所以上电之后不会发出响声,需要使用频率方波去驱动它,使其发出声音。所以一般情况下有源蜂鸣器要比无源蜂鸣器贵一些。
有源蜂鸣器对于驱动程序来说控制要方便一些,而无源蜂鸣器则是需要用频率方波来控制;而频率方波可以控制无源蜂鸣器的声音频率,从而可以发送不同音调的效果来;而有源蜂鸣器则做不到不同音调的效果,最多只能调节音量的大小。
下面我们通过3个小节来分别实现不同蜂鸣器的不同实现方式,程序中我们使用SHELL的调用方式来进行测试运行: 1、有源蜂鸣器开关控制 2、有源蜂鸣器音量控制 3、无源蜂鸣器播放音乐(我和我的祖国)
1、有源蜂鸣器开关控制
有源蜂鸣器内部自带了震荡电路,所以我们只需要控制是否给有源蜂鸣器供电,就可以控制有源蜂鸣器是否发出响声了,控制原理与控制LED灯类似。
1.1、有源蜂鸣器开关控制程序
void BUZZER_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_SET);
}
void BUZZER_SHELL_Handler(void)
{
if(!GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_3))
{
GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_SET);
}
else
{
GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_RESET);
}
}
SHELL_EXPORT_CMD(BUZZER, BUZZER_SHELL_Handler, BUZZER On Or Off);
1.2、有源蜂鸣器开关控制运行
2、有源蜂鸣器音量控制
不同的有源蜂鸣器的音调不尽相同,但同一个有源蜂鸣器的音调确是固定的(内部震荡电路固定导致),我们虽然不能像无源蜂鸣器那样改变它的音调,但我们可以通过频率方波中的占空比来调节有源蜂鸣器发声的音量。
2.1、有源蜂鸣器音量控制程序
void BUZZER_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq(&RCC_Clocks);
RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = RCC_Clocks.PCLK1_Frequency * 2 / 1000000 - 1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 500 - 1;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500 - 1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OC4Init(TIM2, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM2, ENABLE);
TIM_Cmd(TIM2, ENABLE);
TIM_CtrlPWMOutputs(TIM2, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_2); /* TIM2_CH4 */
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void BUZZER_SHELL_Handler(uint16_t Volume)
{
if(Volume < 500)
{
TIM_SetCompare4(TIM2, (499 - Volume));
}
}
SHELL_EXPORT_CMD(BUZZER, BUZZER_SHELL_Handler, Adjust BUZZER Volume);
2.2、有源蜂鸣器音量控制运行
3、无源蜂鸣器播放音乐
无源蜂鸣器内部没有集成震荡电路,而是通过外部的频率方波来驱动,反而相比于有源蜂鸣器内部固定的震荡电路更具有灵活性,从而可以通过程序来驱动无源蜂鸣器发出“哆来咪发唆拉西”的不同音调效果。
在播放音乐之前,我们先来看一下下面所示的乐谱:
音调就是声音频率的高低,哆来咪发唆拉西对应不同的声音频率;同时也与声音强度有关,不同的频率可以体现出不同的音域,就是我们常说的高音、中音、低音等等。
节拍是乐曲中表示固定单位时值和强弱规律的组织形式,又叫作拍子;例如2/4表示以4分音符为1拍,每小节有2拍。而拍号中时值的实际时间,由乐曲标定的速度决定。在不同节拍类型中,每个小节只有一个强拍的叫做单拍子,如2/4、2/8是单2拍子,3/4、3/8是单3拍子;每个小节有一个强拍和次强拍的叫做复拍子,如4/4、6/8是复2拍子,9/8、9/16是复3拍子。其它还有混合拍子等等,有兴趣的小伙伴儿可以去百度深入了解一下。
上面提到了乐曲标定的速度,这个速度一般在乐谱上有注明,它的注明有多种形式的,有直接的速度标记的数字,表示拍每分钟;还有间接的用力度标记、表情术语标记的这些非固定数值的,就需要百度一下对照表了。
在了解上面的几个术语后,我们就可以结合乐谱来编写数据,播放音乐了。
3.1、无源蜂鸣器播放音乐程序
在使用定时器实现频率方波时需要注意定时器是16位的还是32位,在实现不同频率时,装载值是根据定时器的总结频率和实际需要发出声音的频率计算出来的,这个装载值必须限制在定时器的有效范围内;所以当装载值大于定时器有效值时,就需要降低定时器的总线频率,来达到计算出有效装载值的效果。
在如下程序中,TIM2的总线时钟频率已经降低至9MHz了,修改的地方在system_mm32f0140.c文件中的SetSysClockToXX_HSI函数,将APB1进行了8分频操作:RCC->CFGR |= (u32)RCC_CFGR_PPRE1_DIV8,这样APB1即为9MHz,在后面计算装载值的时候,可以保证所有的装载值都在有效的16位数的数值范围内。
const uint16_t TuneFREQ[] =
{
0, //0
262, //1.Do ---低音
294, //2.Re
330, //3.Mi
349, //4.Fa
392, //5.So
440, //6.La
494, //7.Si
0, //8
0, //9
0, //10
523, //11.Do ---中音
587, //12.Re
659, //13.Mi
698, //14.Fa
784, //15.So
880, //16.La
988, //17.Si
0, //18
0, //19
0, //20
1046, //21.Do ---高音
1175, //22.Re
1318, //23.Mi
1397, //24.Fa
1568, //25.So
1760, //26.La
1976, //27.Si
0, //28
0, //29
0, //30
277, //31.Do ---低音#
311, //32.Re
330, //33.Mi
370, //34.Fa
415, //35.So
466, //36.La
494, //37.Si
0, //38
0, //39
0, //40
554, //41.Do ---中音#
622, //42.Re
659, //43.Mi
740, //44.Fa
831, //45.So
932, //46.La
988, //47.Si
0, //48
0, //49
0, //50
1109, //51.Do ---高音#
1245, //52.Re
1318, //53.Mi
1480, //54.Fa
1661, //55.So
1865, //56.La
1976, //57.Si
0, //58
0, //59
};
/* 我和我的祖国 --- 音调 */
const uint8_t SongTone[] =
{
15,16,15,14,13,12, 11,5, 11,13,21,17,16,13, 15,15, 16,17,16,15,14,13,
12,6, 7,6,5,15,11,12, 13,13, 15,16,15,14,13,12, 11,5,
11,13,21,17,22,21, 16,16, 21,17,16,15, 16,15,14,13, 7,6,5,12,
11,11, 21,22,23,22,21,16, 17,16,13,15,15, 21,22,23,22,21,16, 17,15,13,16,16,
15,14,13,12, 7,6,5,13, 13,12,11, 11,11,0, 21,22,23,22,21,16, 17,16,13,15,15,
21,22,23,22,21,16, 17,15,13,16,16, 15,14,13,12, 7,6,5,13, 15,22,21, 21,21,
};
/* 我和我的祖国 --- 节拍 */
const uint8_t SongBeat[] =
{
2,2,2,2,2,2, 6,6, 2,2,2,2,3,1, 6,6, 2,2,2,2,2,2,
6,6, 2,2,2,2,3,1, 6,6, 2,2,2,2,2,2, 6,6,
2,2,2,2,3,1, 6,6, 2,2,2,6, 2,2,2,6, 6,1,6,1,
6,6, 2,2,2,2,2,2, 2,3,1,6,6, 2,2,2,2,2,2, 2,3,1,6,6,
2,2,2,6, 2,1,1,2,6, 6,6,1, 6,6,1, 2,2,2,2,2,2, 2,3,1,6,6,
2,2,2,2,2,2, 2,3,1,6,6, 2,2,2,6, 2,2,2,6, 6,6,6, 6,6,
};
void BUZZER_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq(&RCC_Clocks);
uint32_t Period = (RCC_Clocks.PCLK1_Frequency * 2 / 2700 - 1);
RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = Period;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OC4Init(TIM2, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM2, ENABLE);
TIM_Cmd(TIM2, ENABLE);
TIM_CtrlPWMOutputs(TIM2, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_2); /* TIM2_CH4 */
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void BUZZER_SetFrequency(uint32_t Frequency)
{
uint32_t Period = 0;
if(Frequency != 0)
{
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq(&RCC_Clocks);
Period = (RCC_Clocks.PCLK1_Frequency * 2 / Frequency - 1);
}
printf("\r\n%d : %d", Frequency, Period);
TIM_SetAutoreload(TIM2, Period);
TIM_SetCompare4( TIM2, Period / 2);
}
void BUZZER_PlaySong(void)
{
for(uint32_t i = 0; i < sizeof(SongTone); i++)
{
BUZZER_SetFrequency(TuneFREQ[SongTone[i]]);
SysTick_DelayMS(SongBeat[i] * 172);
}
BUZZER_SetFrequency(0);
}
3.2、无源蜂鸣器播放音乐运行
附件
|