STM32学习笔记——编程驱动蜂鸣器实现音乐播放

1. 硬件准备

  • STM32开发板:STM32F407系列
  • 蜂鸣器:常见的蜂鸣器分为两类:有源蜂鸣器和无源蜂鸣器。若使用有源蜂鸣器,只需提供电源和控制信号即可;若使用无源蜂鸣器,则需要控制频率。
  • 外接电源(可选):确保为蜂鸣器提供足够的电源。
  • 以下是蜂鸣器的原理图 

     

    2. 选择控制方式

    对于音乐播放,主要使用PWM(脉宽调制)信号来控制蜂鸣器的发声频率。通过改变PWM的占空比和频率,可以模拟不同的音调和音量。

    3. GPIO配置

  • 将蜂鸣器连接到STM32的某一个GPIO口,比如PA8或PB5。
  • 该GPIO口配置为输出模式。
  • 4. 定时器配置

    要通过PWM信号控制蜂鸣器的频率,我们通常使用STM32的定时器模块来产生PWM信号。

  • 选择定时器:选择一个适合的定时器,比如TIM2或TIM3。
  • 配置定时器:设置定时器的计数频率,计算所需的PWM频率(比如440Hz对应音乐中的A4音符)。
  • 5. 计算音符频率

     

    6. PWM输出控制

    通过定时器配置的PWM输出控制蜂鸣器的发声。需要设置:

  • PWM周期:对应音符的频率。
  • PWM占空比:设置蜂鸣器的响度,通常为50%(即高电平和低电平的时间相等)。
  • 7. 编写代码

    (1)编写一个延时函数
    /* 延时函数 */
    void delay_ms(uint32_t n)
    {
    	while(n--)
    	{
    		SysTick->CTRL = 0; /* 关闭系统定时器,才能对系统定时器进行配置 */
    		SysTick->LOAD = 168000-1;  /* 设置定时器重载值(168MHz 时钟频率下,每毫秒的计数值)
            168000 - 1 表示定时器计数到 167999 之后产生中断,这相当于 1 毫秒(168 MHz 时钟)*/
    		SysTick->VAL = 0; /* 清空当前计数值,还有清空COUNTFLAG标志位 */
    		SysTick->CTRL = 5; /* 启动定时器,使用处理器时钟频率(168 MHz),并使能定时器
    							5 是控制寄存器的值,低 2 位分别设置时钟源和使能定时器*/
    		
    		 /* 等待计时器计数完毕。通过检查 COUNTFLAG 标志位(CTRL寄存器的 16 位)*/
    		while ((SysTick->CTRL & 0x10000)==0);//,等待COUNTFLAG标志位置1,就是计数完毕
    	}
    	
    	SysTick->CTRL = 0; /* 关闭系统定时器 释放资源 */
    
    }
    (2)初始化GPIO和定时器 

     

    void led_init(void)
    {
    	/* 打开端口E F的硬件时钟(就是对硬件供电),默认状态下,所有时钟都是关闭 */
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOF,ENABLE);
    	
    	
    	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;//指定9引脚
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;//引脚工作在复用功能模式
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
    	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//如果外部没有上拉电阻,就配置推挽输出模式
    	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不需要使能上下拉电阻
    	GPIO_Init(GPIOF,&GPIO_InitStructure);
    
    	
    	
    	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;//指定13 14号引脚
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;//引脚工作在输出模式
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
    	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//如果外部没有上拉电阻,就配置推挽输出模式
    	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不需要使能上下拉电阻
    	GPIO_Init(GPIOE,&GPIO_InitStructure);	
    	
    	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;//指定13 14号引脚
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;//引脚工作在输出模式
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
    	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//如果外部没有上拉电阻,就配置推挽输出模式
    	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不需要使能上下拉电阻
    	GPIO_Init(GPIOE,&GPIO_InitStructure);
    }
    void tim13_init(void)
    {
    	/* 打开TIM13的硬件时钟,对TIM13供电 */
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM13,ENABLE);	
    	
    	
    	/* 配置TIM13:分频值、计数值、时间更新中断 */
    	TIM_TimeBaseStructure.TIM_Period = 10000/100-1; //输出频率为100Hz (计数值:0~99 当完成100个计数,就输出一个脉冲)
    	TIM_TimeBaseStructure.TIM_Prescaler = 8400-1;//进行8400的分频,84MHz/(8400-1+1)
    	//TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV2;//不会生效
    	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
    	TIM_TimeBaseInit(TIM13, &TIM_TimeBaseStructure);
    	
    	
    	/* 配置TIM13的通道1 */
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//使能脉冲输出
    	TIM_OCInitStructure.TIM_Pulse = 0;//比较值0,蜂鸣器为关闭状态
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//有效状态为高电平
    	
    	TIM_OC1Init(TIM13, &TIM_OCInitStructure);
    	
    	
    	/* 使能TIM13工作 */
    	TIM_Cmd(TIM13, ENABLE);
    }
    (3)播放音符

    接下来,可以通过控制定时器的TIM_Period值来控制音符的频率。

    void tim13_set_freq(uint32_t freq)
    {
    	/* 关闭TIM13 */
    	TIM_Cmd(TIM13, DISABLE);
    	
        /*定时器的基本配置,用于配置定时器的输出脉冲的频率为 freq Hz */
        TIM_TimeBaseStructure.TIM_Period = (400000/freq)-1; //设置定时脉冲的频率
        TIM_TimeBaseStructure.TIM_Prescaler = 210-1; //第一次分频,简称为预分频
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
        tim13_cnt= TIM_TimeBaseStructure.TIM_Period;
        TIM_TimeBaseInit(TIM13, &TIM_TimeBaseStructure);
    	
    	/* 使能TIM13 */
    	TIM_Cmd(TIM13, ENABLE);	
    }
    
    void tim13_set_duty(uint32_t duty)
    {
        uint32_t cmp=0;
        cmp = (tim13_cnt+1) * duty/100;
        TIM_SetCompare1(TIM13,cmp);
    }

    8. 实现音乐播放

    通过将每个音符的频率和时长传入,依次播放不同的音符。可以使用延时函数来控制每个音符的播放时长。
    源代码:
     

    #include "stm32f4xx.h"
    
    #define PEout(n)	(*(uint32_t *)(0x42000000+(GPIOE_BASE+0x14-0x40000000)*32+n*4))
    #define PFout(n)	(*(uint32_t *)(0x42000000+(GPIOF_BASE+0x14-0x40000000)*32+n*4))
    
    
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    
    static uint32_t tim13_cnt=0;
    
    /* 毫秒延时函数 */
    void delay_ms(uint32_t n)
    {
    	while(n--)
    	{
    		SysTick->CTRL = 0; /* 关闭系统定时器,才能对系统定时器进行配置 */
    		SysTick->LOAD = 168000-1;  /* 设置定时器重载值(168MHz 时钟频率下,每毫秒的计数值)
            168000 - 1 表示定时器计数到 167999 之后产生中断,这相当于 1 毫秒(168 MHz 时钟)*/
    		SysTick->VAL = 0; /* 清空当前计数值,还有清空COUNTFLAG标志位 */
    		SysTick->CTRL = 5; /* 启动定时器,使用处理器时钟频率(168 MHz),并使能定时器
    							5 是控制寄存器的值,低 2 位分别设置时钟源和使能定时器*/
    		
    		 /* 等待计时器计数完毕。通过检查 COUNTFLAG 标志位(CTRL寄存器的 16 位)*/
    		while ((SysTick->CTRL & 0x10000)==0);//,等待COUNTFLAG标志位置1,就是计数完毕
    	}
    	
    	SysTick->CTRL = 0; /* 关闭系统定时器 释放资源 */
    
    }
    
    void delay_us(uint32_t n)
    {
    
    	SysTick->CTRL = 0; // 关闭系统定时器,才能对系统定时器进行配置
    	SysTick->LOAD = 168*n-1; // Count from 167 to 0 (168 cycles)
    	SysTick->VAL = 0; // Clear current value as well as count flag,清空当前计数值,还有清空COUNTFLAG标志位
    	SysTick->CTRL = 5; // Enable SysTick timer with processor clock,使用处理器时钟频率168MHz,并使能系统定时器
    	while ((SysTick->CTRL & 0x10000)==0);// Wait until count flag is set,等待COUNTFLAG标志位置1,就是计数完毕
    	SysTick->CTRL = 0; // Disable SysTick,关闭系统定时器	
    
    }
    
    void led_init(void)
    {
    	/* 打开端口E F的硬件时钟(就是对硬件供电),默认状态下,所有时钟都是关闭 */
    	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOF,ENABLE);
    	
    	
    	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8;//指定9引脚
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;//引脚工作在复用功能模式
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
    	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//如果外部没有上拉电阻,就配置推挽输出模式
    	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不需要使能上下拉电阻
    	GPIO_Init(GPIOF,&GPIO_InitStructure);
    
    	
    	
    	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;//指定13 14号引脚
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;//引脚工作在输出模式
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
    	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//如果外部没有上拉电阻,就配置推挽输出模式
    	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不需要使能上下拉电阻
    	GPIO_Init(GPIOE,&GPIO_InitStructure);	
    	
    	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14;//指定13 14号引脚
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;//引脚工作在输出模式
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
    	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//如果外部没有上拉电阻,就配置推挽输出模式
    	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不需要使能上下拉电阻
    	GPIO_Init(GPIOE,&GPIO_InitStructure);
    }
    
    
    void tim13_init(void)
    {
    	/* 打开TIM13的硬件时钟,对TIM13供电 */
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM13,ENABLE);	
    	
    	
    	/* 配置TIM13:分频值、计数值、时间更新中断 */
    	TIM_TimeBaseStructure.TIM_Period = 10000/100-1; //输出频率为100Hz (计数值:0~99 当完成100个计数,就输出一个脉冲)
    	TIM_TimeBaseStructure.TIM_Prescaler = 8400-1;//进行8400的分频,84MHz/(8400-1+1)
    	//TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV2;//不会生效
    	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
    	TIM_TimeBaseInit(TIM13, &TIM_TimeBaseStructure);
    	
    	
    	/* 配置TIM13的通道1 */
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;//使能脉冲输出
    	TIM_OCInitStructure.TIM_Pulse = 0;//比较值0,蜂鸣器为关闭状态
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//有效状态为高电平
    	
    	TIM_OC1Init(TIM13, &TIM_OCInitStructure);
    	
    	
    	/* 使能TIM13工作 */
    	TIM_Cmd(TIM13, ENABLE);
    }
    
    void tim13_set_freq(uint32_t freq)
    {
    	/* 关闭TIM13 */
    	TIM_Cmd(TIM13, DISABLE);
    	
        /*定时器的基本配置,用于配置定时器的输出脉冲的频率为 freq Hz */
        TIM_TimeBaseStructure.TIM_Period = (400000/freq)-1; //设置定时脉冲的频率
        TIM_TimeBaseStructure.TIM_Prescaler = 210-1; //第一次分频,简称为预分频
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
        tim13_cnt= TIM_TimeBaseStructure.TIM_Period;
        TIM_TimeBaseInit(TIM13, &TIM_TimeBaseStructure);
    	
    	/* 使能TIM13 */
    	TIM_Cmd(TIM13, ENABLE);	
    }
    
    void tim13_set_duty(uint32_t duty)
    {
        uint32_t cmp=0;
        cmp = (tim13_cnt+1) * duty/100;
        TIM_SetCompare1(TIM13,cmp);
    }
    
    
    
    /* 主函数 */
    int main(void)
    {		
    	led_init();
    	
    	/* PF8连接到TIM13 */
    	GPIO_PinAFConfig(GPIOF, GPIO_PinSource8, GPIO_AF_TIM13);
    	
    
    	tim13_init();
    	
    	while(1)
    	{
    		 /* 设置频率为523Hz,占空比15% */
            tim13_set_freq(523);
            tim13_set_duty(15);
            delay_ms(500);  // 延时500ms,确保每个音符的持续时间
    
            /* 设置频率为587Hz,占空比15% */
            tim13_set_freq(587);
            tim13_set_duty(15);
            delay_ms(500);
    
            /* 设置频率为659Hz,占空比15% */
            tim13_set_freq(659);
            tim13_set_duty(15);
            delay_ms(500);
            
            /* 设置频率为698Hz,占空比15% */
            tim13_set_freq(698);
            tim13_set_duty(15);
            delay_ms(500);
    
            /* 设置频率为784Hz,占空比15% */
            tim13_set_freq(784);
            tim13_set_duty(15);
            delay_ms(500);
    
            /* 设置频率为880Hz,占空比15% */
            tim13_set_freq(880);
            tim13_set_duty(15);
            delay_ms(500);
            
            /* 设置频率为988Hz,占空比15% */
            tim13_set_freq(988);
            tim13_set_duty(15);
            delay_ms(500);
    	}
    }
    
    
    

     可以在STM32上使用定时器和PWM信号控制蜂鸣器,实现简单的音乐播放。可以根据需要添加更多音符,生成完整的曲谱,甚至实现更复杂的音效和音量控制。

    作者:青竹问道

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32学习笔记——编程驱动蜂鸣器实现音乐播放

    发表回复