从0开始编写VS1053音频解码芯片的底层驱动代码(适用于任何单片机)

VS1053是一个高性能的音频解码器芯片,它是干什么的? 他有两个功能:()用来解码音频文件播放音乐的。(2)将麦克风听到的声音编码成音频文件数据,配合单片机可以保存到SD卡。

单片机加上一个VS1053就可以轻松做一个音乐播放器、一个录音机 等项目。

VS1053支持两种协议:SPI协议和IIS协议。 这篇文章是介绍采用软件模拟的SPI协议,完成对VS1053芯片的控制。完成音频播放、录音的功能。

写出的这份代码不依赖于任何单片机型号,不依赖单片机的硬件本身功能,属于纯粹的软件层逻辑代码。不管你是51单片机、STM32单片机、还是树莓派等等单片机。都可以使用,都可以驱动VS1053。

文章目录

  • 一、从0开始学习VS1053音频(编码/解码)芯片寄存器与时序
  • 1.1 VS1053B音频编解码器简介
  • 【1】VS1053B功能介绍
  • 【2】VS1053B原理图介绍
  • 1.2 VS1053B音频解码芯片控制
  • 【1】VS1053B SPI模式时序图
  • 【2】SPI传输模式介绍
  • 【3】VS1053 SCI寄存器列表
  • 【4】MODE 寄存器介绍
  • 【5】BASS 寄存器介绍
  • 【6】CLOCKF寄存器介绍
  • 【7】DECODE_TIME寄存器介绍
  • 【8】HDAT0 和 HDTA1寄存器
  • 【9】VOL输出音量寄存器介绍 (VS1053_SetVol)
  • 1.3 VS1053B解码播放音乐步骤说明(VS1053_PlayOneMusic)
  • 【1】播放音乐流程
  • 【2】硬件复位步骤
  • 【3】进行软件复位并设置SPI为新模式
  • 【4】向VS1053写寄存器数据
  • 【5】读取VS1053寄存器的数据
  • 1.4 VS1053录音功能
  • 【1】VS1053录音功能说明与插件加载
  • 【2】激活PCM录音模式 (VS1053_RecoderInit)
  • 【3】WAV音频文件结构
  • 【4】读取咪头采集的音频数据
  • 【5】录音的流程(RecoderPlay)
  • 【6】WAV文件频率设置说明
  • 二、完整的代码
  • 2.1 vs1053.c
  • 2.2 vs1053.h
  • 一、从0开始学习VS1053音频(编码/解码)芯片寄存器与时序

    1.1 VS1053B音频编解码器简介

    【1】VS1053B功能介绍

    VS1053b 是单片Ogg Vorbis/MP3/AAC/WMA/MIDI音频解码器,及IMA ADPCM 编码器和用户加载的Ogg Vorbis编码器。

    支持:MP3/WMA/OGG/WAV/FLAC/MIDI/AAC 等音频格式的解码,并支持:OGG/WAV音频格式的录音,支持高低音调节设置,功能十分强大。

    它包含了一个高性能、有专利的低功耗DSP 处理器内核VS_DSP4、工作数据存储器、供用户应用程序和任何固化解码器一起运行的16 KiB 指令RAM 及0.5KiB 多的数据RAM、串行的控制和输入数据接口、最多8个可用的通用I/O引脚、一个UART、并有一个优质的可变采样率立体声ADC(“咪”、“线路”、“线路+咪”或“线路*2”)和立体声DAC、和跟随的一个耳机功放及一个公共电压缓冲器。

    【2】VS1053B原理图介绍

    下面是:原理图

    image-20240524103144752

    下面是:与开发板的硬件连接关系图

    XCS	命令片选(低电平有效)----用于传输命令
    XDCS	数据片选 (低电平有效)----用于传输数据
    SCK	SPI时钟线
    MOSI	SPI数据线(主出从入)
    MISO	SPI数据线(主入从出)
    DREQ	数据请求线,用于通知控制器,VS1053是否可以接收数据(高电平表示可以接收数据)
    RST	复位引脚(硬件复位,低电平有效)
    

    1.2 VS1053B音频解码芯片控制

    【1】VS1053B SPI模式时序图

    image-20240524103210400

    示例代码:

    u8 SPI_ReadWriteByte(u8 tx_data)
    {			  	 
    	u8 rx_data=0;				 
    	u8 i;
    	for(i=0;i<8;i++)
    	{
    		SCLK=0;  
    		if(tx_data&0x80)MOSI=1;
    		else MOSI=0;
    		tx_data<<=1;	
    		SCLK=1;
    		rx_data<<=1;
    		if(MISO)rx_data|=0x01;
    	}
    	return rx_data; 
    }
    

    【2】SPI传输模式介绍

    VS1053的SPI数据传送,分为SDI和SCI,SDI用来传输数据,SCI用于传输命令。

    SDI数据传输非常简单,就是标准的SPI通信,不过VS1053的数据传输都是通过DREQ控制到主机必须在判断DREQ 有效(高电平有效)后,才可以发送数据。

    SCI命令传输接口包含了一个指令字节、一个地址字节和一个16位的数据字。读写操作可以读写单个寄存器,在 SCK的上升沿读出数据位,所以主机必须在下降沿刷新数据。SCI的字节数据总是高位在前低位在后的。第一个字节指令字节,只有2个指令,也就是读和写,读指令为:0X03,写指令为:0X02。

    XCS 命令片选(低电平有效)—-用于传输命令
    XDCS 数据片选 (低电平有效)—-用于传输数据

    VS1053读取寄存器数据,通过先拉低 XCS,然后发送读指令(0X03),再发送一个地址,最后,我们在 SO 线(MISO)上就可以读到输出的数据了。而同时 SI(MOSI)上的数据将被忽略。

    VS1053写寄存器数据也是先发指令,再发地址。写时序中,指令(0X02),数据是通过 SI 写入 VS1053 的, SO 则一直维持低电平。
    在写入和读出数据之后,VS1053需要一些时间来处理内部的事情,这段时间,是不允许外部打断的,所以,在 SCI 操作之前,最好判断一 下DREQ 是否为高电平,如果不是,则等待 DREQ 变为高。

    这是: SCI读时序

    image-20240524103227282

    这是:SCI写时序

    image-20240524103236762

    【3】VS1053 SCI寄存器列表

    VS1053总共有16个 SCI寄存器,通过这些寄存器实现对VS1053的各种控制,VS1053的所有SCI 寄存器。

    SCI 寄存器

    SCI 寄存器
    寄存器 类型 复位值 缩写 描述
    0X00 RW 0X0800 MODE 模式控制
    0X01 RW 0X003C STATUS VS1053 状态
    0X02 RW 0X0000 BASS 内置高低音增强器
    0X03 RW 0X0000 CLOCKF 时钟频率+倍频数
    0X04 RW 0X0000 DECODE_TIME 解码时间
    0X05 RW 0X0000 AUDATA 各种音频数据
    0X06 RW 0X0000 WRAM RAM 写/读
    0X07 RW 0X0000 WRAMADDR RAM 写/读的基址
    0X08 R 0X0000 HDAT0 流头数据 0
    0X09 R 0X0000 HDAT1 流头数据 1
    0X0A RW 0X0000 AIADDR 用户代码起始地址
    0X0B RW 0X0000 VOL 音量控制
    0X0C RW 0X0000 AICTRL0 应用控制寄存器 0
    0X0D RW 0X0000 AICTRL1 应用控制寄存器 1
    0X0E RW 0X0000 AICTRL2 应用控制寄存器 2
    0X0F RW 0X0000 AICTRL3 应用控制寄存器 3

    VS1053寄存器定义:

    //VS10XX寄存器定义
    #define SPI_MODE        	0x00   
    #define SPI_STATUS      	0x01   
    #define SPI_BASS        	0x02   
    #define SPI_CLOCKF      	0x03   
    #define SPI_DECODE_TIME 	0x04   
    #define SPI_AUDATA      	0x05   
    #define SPI_WRAM        	0x06   
    #define SPI_WRAMADDR    	0x07   
    #define SPI_HDAT0       	0x08   
    #define SPI_HDAT1       	0x09 
    
    #define SPI_AIADDR      	0x0a   
    #define SPI_VOL         	0x0b   
    #define SPI_AICTRL0     	0x0c   
    #define SPI_AICTRL1     	0x0d   
    #define SPI_AICTRL2     	0x0e   
    #define SPI_AICTRL3     	0x0f 
    

    【4】MODE 寄存器介绍

    mode寄存器用于控制 VS1053 的模式,是最关键的寄存器之一,该寄存器的复位值为0x0800,其实就是默认设置为新模式。

    VS1053的SPI支持两种模式:
    1,VS1002有效模式(即新模式,此模式是 VS1053 的默认模式)
    2,VS1001 兼容模式。

    下面是 MODE 寄存器的各位描述:

    名称 功能 说明
    0 SM_DIFF 差分 0 正常的同相音频
    1 左通道反相
    1 SM_LAYER12 允许 MPEG layers I&II 解码 0 不允许
    1 允许
    2 SM_RESET 软件复位 0 不复位
    1 复位
    3 SM_CANCEL 取消当前文件的解码 0 不取消
    1 取消
    4 SM_EARSPEAKER_LO EarSpeaker 低设定 0 关闭
    1 激活
    5 SM_TESTS 允许 SDI 测试 0 不允许
    1 允许
    6 SM_STREAM 流模式 0 不是
    1
    7 SM_EARSPEAKER_HI EarSpeaker 高设定 0 关闭
    1 激活
    8 SM_DACT DCLK 的有效边沿 0 上升沿
    1 下降沿
    9 SM_SDIORD SDI 位顺序 0 1 MSB 在前 MSB 在后
    10 SM_SDISHARE 共享 SPI 片选 0 不共享
    1 共享
    11 SM_SDINEW VS1002 本地 SPI 模式 0 非本地模式
    1 本地模式(新模式)
    12 SM_ADPCM ADPCM 录音激活 0 不激活
    1 激活
    13 -SM_ADPCM_HP ADPCM 高通滤波允许 0 不允许
    1 允许
    14 SM_LINE_IN ADPCM 音源选择 0 麦克风
    1 线路输入
    15 SM_CLK_RANGE 输入时钟范围 0 12…13Mhz
    1 24…26Mhz

    这里 SM_RESET,可以提供一次软复位,建议在每播放一首歌曲之后,软复位一次。
    SM_SDINEW 为模式设置位,应该选择的是新模式,所以设置该位为 1(默认的设置)。

    【5】BASS 寄存器介绍

    bass寄存器可以用于设置 VS1053的高低音效。

    下面是寄存器的各位描述:

    序号 描述
    ST_AMPLITUDE 15:12 高音控制,1.5dB 步进(-8…7,0 表示关闭)
    ST_FREQLIMIT 11:8 最低频限 1000Hz 步进(0…15)
    SB_AMPLITUDE 7:4 低音加重,1dB 步进(0…15,0 表示关闭)
    SB_FREQLIMIT 3:0 最低频限 10Hz 步进(2…15)

    通过这个寄存器以上位的一些设置,我们可以随意配置自己喜欢的音效(其实就是高低音的调节)。

    【6】CLOCKF寄存器介绍

    clockf寄存器用来设置时钟频率、倍频等相关信息。

    下面是寄存器的各位描述:

    15:13 12:11 10:0
    名称 SC_MULT SC_ADD SC_FREQ
    描述 时钟倍频数 允许倍频 时钟频率
    说明 CLKI=XTALI×(SC_MULT×0.5+1) 倍频增量 =SC_ADD*0.5 当外部时钟频率不为 12.288Mhz 时, 外部时钟的频率。外部时钟频率为 12.288Mhz 时,此部分设置为 0

    SC_FREQ是以4Khz为步进的一个时钟寄存器,当外部时钟不是12.288M 的时候,其计算公式为:

    SC_FREQ=(XTALI-8000000)/4000 
    

    XTALI 的单位是Hz。

    CLKI 是内部时钟频率,XTALI 是外部晶振的时钟频率。开发板上的VS1053B使用的是 12.288M 的晶振,在这里设置此寄存器的值为0x9800,也就是设置内部时钟频率为输入时钟频率的3倍,倍频增量为1.0倍。

    【7】DECODE_TIME寄存器介绍

    DECODE_TIME寄存器是一个十六位的寄存器,用于存放解码时间,以秒钟为单位,我们通过读取该寄存器的值,就可以得到解码时间。不过它是一个累计时间,所以我们需要在每首歌播放之前把它清空一下,以得到这首歌的准确解码时间。

    【8】HDAT0 和 HDTA1寄存器

    HDAT0和HDTA1是两个数据流头寄存器,不同的音频文件,读出来的值意义不一样, 我们可以通过这两个寄存器来获取音频文件的码率,从而可以计算音频文件的总长度。这两个寄存器的详细介绍,请参考 VS1053 的数据手册。

    【9】VOL输出音量寄存器介绍 (VS1053_SetVol)

    VOL寄存器用于控制VS1053的输出音量,该寄存器可以分别控制左右声道的音量,每个声道的控制范围为 0~254,每个增量代表 0.5db 的衰减,所以该值越小,代表音量越大。

    比如:设置为 0x0000 则音量最大,而设置为0xFEFE 则音量 最小。
    注意:如果设置 VOL 的值为 0xFFFF,将使芯片进入掉电模式。

    //右声道是高8位  左声道是低8位
    设置示例:  u16 tmp=vol_R<<8|vol_L;
    VS1053_WriteCmd(SPI_VOL,tmp);//设音量
    

    1.3 VS1053B解码播放音乐步骤说明(VS1053_PlayOneMusic)

    【1】播放音乐流程

    (1)初始化与VS1053连接的单片机硬件GPIO口,默认上拉片选脚。

    (2)配置VS1053的模式为新模式

    (3)进行VS1053硬件复位和软件复位,为了让VS1053的状态回到原始状态,准备解码下一首歌曲。
    建议在每首歌曲播放之前都执行一次硬件复位和软件复位,以便更好的播放音乐,不出现杂音。

    (4)配置VS1053时钟寄存器(设置VS10XX的时钟,3倍频 ,1.5xADD)

    (5)设置VS1053播放音量

    (6)拉低VS1053_XDCS片选引脚,通过SPI发送函数给VS1053发送音乐数据,只要是 VS1053 支持的音频格式,直接往里面丢就可以了,VS1053 会自动识别,并进行播放。数据发送完毕再拉高VS1053_XDCS片选。
    注意: VS1053_DREQ为高电平时,才可继续给VS1053发送数据。

    【2】硬件复位步骤

    (1)拉低RESET脚,进行硬件复位 RESET=0

    (2)至少延时20ms,等待复位

    (3)取消数据传输 XDCS=1

    (4)取消命令传输 XCS=1

    (5)拉高RESET脚,完成复位 RESET=1

    【3】进行软件复位并设置SPI为新模式

    (1)先等待DREQ数据请求线为高电平

    (2)设置模式并启动软件复位(因为模式设置和软件复位在同一个寄存器里,所以可以同时操作)
    设置的寄存器是MODE寄存器,设置的值: 0x804

    (3)至少等待1.35ms完成复位

    (4)等待DREQ数据请求线为高电平,完成复位

    【4】向VS1053写寄存器数据

    写寄存器之前先等待DREQ为高电平,并将XDCS数据片选线拉高,然后拉低XCS写数据,写完拉高。

    image-20240524103340624

    【5】读取VS1053寄存器的数据

    读寄存器数据之前先等待DREQ为高,并将XDCS数据片选线拉高,然后拉低XCS写/读数据,写完拉高。

    image-20240524103353638

    1.4 VS1053录音功能

    录音功能详细说明,在VS103B中文手册的第9章大概53页,有详细说明。

    image-20240524103404362

    image-20240524103428274

    【1】VS1053录音功能说明与插件加载

    VLSI的VS10XX系列芯片都会有一些 patch(插件),用于修复芯片存在的 bug,或者用于增加新的功能。

    VS10XX的patch其实就是一段可以在 VS10XX 芯片上面执行的代码,由 VLSI 官方提供,可以在:http://www.vlsi.fi/en/support/software/vs10xxpatches.html这个地址下载到。

    patch一般有2种格式:一种是采用 16 位无符号数组存储的,是采用了游程编码(RLE) 压缩算法,进行过压缩的格式。另外一种,是直接采用 2 个 8 位数组存储的,没有进行压缩的格式。

    推荐采用16位无符号存储格式的 patch,更省空间。

    (1)VS1053 采用的 16 位 RLE 压缩编码规则如下:

    首先读寄存器地址 addr(第 1 个数据)和重复数 n(第 2 个数据)。

    (2)如果 n&0x8000 为真,那么将下一个数据(第 3 个数据)重复写 n 次到寄存器 addr。

    (3)如果 n&0x8000 为假,则写接下来的(第 3 个数据开始)n 个数据到寄存器 addr。

    (4)重复以上 3 步,直到数组结束。

    如VS1053,WAV录音无数据输出的bug,修正patch如下:

    const u16 VS1053_WavPlugin[40]=/* Compressed plugin */ 
    { 
    		0x0007, 0x0001, 0x8010, 0x0006, 0x001c, 0x3e12, 0xb817, 0x3e14, /* 0 */ 
    		0xf812, 0x3e01, 0xb811, 0x0007, 0x9717, 0x0020, 0xffd2, 0x0030, /* 8 */ 
    		0x11d1, 0x3111, 0x8024, 0x3704, 0xc024, 0x3b81, 0x8024, 0x3101, /* 10 */ 
    		0x8024, 0x3b81, 0x8024, 0x3f04, 0xc024, 0x2808, 0x4800, 0x36f1, /* 18 */ 
    		0x9811, 0x0007, 0x0001, 0x8028, 0x0006, 0x0002, 0x2a00, 0x040e,  
    };
    

    根据前面提到的原则,得到:addr=0x0007,n=0x0001,即将接下来的 0x8010 写入寄存器 0X0007 即可,之后:addr=0x0006,n=0x001C,即将接下来的 28 个数据,写入寄存器 0x0006。

    后续的以此类推。因此,我们可以得出 patch 加载函数(即 RLE 解压):

    void VS1053_LoadPatch(u16 *patch,u16 len)  //加载补丁
    {
    	u16 i; 
    	u16 addr, n, val; 	  			   
    	for(i=0;i<len;) 
    	{ 
    		addr = patch[i++]; 
    		n    = patch[i++]; 
    		if(n & 0x8000U) //RLE run, replicate n samples 
    		{ 
    			n  &= 0x7FFF; 
    			val = patch[i++]; 
    			while(n--)VS1053_WriteCmd(addr, val);  
    		}
    		else //copy run, copy n sample 
    		{ 
    			while(n--) 
    			{ 
    				val = patch[i++]; 
    				VS1053_WriteCmd(addr, val); 
    			} 
    		} 
    	} 	
    }
    

    【2】激活PCM录音模式 (VS1053_RecoderInit)

    如果是IMA ADPCM,采样率计算公式如下: 采样率=CLKI/256*d;

    假设d=0,并2倍频,外部晶振为12.288M.那么Fc=(212288000)/2566=16Khz

    如果是线性PCM,采样率直接就写采样值。

    (1)设置SPI_BASS寄存器的值为0

    (2)设置采样率为8Khz: 设置SPI_AICTRL0寄存器的值为8000

    (3)设置增益: 0表示自动增益,1024表示1倍,512表示0.5倍,最大值65535=64倍,设置SPI_AICTRL1寄存器的值推荐为1024

    (4)设置增益最大值: 设置SPI_AICTRL2寄存器的值为0

    (5)左通道(MIC单声道输入): 设置SPI_AICTRL3寄存器为6

    (6)设置VS1053的时钟: 设置SPI_CLOCKF值为0X2000

    (7)录音激活: 设置SPI_MODE寄存器的值为: 0x1804

    (8)延时5ms等待录音功能激活

    (9)加载VS1053WAV录音需要的patch(补丁)

    【3】WAV音频文件结构

    WAV头:

    typedef __packed struct
    {
    	//RIFF块
    	u32 RIFF_ChunkID;      //chunk id;这里固定为"RIFF",即 0X46464952
    	u32 RIFF_ChunkSize;     //集合大小;文件总大小(-8)
    	u32 RIFF_Format;        //格式;WAVE,即 0X45564157
    	//fmt块
    	u32 FMT_ChunkID;   //chunk id;这里固定为"fmt ",即 0X20746D66
    	u32 FMT_ChunkSize; //子集合大小(不包括 ID 和 Size);这里为:20.
    	u16 FMT_AudioFormat;   //音频格式;0X10,表示线性 PCM;0X11 表示 IMA ADPCM
    	u16 FMT_NumOfChannels; //通道数量;1,表示单声道;2,表示双声道;
    	u32 FMT_SampleRate; 	 //采样率;0X1F40,表示 8Khz
    	u32 FMT_ByteRate; 		 //字节速率;
    	u16 FMT_BlockAlign;    //块对齐(字节);
    	u16 FMT_BitsPerSample; //单个采样数据大小;4 位 ADPCM,设置为 4
    	//data块
    	u32 DATA_ChunkID;  //chunk id;这里固定为"data",即 0X61746164
    	u32 DATA_ChunkSize;//子集合大小(不包括 ID 和 Size);文件大小-60.
    }WaveHeader;
    

    WAV音频文件头初始化:

    wav->RIFF_ChunkID=0x46464952; //"RIFF"
    wav->RIFF_ChunkSize=0; 				//还未确定,最后需要计算
    wav->RIFF_Format=0x45564157;  //"WAVE"
    wav->FMT_ChunkID=0x20746D66;  //"fmt "
    wav->FMT_ChunkSize=16; //大小为 16 个字节
    wav->FMT_AudioFormat=0X01; //0X01,表示 PCM;0X01,表示 IMA ADPCM
    wav->FMT_NumOfChannels=1; //单声道
    wav->FMT_SampleRate=13000; //13Khz 采样率 采样速率
    wav->FMT_ByteRate=wav->FMT_SampleRate*2;//16 位,即 2 个字节
    wav->FMT_BlockAlign=2; //块大小,2 个字节为一个块
    wav->FMT_BitsPerSample=16; //16 位 PCM
    wav->DATA_ChunkID=0x61746164; //"data"
    wav->DATA_ChunkSize=0; //数据大小,还需要计算
    

    向文件写音频数据之前,需要先向音频文件写wav文件头结构进去。

    【4】读取咪头采集的音频数据

    在激活了 IMA ADPCM 录音之后,寄存器 SCI_HDAT0 和 SCI_HDAT1 有了新的功能。

    IMA ADPCM 的采样缓冲区是1024个16位字。缓冲区的填充状态可以通过 SCI_HDAT1 来读取。

    如果SCI_HDAT1大于0,则你可以从 SCI_HDAT0 读取许多个16位的字。

    如果数据没能足够快的读取,则缓冲区会溢出并返回空的状态。

    注意:如果 SCI_HDAT1≥896,它最好是等待缓冲区溢出和在读取采样数据之前清除。

    这样是为了让你避开缓冲区混叠。

    每个 IMA ADPCM 块是 128 字组,即 256 字节。

    如果你想暂停一下读取数据并在稍后恢复,请按照128 字的界限来中止。这样可以跳过完整的块并让编码流保持正确。

    WaveHeader wav;
    /*1. 创建音频文件*/
      <....>
    /*2. 初始化wav音频文件*/
    <....>
    /*3. 写入wav头*/
    <....>
    /*4. 读取音频数据写入到文件*/
    while(1)
    {
    	//判断是否有音频数据可以读取
    	cnt=VS1053B_ReadReg(VS1053_HDAT1);
    	if(cnt>=256&&cnt<=896)
    	{
    			for(i=0;i<256;i+=2)
    		  {
    				 //读取音频数据
    				 tmp=VS1053B_ReadReg(VS1053_HDAT0);
    				 read_buffer[i]=tmp&0xFF;
    				 read_buffer[i+1]=(tmp>>8)&0xFF;
    			}
    			block_cnt++; //记录数据块的次数
    			//写到文件
    			<....>
    	}
    	key=KEY_Scanf();
    	if(key)
    	{
    		wav.RIFF_ChunkSize=block_cnt*256+sizeof(WaveHeader)-8; //填充结构成员--整个文件的大小
    		wav.DATA_ChunkSize=block_cnt*256; //填充结构体成员数据大小
    		//偏移文件指针到文件头
    		<....>
    		//重新写wav文件头
    		<....>
    		//关闭文件
    		<....>
    		break; //跳出循环
    	}
    }
    

    【5】录音的流程(RecoderPlay)

    (1)VS1053连接的GPIO口初始化

    (2)激活PCM 录音模式

    (3)定义wav结构体,并初始化wav数据

    (4)创建存放录音的文件

    (5)将初始化后的wav数据写入文件

    (6)循环读取咪头采集的数据,并写入文件

    (7)如果录音结束,重新填充wav结构体的文件大小和数据大小,将文件指针移到文件头,重新将wav结构再写入文件。

    (8)关闭文件

    (9)结束录音

    【6】WAV文件频率设置说明

    wav 录音默认采样率为 8Khz,设置为 8Khz,就是正常语调,通过将采样率设置为不同的值可以得到不同的语调,语速设置范围为 4000hz~16000hz,根据设置的语速范围不同,可以实现变调的原理。

    注意: 这里的频率范围设置说的是wav文件结构里的频率设置,不是VS1053的采样频率(VS1053的采频率一般推荐填8KHZ和16KHZ)。

    比如: 将频率设置为 13000Hz,这个语调比较接近TOM 猫效果。
    将频率设置为 4000Hz,这个语调比较接近老人的声音。

    二、完整的代码

    2.1 vs1053.c

    #include "vs1053b.h"	
    
    /*
    函数功能:移植接口--SPI时序读写一个字节
    函数参数:data:要写入的数据
    返 回 值:读到的数据
    */
    u8 VS1053_SPI_ReadWriteByte(u8 tx_data)
    {			  	 
    	u8 rx_data=0;				 
      u8 i;
      for(i=0;i<8;i++)
    	{
    		VS1053_SCLK=0;  
    		if(tx_data&0x80){VS1053_OUTPUT=1;}
    		else {VS1053_OUTPUT=0;}
    		tx_data<<=1;	
    		VS1053_SCLK=1;
    		rx_data<<=1;
    		if(VS1053_INPUT)rx_data|=0x01;
    	}
    	return rx_data; 
    }
    
    
    /*
    函数功能:初始化VS1053的IO口	 
    */
    void VS1053_Init(void)
    {
    	 RCC->APB2ENR|=1<<0;
    	 AFIO->MAPR&=~(0x7<<24);  //释放PA13/14/15
    	 AFIO->MAPR|=0x4<<24;
    	
    	 RCC->APB2ENR|=1<<2;
    	 RCC->APB2ENR|=1<<3;
    	 
    	 GPIOA->CRH&=0x00000FFF;
    	 GPIOA->CRH|=0x33338000;
    	  
    	 GPIOB->CRL&=0xFFF00FFF;
    	 GPIOB->CRL|=0x00083000;
    	
    	 VS1053_SCLK=1;
    	 VS1053_XCS=1;
         VS1053_RESET=1;
    
    }	
    
    
    /*
    函数功能:软复位VS10XX
    */
    void VS1053_SoftReset(void)
    {	 
    	u8 retry=0;  				   
    	while(VS1053_DREQ==0); 							//等待软件复位结束	   
    	VS1053_SPI_ReadWriteByte(0Xff);			//启动传输
    	retry=0;
    	while(VS1053_ReadReg(SPI_MODE)!=0x0800)	// 软件复位,新模式  
    	{
    		VS1053_WriteCmd(SPI_MODE,0x0804);		// 软件复位,新模式	    
    		DelayMs(2);//等待至少1.35ms 
    		if(retry++>100)break; 	  
    	}	
    	while(VS1053_DREQ==0);//等待软件复位结束	 
    	retry=0;
    	while(VS1053_ReadReg(SPI_CLOCKF)!=0X9800)//设置VS10XX的时钟,3倍频 ,1.5xADD 
    	{
    		VS1053_WriteCmd(SPI_CLOCKF,0X9800);	//设置VS10XX的时钟,3倍频 ,1.5xADD
    		if(retry++>100)break; 	    
    	}	 
    	DelayMs(20);
    } 
    
    
    /*
    函数 功 能:硬复位MP3
    函数返回值:1:复位失败;0:复位成功	
    */
    u8 VS1053_Reset(void)
    {
    	u8 retry=0;
    	VS1053_RESET=0;
    	DelayMs(20);
    	VS1053_XDCS=1;//取消数据传输
    	VS1053_XCS=1; //取消数据传输
    	VS1053_RESET=1;	   
    	while(VS1053_DREQ==0&&retry<200)//等待DREQ为高
    	{
    		retry++;
    		DelayUs(50);
    	}
    	DelayMs(20);	
    	if(retry>=200)return 1;
    	else return 0;	    		 
    }
    
    
    /*
    函数功能:向VS10XX写命令
    函数参数:
    				address:命令地址
    				data   :命令数据
    */
    void VS1053_WriteCmd(u8 address,u16 data)
    {  
    	while(VS1053_DREQ==0);	//等待空闲		   	   
    	VS1053_XDCS=1; 	 
    	VS1053_XCS=0; 	 
    	VS1053_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令
    	VS1053_SPI_ReadWriteByte(address); 	//地址
    	VS1053_SPI_ReadWriteByte(data>>8); 	//发送高八位
    	VS1053_SPI_ReadWriteByte(data);	 		//第八位
    	VS1053_XCS=1;            
    } 
    
    
    /*
    函数参数:向VS1053写数据
    函数参数:data:要写入的数据
    */
    void VS1053_WriteData(u8 data)
    {
    	VS1053_XDCS=0;   
    	VS1053_SPI_ReadWriteByte(data);
    	VS1053_XDCS=1;      
    }
    
    
    /*
    函数功能:读VS1053的寄存器 
    函数参数:address:寄存器地址
    返回值:读到的值
    */
    u16 VS1053_ReadReg(u8 address)
    { 
    	u16 temp=0;   	
      while(VS1053_DREQ==0);//非等待空闲状态   	
    	VS1053_XDCS=1;       
    	VS1053_XCS=0;        
    	VS1053_SPI_ReadWriteByte(VS_READ_COMMAND);//发送VS10XX的读命令
    	VS1053_SPI_ReadWriteByte(address);       	//地址
    	temp=VS1053_SPI_ReadWriteByte(0xff); 		  //读取高字节
    	temp=temp<<8;
    	temp+=VS1053_SPI_ReadWriteByte(0xff); 		//读取低字节
    	VS1053_XCS=1;      
       return temp; 
    }  
    
    
    /*
    函数功能:读取VS1053的RAM
    函数参数:addr:RAM地址
    返 回 值:读到的值
    */
    u16 VS1053_ReadRAM(u16 addr) 
    { 
    	u16 res;			   	  
     	VS1053_WriteCmd(SPI_WRAMADDR, addr); 
    	res=VS1053_ReadReg(SPI_WRAM);  
     	return res;
    } 
    
    
    /*
    函数功能:写VS1053的RAM
    函数参数:
    				addr:RAM地址
    				val:要写入的值 
    */
    void VS1053_WriteRAM(u16 addr,u16 val) 
    {  		   	  
     	VS1053_WriteCmd(SPI_WRAMADDR,addr);	//写RAM地址 
    	while(VS1053_DREQ==0); 							//等待空闲	   
    	VS1053_WriteCmd(SPI_WRAM,val); 			//写RAM值 
    } 
    
    
    /*
    函数参数:发送一次音频数据,固定为32字节
    返 回 值:0,发送成功
    				  1,本次数据未成功发送   
    */ 
    u8 VS1053_SendMusicData(u8* buf)
    {
    	u8 n;
    	if(VS1053_DREQ!=0)  //送数据给VS10XX
    	{			   	 
    		VS1053_XDCS=0;  
        for(n=0;n<32;n++)
    		{
    			VS1053_SPI_ReadWriteByte(buf[n]);	 			
    		}
    		VS1053_XDCS=1;     				   
    	}else return 1;
    	return 0;//成功发送了
    }
    
    
    /*
    函数参数:发送一次音频数据,固定为32字节
    返 回 值:0,发送成功
    				  1,本次数据未成功发送   
    */ 
    void VS1053_SendMusicByte(u8 data)
    {
    	u8 n;
    	while(VS1053_DREQ==0){}	   	 
    	VS1053_XDCS=0;  
    	VS1053_SPI_ReadWriteByte(data);	 			
    	VS1053_XDCS=1;     				   
    }
    
    
    /*
    函数功能:设定VS1053播放的音量
    函数参数:volx:音量大小(0~254)
    */
    void VS1053_SetVol(u8 volx)
    {
        u16 volt=0; 			      //暂存音量值
        volt=254-volx;			    //取反一下,得到最大值,表示最大的表示 
    	  volt<<=8;
        volt+=254-volx;					//得到音量设置后大小
        VS1053_WriteCmd(SPI_VOL,volt);//设音量 
    }
    
    
    /*--------------------------------------录音功能-----------------------------------------------------*/
    
    
    /*
    函数功能:vs10xx装载patch
    函数参数:
    				patch:patch首地址
    				len  :patch长度
    */
    void VS1053_LoadPatch(u16 *patch,u16 len) 
    {
    	u16 i; 
    	u16 addr, n, val; 	  			   
    	for(i=0;i<len;) 
    	{ 
    		addr = patch[i++]; 
    		n    = patch[i++]; 
    		if(n & 0x8000U) //RLE run, replicate n samples 
    		{ 
    			n  &= 0x7FFF; 
    			val = patch[i++]; 
    			while(n--)VS1053_WriteCmd(addr, val);  
    		}
    		else //copy run, copy n sample 
    		{ 
    			while(n--) 
    			{ 
    				val = patch[i++]; 
    				VS1053_WriteCmd(addr, val); 
    			} 
    		} 
    	} 	
    }
    
    
    /*
    函数参数:初始化WAV头
    */
    void VS1053_RecoderWavInit(__WaveHeader* wavhead) //初始化WAV头			   
    {
    	wavhead->riff.ChunkID=0X46464952;	//"RIFF"
    	wavhead->riff.ChunkSize=0;				//还未确定,最后需要计算
    	wavhead->riff.Format=0X45564157; 	//"WAVE"
    	wavhead->fmt.ChunkID=0X20746D66; 	//"fmt "
    	wavhead->fmt.ChunkSize=16; 				//大小为16个字节
    	wavhead->fmt.AudioFormat=0X01; 		//0X01,表示PCM;0X01,表示IMA ADPCM
     	wavhead->fmt.NumOfChannels=1;			//单声道
     	wavhead->fmt.SampleRate=8000;			//8Khz采样率 采样速率
     	wavhead->fmt.ByteRate=wavhead->fmt.SampleRate*2;//16位,即2个字节
     	wavhead->fmt.BlockAlign=2;				//块大小,2个字节为一个块
     	wavhead->fmt.BitsPerSample=16;		//16位PCM
      wavhead->data.ChunkID=0X61746164;	//"data"
     	wavhead->data.ChunkSize=0;				//数据大小,还需要计算  
    }
    
    //VS1053的WAV录音有bug,这个plugin可以修正这个问题 							    
    const u16 VS1053_WavPlugin[40]=/* Compressed plugin */ 
    { 
    		0x0007, 0x0001, 0x8010, 0x0006, 0x001c, 0x3e12, 0xb817, 0x3e14, /* 0 */ 
    		0xf812, 0x3e01, 0xb811, 0x0007, 0x9717, 0x0020, 0xffd2, 0x0030, /* 8 */ 
    		0x11d1, 0x3111, 0x8024, 0x3704, 0xc024, 0x3b81, 0x8024, 0x3101, /* 10 */ 
    		0x8024, 0x3b81, 0x8024, 0x3f04, 0xc024, 0x2808, 0x4800, 0x36f1, /* 18 */ 
    		0x9811, 0x0007, 0x0001, 0x8028, 0x0006, 0x0002, 0x2a00, 0x040e,  
    }; 
    
    
    /*
    函数功能:激活PCM 录音模式
    函数参数:
    				agc:0,自动增益
            1024相当于1倍
            512相当于0.5倍
            最大值65535=64倍		  
    */
    void VS1053_RecoderInit(u16 agc)
    {
    	//如果是IMA ADPCM,采样率计算公式如下:
     	//采样率=CLKI/256*d;	
    	//假设d=0,并2倍频,外部晶振为12.288M.那么Fc=(2*12288000)/256*6=16Khz
    	//如果是线性PCM,采样率直接就写采样值 
      VS1053_WriteCmd(SPI_BASS,0x0000);    
     	VS1053_WriteCmd(SPI_AICTRL0,8000);	//设置采样率,设置为8Khz
     	VS1053_WriteCmd(SPI_AICTRL1,agc);		//设置增益,0,自动增益.1024相当于1倍,512相当于0.5倍,最大值65535=64倍	
     	VS1053_WriteCmd(SPI_AICTRL2,0);		  //设置增益最大值,0,代表最大值65536=64X
     	VS1053_WriteCmd(SPI_AICTRL3,6);		  //左通道(MIC单声道输入)
    	VS1053_WriteCmd(SPI_CLOCKF,0X2000);	//设置VS10XX的时钟,MULT:2倍频;ADD:不允许;CLK:12.288Mhz
    	VS1053_WriteCmd(SPI_MODE,0x1804);		//MIC,录音激活    
     	DelayMs(5);					//等待至少1.35ms 
     	VS1053_LoadPatch((u16*)VS1053_WavPlugin,40);//VS1053的WAV录音需要patch
    }
    

    2.2 vs1053.h

    #ifndef __VS10XX_H__
    #define __VS10XX_H__
    #include "sys.h"
    #include "delay.h"
    #include "usart.h"	  	    
    
    /*
    VS1053音频解码芯片外部的接口: 
    */
    #define VS1053_DREQ     PAin(11)  	//DREQ  数据请求
    #define VS1053_RESET    PAout(12)   //RST   硬件复位--低电平有效
    #define VS1053_XCS      PAout(13)  	//XCS   片选--低电平有效
    #define VS1053_XDCS     PAout(14)  	//XDCS  用于数据片选、字节同步
    #define VS1053_SCLK     PAout(15)
    #define VS1053_OUTPUT   PBout(3)
    #define VS1053_INPUT 	PBin(4)
    
    #define VS_WRITE_COMMAND 	0x02  //写命令
    #define VS_READ_COMMAND 	0x03  //读命令
    
    
    //VS10XX寄存器定义
    #define SPI_MODE        	0x00   
    #define SPI_STATUS      	0x01   
    #define SPI_BASS        	0x02   
    #define SPI_CLOCKF      	0x03   
    #define SPI_DECODE_TIME 	0x04   
    #define SPI_AUDATA      	0x05   
    #define SPI_WRAM        	0x06   
    #define SPI_WRAMADDR    	0x07   
    #define SPI_HDAT0       	0x08   
    #define SPI_HDAT1       	0x09 
      
    #define SPI_AIADDR      	0x0a   
    #define SPI_VOL         	0x0b   
    #define SPI_AICTRL0     	0x0c   
    #define SPI_AICTRL1     	0x0d   
    #define SPI_AICTRL2     	0x0e   
    #define SPI_AICTRL3     	0x0f   
    #define SM_DIFF         	0x01   
    #define SM_JUMP         	0x02   
    #define SM_RESET        	0x04   
    #define SM_OUTOFWAV     	0x08   
    #define SM_PDOWN        	0x10   
    #define SM_TESTS        	0x20   
    #define SM_STREAM       	0x40   
    #define SM_PLUSV        	0x80   
    #define SM_DACT         	0x100   
    #define SM_SDIORD       	0x200   
    #define SM_SDISHARE     	0x400   
    #define SM_SDINEW       	0x800   
    #define SM_ADPCM        	0x1000   
    #define SM_ADPCM_HP     	0x2000 		 
    
    #define I2S_CONFIG			 0XC040
    #define GPIO_DDR			   0XC017
    #define GPIO_IDATA			 0XC018
    #define GPIO_ODATA			 0XC019
    
    
    void VS1053_Init(void);
    u16  VS1053_ReadReg(u8 address);						//读寄存器
    u16  VS1053_ReadRAM(u16 addr);	    	  		//读RAM
    void VS1053_WriteRAM(u16 addr,u16 val);			//写RAM
    void VS1053_WriteData(u8 data);				  		//写数据
    void VS1053_WriteCmd(u8 address,u16 data);	//写命令
    u8   VS1053_Reset(void);			    					//硬复位
    void VS1053_SoftReset(void);           			//软复位
    u8 	 VS1053_SPI_ReadWriteByte(u8 data);     //SPI接口,读写一个字节 
    void VS1053_SoftReset(void);								//初始化VS1053	 
    u8 	 VS1053_SendMusicData(u8* buf);					//向VS10XX发送32字节 
    void VS1053_SetVol(u8 volx);				  			//设置主音量 
    void VS1053_SendMusicByte(u8 data);
    
    
    
    
    /*-----------------------------------以下是录音功能相关的结构体---------------------------------------*/
     //RIFF块
    typedef __packed struct
    {
        u32 ChunkID;		   	//chunk id;这里固定为"RIFF",即0X46464952
        u32 ChunkSize ;		   	//集合大小;文件总大小-8
        u32 Format;	   			//格式;WAVE,即0X45564157
    }ChunkRIFF ;
    
    
    //fmt块
    typedef __packed struct
    {
        u32 ChunkID;		   	//chunk id;这里固定为"fmt ",即0X20746D66
        u32 ChunkSize ;		   	//子集合大小(不包括ID和Size);这里为:20.
        u16 AudioFormat;	  	//音频格式;0X10,表示线性PCM;0X11表示IMA ADPCM
    	u16 NumOfChannels;		//通道数量;1,表示单声道;2,表示双声道;
    	u32 SampleRate;			//采样率;0X1F40,表示8Khz
    	u32 ByteRate;			//字节速率; 
    	u16 BlockAlign;			//块对齐(字节); 
    	u16 BitsPerSample;		//单个采样数据大小;4位ADPCM,设置为4
    //	u16 ByteExtraData;		//附加的数据字节;2个; 线性PCM,没有这个参数
    //	u16 ExtraData;			//附加的数据,单个采样数据块大小;0X1F9:505字节  线性PCM,没有这个参数
    }ChunkFMT;	
    
    
    //fact块 
    typedef __packed struct 
    {
        u32 ChunkID;		   	//chunk id;这里固定为"fact",即0X74636166;
        u32 ChunkSize ;		   	//子集合大小(不包括ID和Size);这里为:4.
        u32 NumOfSamples;	  	//采样的数量; 
    }ChunkFACT;
    
    
    //data块 
    typedef __packed struct 
    {
        u32 ChunkID;		   	//chunk id;这里固定为"data",即0X61746164
        u32 ChunkSize ;		   	//子集合大小(不包括ID和Size);文件大小-60.
    }ChunkDATA;
    
    //wav头
    typedef __packed struct
    { 
    	ChunkRIFF riff;	//riff块
    	ChunkFMT fmt;  	//fmt块
    	//ChunkFACT fact;	//fact块 线性PCM,没有这个结构体	 
    	ChunkDATA data;	//data块		 
    }__WaveHeader; 
    void VS1053_RecoderInit(u16 agc);
    void VS1053_RecoderWavInit(__WaveHeader* wavhead);
    void VS1053_LoadPatch(u16 *patch,u16 len); 
    #endif
    

    作者:DS小龙哥

    物联沃分享整理
    物联沃-IOTWORD物联网 » 从0开始编写VS1053音频解码芯片的底层驱动代码(适用于任何单片机)

    发表回复