STM32音乐播放器音频实验入门:PWM、DAC与.wav、.mp3格式详解

音频实验基础知识

简单的乐谱知识

音调:音阶分为中音、高音、低音,点在上面为高音,没有点为中音,点在下面为低音

音长:

简谱对应音阶下无横线为一拍,有单横线为半拍,双横线为1/4拍。音阶数字后有点加半拍音长,有横线加一拍。对应曲谱可以搜索一下。

	int16_t solitary_brave[]=
	{
		M6,50,M7,50,H1,50,H2,50,M7,50,H1,50,H1,100,Z0,10,	//爱你孤身走暗巷
		H1,50,M7,50,H1,50,H2,50,M7,50,H1,50,H1,100,Z0,10, 	//爱你不跪的模样
		H1,50,H2,50,H3,50,H2,50,H3,50,H2,50,H3,100,H3,50,H3,50,H2,50,H3,100,H5,100,H3,100,Z0,10 
		//爱你对峙过绝望不肯哭一场
	};

wav文件数据分析

wav文件1分析

wav文件2分析

00-03    52 49 46 46:RIFF的标志

04-07    24 90 01 00 :文件的长度

08-0B    57 41 56 45  :WAVE的标志

0C-0F   66 6D 74 20  :fmt的标志

10-13   10 00 00 00   :编码的格式类别,10H代表PCM形式。

14-15    01  00           :字块总数

16-17    01  00           : 通道数  1为单声道,2为双声道

18-1B   80 3E 00 00  :采样率:每秒采集二进制的位数。16000Hz

1C-1F   00 7D 00 00  :每秒播放的字节数 = 通道数 x 每秒采集二进制的位数 x 采集数据的位数 / 8.  32000

20-21   02 00 :每个样点的字节数

22-23   01 00 :每个样点的数据位数

24-27   64 61 74 61 :data的标志

28-2b   00 90 01 00 : 文件长度 3200KB

12位dac播放16位音频数据

一般WAV数据开始的第44字节开始就是wav数据,把wav数据取出后直接送到DAC播放。这里的WAV的dac数据是16bit的需要进行转换成12bit(stm32是12bit的DAC,所以整体需要右移4位,损失4bit的精度,保留大头)

//方法1:
buffer[i] = buffer[i]+0x8000;//16bit dac 数据为补码形式的,需要进行处理+0x8000
buffer[i] = buffer[i] >>num; //num 一般为4 右移4位剩下12bit dac数据

//方法二:
(buffer[0]+(buffer[1]-0x80)*256)>>4)//高位用补码形式保存的,转换时需要先减去0x80
//或者
tmpCap = (((u8)(buffer[1] - 0x80) << 4) | (buffer[0] >> 4));

设置time定时器的频率16K的采集率,每秒定时中断1/16000,在中断中填充音频数据到dac接口,即可实现普通dac播放音频文件!

 注意:

【1】用二进制补码表示的音频数据(MCU或者计算机数值⼀律⽤补码来表⽰和存储),数据有正负,所以需要转换;

【2】有符号数据的模拟量波形是在0值附近上下波动的,所以需要向上平移0x8000,后再取高12位的数据送到 12位的DAC里面 !

//测试1:
    s16 data1 = 1000;
    s16 data2 = -1000;
    printf("data             = %x,%x\n",data1,data2);
    printf("----------------------------------------------\n\n");
    u8 wav_data1[2] = {0xe8,0x03};//1000
    u8 wav_data2[2] = {0x18,0xfc};//-1000
    u16 dac_data1 =(((u8)(wav_data1[1]+0x80)<<8)|(wav_data1[0]));
    printf("wav_data1          = %d,%x\n",*(s16*)wav_data1,*(s16*)wav_data1);
    printf("dac_data1          = %d,%x\n",dac_data1,dac_data1);
    u16 tmp_data =(((u8)(wav_data1[1])<<8)|(wav_data1[0])) + 0x8000;
    printf("tmp_data           = %d,%x\n",tmp_data,tmp_data);

    u16 dac_data2 = (((u8)(wav_data2[1]+0x80)<<8)|(wav_data2[0]));
    printf("----------------------------------------------\n\n");
    printf("wav_data2          = %d,%x\n",*(s16*)wav_data2,*(s16*)wav_data2);
    printf("dac_data2          = %d,%x\n",dac_data2,dac_data2);
    tmp_data =(((u8)(wav_data2[1])<<8)|(wav_data2[0])) + 0x8000;
    printf("tmp_data           = %d,%x\n",tmp_data,tmp_data);
//测试2:
    s16 data1 = 1000;
    s16 data2 = -1000;
    printf("data             = %x,%x\n",data1,data2);
    printf("----------------------------------------------\n\n");
    u8 wav_data1[2] = {0xe8,0x03};//1000
    u8 wav_data2[2] = {0x18,0xfc};//-1000
    u16 dac_data1 =(((u8)(wav_data1[1]-0x80)<<8)|(wav_data1[0]));
    printf("wav_data1          = %d,%x\n",*(s16*)wav_data1,*(s16*)wav_data1);
    printf("dac_data1          = %d,%x\n",dac_data1,dac_data1);
    u16 tmp_data =(((u8)(wav_data1[1])<<8)|(wav_data1[0])) - 0x8000;
    printf("tmp_data           = %d,%x\n",tmp_data,tmp_data);

    u16 dac_data2 = (((u8)(wav_data2[1]-0x80)<<8)|(wav_data2[0]));
    printf("----------------------------------------------\n\n");
    printf("wav_data2          = %d,%x\n",*(s16*)wav_data2,*(s16*)wav_data2);
    printf("dac_data2          = %d,%x\n",dac_data2,dac_data2);
    tmp_data =(((u8)(wav_data2[1])<<8)|(wav_data2[0])) - 0x8000;
    printf("tmp_data           = %d,%x\n",tmp_data,tmp_data);

测试打印(目的是为了验证( buffer[1]+0x8000))以及 (buffer[1]-0x80)效果都是一样;

有符号减去或者加上0x8000计算的结果都是一样(因为两种情况s16/u16有符号数据都溢出了,所以计算结果一样);

有符号数据的模拟量波形是在0值附近上下波动的,所以需要向上平移0x8000

测试1打印:

data = 3e8,fffffc18

———————————————-

wav_data1 = 1000,3e8

dac_data1 = 33768,83e8

tmp_data = 33768,83e8

———————————————-

wav_data2 = -1000,fffffc18

dac_data2 = 31768,7c18

tmp_data = 31768,7c18

测试2打印:

data = 3e8,fffffc18

———————————————-

wav_data1 = 1000,3e8

dac_data1 = 33768,83e8

tmp_data = 33768,83e8

———————————————-

wav_data2 = -1000,fffffc18

dac_data2 = 31768,7c18

tmp_data = 31768,7c18

补码和原码之间的换算:

1.原码 转  补码:

    正整数的补码是它本身,即 1的原码是0000  0001 ,补码  还是  0000 0001.
    负整数的补码是符号位不变,其余位按位取反 ,再加1 ,例如  -1 的原码 1000 0001,补码  1111 1111 .

2.补码 转 原码

    正整数的补码即是原码。例,1 的补码是0000 0001 ,原码还是 0000 0001
    负整数已知补码求原码,只需要再对补码求一次补码即可,即负整数的补码的补码即是原码。
    例 -1 的补码  1111 1111 ,再取一次补码(符号位不变,其余位按位取反,再加1)即是原码  1000 0001.

 mcu数据以补码形式存储:

 1.pwm实现简易电子琴实验

1.改变PWM频率,输出不同音调
2.改变占空比,调节音量大小
3.按键弹奏,支持按按键录取弹奏音
4.播放:中高低音;录取音;指定歌曲
5.支持按上一首,下一首,调弹奏速度(播放时间),切模式,暂停播放

2.普通dac播放wav文件

3.普通dac播放mp3文件

4.编解码芯片vs1053播放wav文件

5.编解码芯片WM8978播放mp3文件

6.音频知识总结

(1)dac波形

dac引脚的波形:

接上106 10uf的电容,没接喇叭的波形:

(2)pwm波形

pwm引脚波形:

经过1uF电容的波形:

再经过1uf电容的波形(后输入功放):

 功放出来喇叭差分的波形:

作者:卓学电子

物联沃分享整理
物联沃-IOTWORD物联网 » STM32音乐播放器音频实验入门:PWM、DAC与.wav、.mp3格式详解

发表回复