【STM32】利用PWM输出播放音乐

一、前言

STM32是一款强大的微控制器,可以通过PWM(脉冲宽度调制)输出来控制电机、灯光等外部设备。除了这些常见用途外,STM32还可以通过PWM输出来播放音乐!

想要播放音乐使用无源蜂鸣器播放即可。无源蜂鸣器可以根据输入信号的频率发出对应不同频率的声音,可以满足播放简单乐曲。

使用STM32定时器产生PWM波形,通过该波形驱动无源蜂鸣器,调整PWM波的频率即可改变蜂鸣器的发声频率。

二、步骤

STM32通过PWM输出播放音乐的过程可以概括为以下几个关键步骤:

1. 确定音乐乐谱

使用延时函数和蜂鸣器进行乐曲的播放,当乐曲的音符较多时,代码非常繁琐

可以注意到,乐曲的每一个音符包含的信息有“频率”和“时长”两个信息,我们可以定义一个二维数组,数组的元素表示音符的频率和时长,整个数组表示一段音符,使用for循环即可播放这段音符。

定义音符频率:根据音乐理论中的音阶频率表,为每个音符定义其对应的频率值。例如,C4=262Hz, D4=294Hz等。示例代码如下:

定义音符与频率之间的对应关系:

#define L0 0 
#define L1 262
#define L2 294
#define L3 330
#define L4 349
#define L5 392
#define L6 440
#define L7 494

#define M0 0
#define M1 523
#define M2 587
#define M3 659
#define M4 698
#define M5 784
#define M6 880
#define M7 988

#define H1 1047
#define H2 1175
#define H3 1319
#define H4 1397
#define H5 1568
#define H6 1760
#define H7 1967

定义音符与时长间的关系:

#define T0  500
#define T1  T0/2
#define T2  T1/2

2. STM32 PWM配置

(1)初始化定时器:选择STM32的定时器(如TIM1、TIM2等),并配置其参数以产生PWM信号。设置预分频值,用于降低定时器时钟频率。此处以定时器TIM2为例:

static void TIM_PWMOUTPUT_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStructure;
    // 开启TIMx_CLK,x[2,3,4,5,12,13,14] 
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); 
     /* 累计 TIM_Period个后产生一个更新或者中断*/		
    //当定时器从0计数到8999,即为9000次,为一个定时周期
    TIM_TimeBaseStructure.TIM_Period = 1000-1;       
    // 通用控制定时器时钟源TIMxCLK = HCLK/2=90MHz 
    // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=100KHz
    TIM_TimeBaseStructure.TIM_Prescaler = 90-1;	
    // 采样时钟分频
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    // 计数方式
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    // 初始化定时器TIMx, x[2,3,4,5,12,13,14] 
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    /*PWM模式配置*/
    /* PWM1 Mode configuration: Channel1 */
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;	    //配置为PWM模式1
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	
    TIM_OCInitStructure.TIM_Pulse = 1000-1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;  	  //当定时器计数值小于CCR1_Val时为高电平
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);	 //使能通道1
    /*使能通道1重载*/
    TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);
    // 使能定时器
    TIM_Cmd(TIM2, ENABLE);	
}

(2)设置自动重装载值,确定PWM信号的周期。
(3)初始化输出比较参数,确定PWM信号的占空比。PWM信号保持高电平的时间百分比称为占空比。如果信号始终为高电平,则它处于100%占空比,如果它始终处于低电平,则占空比为0%。如图1所示,T1为占空比,T为一个PWM周期。播放声音我们让占空比为50%即可,主要是调整周期T。


(4)配置GPIO:将用于PWM输出的GPIO引脚配置为复用推挽输出模式,并连接到蜂鸣器。此处使用PA5引脚与蜂鸣器连接为例子,PA5引脚初始化:

static void TIMx_GPIO_Config(void) 
{
	/*定义一个GPIO_InitTypeDef类型的结构体*/
	GPIO_InitTypeDef GPIO_InitStructure;
 
	/*开启相关的GPIO外设时钟*/
	RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE); 
  /* 定时器通道引脚复用 */
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_TIM2); 
  
	/* 定时器通道引脚配置 */															   
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;    
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; 
	GPIO_Init(GPIOA, &GPIO_InitStructure);
}

3. 编写PWM控制函数

编写一个函数(如set_beep),该函数接受音符的频率作为输入,并配置PWM以产生该频率的输出。使用TIM_SetAutoreload函数修改自动重装载寄存器的值,以得到所需的PWM频率。

频率与重装载计数值的关系:由定时器配置代码可知,定时器的预分频为90,定时器的时钟源为90Mhz,经过90分频后定时器的时钟频率为1MHz,由T=1/f可得,计数器CNT每增加1的时间T1 = 1/1000000 (s)。以产生1KHz的PWM为例,其周期T2 = 1/1000(s),那么重装载计数值应为T2/T1,化简后为1000000/1000,也就是定时器的时钟频率除以要产生的PWM波的频率

4. 实现乐谱播放

(1)乐谱数组:将音乐乐谱转换为音符频率的数组。例如,int16_t music[] = { M3, M3, S, S, … },其中M3代表中音Mi的频率,S代表静音或短暂延时。此处以歌曲《猪猪侠》作为例子:

//猪猪侠
uint16_t music1[][2]=
{
	{M3,500},
	{M3,500},
	{M3,500},
	{M1,500},
	{M3,500},
	{M4,500},
	{M3,500},

	{M3,500},
	{M4,500},
	{M3,500},
	{M2,500},
	{M2,500},
	
	{M6,500},
	{M2,500},
	{M2,500},
	{M5,500},
	{M5,500},
	{M5,500},
	{M2,500},
	{M5,500},
	{M6,500},
	{M5,500},
  
	{M5,500},
	{M6,500},
	{M5,500},
	{M4,500},
	{M4,500},
	{M4,500},	
	{M5,500},
	{M4,500},
	{M3,500},
	
	{M3,500},
	{M3,500},
	{M3,500},
	{M1,500},
	{M3,500},
	{M4,500},
	{M3,500},

	{M3,500},
	{M4,500},
	{M3,500},
	{M2,500},
	{M2,500},
	{M2,500},
	
	{M6,500},
	{M2,500},
	{M2,500},
	{M5,500},
	{M5,500},
	{M5,500},
	{M2,500},
	{M5,500},
	{M6,500},
	{M5,500},
	
	{M5,500},
	{M6,500},
	{M7,500},
	{H1,500},
    {H1,500},
	{M7,500},
    {M7,500},
	{H1,500},
};

(2)播放函数:编写一个函数(如play_music),该函数遍历乐谱数组,并为每个音符调用PWM控制函数。在每个音符之间添加适当的延时,以模拟音乐的节奏。 以下为示例代码(需要注意的是i代表乐曲有几个音符):

void play1()
{
	static uint16_t i = 0;
	static uint16_t delay = 0;
	
	//delay等于0表示开始播放这个音符
	if(delay == 0)
		PWM_SetFrequency(music1[i][0]); //播放该音符
	
	//计算该音符已经播放的时间
	delay += 10;
	//如果该音符播放时间达到设置的时间
	if(delay>=music1[i][1])
	{
		//清空统计时间的变量
		delay = 0;
		//播放下一个音符
		i++;
		if(i>62) //乐曲一共有62个音符
			i = 0;
	}

}

5. 主程序调用

在STM32的主程序中,调用播放函数来播放乐谱。

int main(void)
{
	Delay_init();
	TIMx_Configuration();
	
		
	while(1)
	{		
		    Delay_ms(10);
			play1();
    }
}

三、注意事项

1.确保PWM的频率在蜂鸣器的响应范围内。
2.延时函数的精度对于音乐的节奏至关重要。
3.硬件连接需正确无误,包括电源、地线以及PWM输出引脚的连接。

四、总结

通过上述步骤和示例代码,STM32可以通过PWM输出播放音乐,实现嵌入式系统中的音频播放功能。STM32通过PWM输出播放音乐在嵌入式系统中有广泛的应用,如音乐盒、电子乐器、机器人声音提示等。通过编程和硬件设计,可以实现各种有趣和实用的音乐播放功能。

作者:Kanks

物联沃分享整理
物联沃-IOTWORD物联网 » 【STM32】利用PWM输出播放音乐

发表回复