STM32 | 有源蜂鸣器响,无源蜂鸣器播音乐

发布于:2025-07-22 ⋅ 阅读:(12) ⋅ 点赞:(0)

目录

 Overview

有源蜂鸣器

无源蜂鸣器

有源蜂鸣器控制

GPIO配置

控制程序

无源蜂鸣器控制

反转GPIO控制

GPIO配置

控制接口

PWM控制

GPIO配置

控制函数

改变频率播音乐

原理

1. 频率决定音调

2. 占空比决定音量

GPIO初始化

结构体定义和音符频率表

 播放接口

播放生日快乐歌


 Overview

有源蜂鸣器

【结构与工作原理】

内部集成了振荡电路,只需提供直流电源(通常为 3-5V)即可发声,频率固定(常见为 2-4kHz)。

【驱动电路】

驱动简单,直接通过 GPIO 控制通断即可。需注意添加限流电阻(约 100Ω)保护 IO 口。

无源蜂鸣器

【结构与工作原理】

内部不含振荡电路,需要外部提供 PWM(脉冲宽度调制)信号驱动,通过调整频率和占空比可发出不同音调

【驱动电路】

需要 PWM 信号驱动,需配置 STM32 的定时器产生特定频率的方波。例如,若需发出 4kHz 的声音,则 PWM 频率应设为 4kHz。

有源蜂鸣器控制

GPIO配置

我的这个有源蜂鸣器模块带三个引脚,分别是VCC,I/O和GND,I/O口输入高电平时,蜂鸣器响。

(也有一些有源蜂鸣器只有两个引脚,当电压差满足驱动就会响)

  • 连接I/O引脚的GPIO引脚,配置为简单的输出即可。
// 外部有源蜂鸣器
void ExtActiveBuzzerInit()
{
    GPIO_InitTypeDef  GPIO_InitStructure;
        
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);	 //使能GPIOE端口时钟
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;				 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	 //速度为50MHz
    GPIO_Init(GPIOE, &GPIO_InitStructure);	 //根据参数初始化
    
    GPIO_ResetBits(GPIOE, GPIO_Pin_2);//输出0,关闭蜂鸣器输出
}

控制程序

  • 一秒响,一秒不响
void task1_task(void *pvParameters)
{

	ExtActiveBuzzerInit();

	while(1)
	{
		SetExtActiveBuzzer(1);
		printf("SetExtActiveBuzzer(1)\r\n");

		delay_ms(1000);
		
		SetExtActiveBuzzer(0);
		printf("SetExtActiveBuzzer(0)\r\n");
		
		delay_ms(1000);
	}
}

无源蜂鸣器控制

        无源蜂鸣器内部不含振荡电路,需要外部提供 PWM(脉冲宽度调制)信号驱动,通过调整频率和占空比可发出不同音调。

        不使用 PWM 的情况下,也可以通过 GPIO 软件翻转模拟 PWM 信号驱动无源蜂鸣器

反转GPIO控制

因为想观察不同的频率,所以我配置了两个GPIO,以不同频率做电平翻转

GPIO配置

void ExtPassiveBuzzerInit()
{
	GPIO_InitTypeDef  GPIO_InitStructure;
        
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);	    //使能GPIOE端口时钟
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;	                //BEEP 端口配置
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		    //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	        //速度为50MHz
    GPIO_Init(GPIOE, &GPIO_InitStructure);	                    //根据参数初始化

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_Init(GPIOE, &GPIO_InitStructure);

}

控制接口

一个基础的循环周期为100us

  • 对于PE1,每100us后,电平翻转一次,所以方波周期是200us,频率就是5KHz
  • 对于PE3,每1000us(1ms)后,电平翻转一次,所以方波周期是2ms,频率就是500Hz

频率决定音调的高低,而更高的频率通常会让人感觉 “更刺耳”

void task1_task(void *pvParameters)
{
	int i = 0;
	int bE1Set = 0;
	int bE3Set = 0;
	ExtPassiveBuzzerInit();

	
	while (1)
	{
		i++;
		if (i >= 10000) 
		{
			printf("i = %d \r\n",i);
			i = 0;
		}


		if ( i % 2 == 0)
		{
			if (bE1Set == 1)
			{
				GPIO_ResetBits(GPIOE, GPIO_Pin_1);
				bE1Set = 0;
			}
			else
			{
				GPIO_SetBits(GPIOE, GPIO_Pin_1);
				bE1Set = 1;
			}
		}

		if ( i % 20 == 0)
		{
			if (bE3Set == 1)
			{
				GPIO_ResetBits(GPIOE, GPIO_Pin_3);
				bE3Set = 0;
			}
			else
			{
				GPIO_SetBits(GPIOE, GPIO_Pin_3);
				bE3Set = 1;
			}
		}

		// 半个周期100us
		delay_us(100);

	}


}

PWM控制

使用PA6,复用TIM3的通道1

GPIO配置

void ExtPassiveBuzzerPWMInit()
{
    GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

    // 使用PA6,做TIM3_CH1
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    //设置该引脚为复用输出功能,输出TIM3 CH1的PWM脉冲波形	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; 			//TIM_CH1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  	//复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);				//初始化GPIO

    
    //初始化TIM3
	TIM_TimeBaseStructure.TIM_Period = 35999; 			//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler = 0; 			//设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; 		//设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 	//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	//初始化TIM3 Channel1 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; 				//选择定时器模式:TIM脉冲宽度调制模式
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; 	//比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; 		//输出极性:TIM输出比较极性高
	TIM_OC1Init(TIM3, &TIM_OCInitStructure);  						//根据T指定的参数初始化外设TIM3
 
	TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR1上的预装载寄存器
 
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3

}

控制函数

占空比会用于实现音量高低变化

void task1_task(void *pvParameters)
{
	ExtPassiveBuzzerPWMInit();

    while(1)
    {
        printf("Beep --- High Volume\r\n");
        // 高音量 (占空比约75%)
        TIM_SetCompare1(TIM3, 27000);  // 36000 * 0.75 = 27000
        delay_ms(1000);
        
        printf("Beep --- Low Volume\r\n");
        // 低音量 (占空比约10%)
        TIM_SetCompare1(TIM3, 3600);
        delay_ms(1000);
    }
}

改变频率播音乐

原理

PWM(脉冲宽度调制)通过改变输出信号的频率占空比,可以驱动无源蜂鸣器播放音乐。

如何理解频率和占空比的作用:

1. 频率决定音调
  • 频率:指 PWM 信号每秒的周期数(单位:Hz)。

  • 音调:不同频率对应不同音符,例如:

    • Do (C4) = 262 Hz

    • Re (D4) = 294 Hz

    • Mi (E4) = 330 Hz

通过快速切换 PWM 频率,可以组合出音乐旋律。

2. 占空比决定音量
  • 占空比:指高电平时间占整个周期的比例(通常用百分比表示)。

  • 音量:对于无源蜂鸣器,50% 占空比通常能产生最大音量,因为此时信号对称,能充分驱动蜂鸣器振动。

GPIO初始化

和上面的函数一样,但是留出了控制频率的参数输入

void ExtPassiveBuzzerAdvancePWMInit(uint32_t frequency)
{
    GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;

    // 计算自动重装载值和预分频值
    uint32_t timer_clock = 72000000;  // 72MHz
    uint16_t psc = 1;
    uint16_t arr;

    // 使用PA6,做TIM3_CH1
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    //设置该引脚为复用输出功能,输出TIM3 CH1的PWM脉冲波形	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; 			//TIM_CH1
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  	//复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);				//初始化GPIO


    //初始化TIM3
    // 计算自动重装载值,确保不超过16位
    arr = timer_clock / (frequency * psc);
    if (arr > 65535) 
    {
        psc = timer_clock / (65535 * frequency) + 1;
        arr = timer_clock / (frequency * psc);
    }
    
	TIM_TimeBaseStructure.TIM_Period = arr; 			//设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseStructure.TIM_Prescaler = psc-1; 			//设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; 		//设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); 	//根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
	
	//初始化TIM3 Channel1 PWM模式	 
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; 				//选择定时器模式:TIM脉冲宽度调制模式
 	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; 	//比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; 		//输出极性:TIM输出比较极性高
	TIM_OC1Init(TIM3, &TIM_OCInitStructure);  						//根据T指定的参数初始化外设TIM3
 
	TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3在CCR1上的预装载寄存器
 
	TIM_Cmd(TIM3, ENABLE);  //使能TIM3

    // 设置初始占空比为50%
    TIM_SetCompare1(TIM3, arr / 2);

}

结构体定义和音符频率表

// 音乐结构定义
typedef struct {
    const uint8_t* melody;     // 音符数组
    const uint8_t* duration;   // 节拍数组
    uint8_t length;            // 音符长度
} Music_t;


// 音符频率表 (Hz)
const uint16_t notes[] = {
    0,    // 休止符
    262,  // C4 (Do)
    294,  // D4 (Re)
    330,  // E4 (Mi)
    349,  // F4 (Fa)
    392,  // G4 (Sol)
    440,  // A4 (La)
    494,  // B4 (Si)
    523   // C5 (Do高八度)
};

 播放接口

beat节拍,每一拍100ms

// 播放单个音符
void PlayNote(uint8_t note_index, uint8_t beat)
{
    uint16_t frequency;
    if (note_index > 8) return;  // 确保索引有效
    
    frequency = notes[note_index];
    
    if (frequency > 0) {
        // 初始化PWM频率
        ExtPassiveBuzzerAdvancePWMInit(frequency);
    } else {
        // 休止符: 关闭PWM输出
        TIM_SetCompare1(TIM3, 0);
    }
    
    // 播放指定节拍 (1拍 = 100ms)
    delay_ms(beat * 100);
}

// 播放音乐
void PlayMusic(const Music_t* music)
{
    uint8_t i;
    for (i = 0; i < music->length; i++) 
    {
        PlayNote(music->melody[i], music->duration[i]);
    }
    
    // 播放结束,关闭蜂鸣器
    TIM_SetCompare1(TIM3, 0);
}

播放生日快乐歌

音符(控制PWM频率)和节拍数(控制音符对于频率PWM持续时长)

void task1_task(void *pvParameters)
{
    // 定义任务局部的音乐结构体指针
    static Music_t birthday;
    
    // 示例音乐:生日快乐歌
    static const uint8_t birthday_melody[] = {5, 5, 6, 5, 1, 7, 5, 5, 6, 5, 2, 1, 5, 5, 5, 3, 1, 7, 6, 4, 4, 3, 1, 2, 1};
    static const uint8_t birthday_duration[] = {2, 2, 4, 4, 4, 8, 2, 2, 4, 4, 4, 8, 2, 2, 2, 4, 4, 4, 8, 2, 2, 4, 4, 4, 8};
    
    // 在任务启动时初始化结构体
    birthday.melody = birthday_melody;
    birthday.duration = birthday_duration;
    birthday.length = sizeof(birthday_melody);
			
    while(1)
    {
        // 循环播放生日快乐歌
        PlayMusic(&birthday);
        delay_ms(1000);  // 播放间隔
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到