WeChat_20231023233924
前言
前面我们实现了PWM的输出,可以设置指定的占空比和频率。基于此我们可以实现PWM播放音频。
PWM播放音频有两种方式
一种是PWM输出不同频率对应不同音调,类似于MIDI音。这种相对对于PWM更新频率要求低,一个音在一个演奏时间内不变。
基本原理如下:
频率-对应音调
占空比-对应响度
PWM输出时长-对应演奏时长
另外一种方式即PWM-DAC方式,PWM输出不同占空比表示不同的DAC输出值,这种需要较高的PWM更新频率。
我们现在来使用前者实现音乐播放。
准备
我们需要先将乐谱转化为PWM的频率和占空比以及演奏时长的参数。
以国际标准音A-la-440HZ为准:
do的频率为261.6HZ,
re的频率为293.6HZ,
mi的频率为329.6HZ,
fa的频率为349.2HZ,
sol的频率为392HZ,
la的频率为440HZ,
si的频率为493.8HZ。
我们以”两只老虎为例”
先看左上角的节拍说明:2/4拍子 4分音符一拍 每小节2拍
一分钟76拍。
先定义如下音阶和频率的对应关系
#define DO 523
#define RE 587
#define MI 659
#define FA 698
#define _SO 392
#define SO 784
#define LA 880
#define SI 987
#define NO 523
#define DUTY0 0
我们可以看到如果主频是100MHz则最大16位65535分频,最低频率只能到
1525HZ,所以需要降低主时钟源频率,我们改为64分频
相应的如下函数也要修改
void pwm_set_frq_period(uint32_t duty, uint32_t frq)
{
uint32_t period = (uint64_t)(100000000ul/64)/frq;
duty = ((uint64_t)duty*period*2+100)/200;
set_intensity(duty, period, TIMER_PIN);
}
定义节拍时间
#define METERS 76ul // 拍数76 1分钟76拍 每拍60/76秒 2/4 4分音符一拍 每小节2拍
#define NOTE_4 (60000ul/METERS) // 先定一拍对应的音符 4分音符 x1000转化为单位mS
#define NOTE_8 (NOTE_4/2ul) // 8分音符 一个下划线
#define NOTE_16 (NOTE_8/2ul) // 16分音符 二个下划线
定义音阶表,对应频率
const uint16_t beepfrep[] =
{
DO,RE,MI,DO,
DO,RE,MI,DO,
MI,FA,SO,NO,
MI,FA,SO,NO,
SO,LA,SO,FA,MI,DO,
SO,LA,SO,FA,MI,DO,
DO,_SO,DO,NO,
DO,_SO,DO,NO
};
定义声音大小表,对应占空比
const uint16_t beepduty[] =
{
40,40,40,40,
40,40,40,40,
40,40,40,DUTY0,
40,40,40,DUTY0,
40,40,40,40,40,40,
40,40,40,40,40,40,
40,40,40,DUTY0,
40,40,40,DUTY0,
};
定义节拍表,对应每个音阶的演奏时间
const uint32_t beeptime[]=
{
NOTE_8,NOTE_8,NOTE_8,NOTE_8,
NOTE_8,NOTE_8,NOTE_8,NOTE_8,
NOTE_8,NOTE_8,NOTE_8,NOTE_8,
NOTE_8,NOTE_8,NOTE_8,NOTE_8,
NOTE_16,NOTE_16,NOTE_16,NOTE_16,NOTE_8,NOTE_8,
NOTE_16,NOTE_16,NOTE_16,NOTE_16,NOTE_8,NOTE_8,
NOTE_8,NOTE_8,NOTE_8,NOTE_16,
NOTE_8,NOTE_8,NOTE_8,NOTE_16,
};
实现延时函数,单位mS
void mdelay(uint32_t t)
{
R_BSP_SoftwareDelay(t,BSP_DELAY_UNITS_MILLISECONDS);
}
实现演奏函数
extern void pwm_set_frq_period(uint32_t duty, uint32_t frq);
void beep_play_music(const uint16_t* freq,const uint16_t* duty, const uint32_t* time, uint16_t len)
{
uint16_t i = 0;
for(i=0; i< len; i++)
{
pwm_set_frq_period(duty[i],freq[i]);
mdelay(time[i]);
}
}
测试
添加测试函数
void pwm_audio_test(void)
{
beep_play_music(beepfrep, beepduty, beeptime, sizeof(beepfrep)/sizeof(beepfrep[0]));
}
shell_func.c中添加命令行
{ (const uint8_t*)"pwmaudio", PwmaudioFun, "pwmaudio"},
void pwm_audio_test(void);
void PwmaudioFun(unsigned char* param)
{
(void)param;
pwm_audio_test();
}
shell_func.h中添加申明
void PwmaudioFun(unsigned char* param);
可以看到添加的命令如下
接上蜂鸣器测试效果详见视频
总结
- 可以添加更多的二乐谱实现不同音乐播放。
- 还可以将上述阻塞延时改为定时中断处理,这样非阻塞更方便使用。