发新帖本帖赏金 100.00元(功能说明)我要提问
返回列表
打印
[MM32生态]

【MM32+模块】系列:05、蜂鸣器控制

[复制链接]
1708|13
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 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/42/8是单2拍子,3/43/8是单3拍子;每个小节有一个强拍和次强拍的叫做复拍子,如4/46/8是复2拍子,9/89/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、无源蜂鸣器播放音乐运行




附件
软件工程源代码: 05.BUZZER.zip (711.55 KB)
无源蜂鸣器播放音乐效果: BUZZER_我和我的祖国.zip (365.98 KB)


使用特权

评论回复

打赏榜单

21小跑堂 打赏了 100.00 元 2022-04-12
理由:恭喜通过原创文章审核!请多多加油哦!

沙发
guijial511| | 2022-4-11 20:34 | 只看该作者
这年头,连个蜂鸣器也要做成模块吗?

使用特权

评论回复
板凳
xld0932|  楼主 | 2022-4-12 10:05 | 只看该作者
guijial511 发表于 2022-4-11 20:34
这年头,连个蜂鸣器也要做成模块吗?

淘宝上直接买的现成的,软件模块化编程,现在硬件也模块化了

使用特权

评论回复
地板
redone| | 2022-4-12 15:43 | 只看该作者
看楼主,貌似打算连载了,优秀

使用特权

评论回复
5
xld0932|  楼主 | 2022-4-12 20:11 | 只看该作者
redone 发表于 2022-4-12 15:43
看楼主,貌似打算连载了,优秀

点击头像“关注我”一起进步,在我的主页可以学习更多技术干货哦

使用特权

评论回复
6
tpgf| | 2022-5-3 10:07 | 只看该作者
需要加限流电阻吗

使用特权

评论回复
7
木木guainv| | 2022-5-3 10:17 | 只看该作者
非常不错的小玩意

使用特权

评论回复
8
磨砂| | 2022-5-3 10:25 | 只看该作者
小模块是自己做的还是买的啊

使用特权

评论回复
9
晓伍| | 2022-5-3 10:37 | 只看该作者
做成模块使用起来才方便啊

使用特权

评论回复
10
八层楼| | 2022-5-3 10:48 | 只看该作者
成熟了可以做对插哈  省的飞线了

使用特权

评论回复
11
观海| | 2022-5-3 10:58 | 只看该作者
呵呵 音效如何呀

使用特权

评论回复
12
xld0932|  楼主 | 2022-5-3 15:11 | 只看该作者
观海 发表于 2022-5-3 10:58
呵呵 音效如何呀

附件中有录的音,你可以下载下来试听一下哈

使用特权

评论回复
13
xld0932|  楼主 | 2022-5-4 22:03 | 只看该作者
磨砂 发表于 2022-5-3 10:25
小模块是自己做的还是买的啊

核心板是自己做的,其它的模块是买的

使用特权

评论回复
14
xld0932|  楼主 | 2022-5-4 22:04 | 只看该作者
八层楼 发表于 2022-5-3 10:48
成熟了可以做对插哈  省的飞线了

后面可以直接应用到项目中,就不用飞线了

使用特权

评论回复
发新帖 本帖赏金 100.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司资深现场应用工程师
简介:诚信·承诺·创新·合作

70

主题

3001

帖子

31

粉丝