【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