(部分代码参考自正点原子和网络)
VS1053B简介
VS1053 是单片Ogg Vorbis/MP3/AAC/WMA/MIDI 音频解码器,及IMA ADPCM编码器和用户加载的Ogg Vorbis编码器。它包含了一个高性能、有专利的低功耗 DSP 处理器内核VS_DSP、工作数据存储器、供用户应用程序和任何固化解码器一起运行的16 KiB指令RAM及0.5 KiB多的数据 RAM、串行的控制和输入数据接口、最多8个可用的通用I/O引脚、一个UART、并有一个优质的可变采样率立体声ADC(“咪”、“线路”、“线路+咪”或“线路*2”)和立体声DAC、 和跟随的一个耳机功放及一个公共电压缓冲器。
VS1053通信方式
VS1053通过SPI接口来接受输入的音频数据流
我们通过SPI口向VS1053不停的输入音频数据,它就会自动帮我解码了,然后从输出通道输出音乐,这时我们接上耳机就能听到所播放的歌曲了
VS1053通过7根信号线同主控芯片连接
RST是VS1053的复位信号线,低电平有效。
DREQ是一个数据请求信号,用来反馈VS1053的2048字节FIFO是否可以接收数据。
SCK、SI(MOSI)和SO(MISO)则是VS1053的SPI接口,他们
在XCS和XDCS的控制下面来执行不同的数据通信。
VS1053 的 SPI 数据传送,分为 SDI 和 SCI,SDI 用来传输数据,SCI 用于传输命令
SDI: VS1053 的数据传输都是通过 DREQ 控制的 ,主机必须在判断 DREQ 有效(高电平有效)后,才可以发送数据,推荐每次发送控制在32字节,因为DREQ可能在任何时间转变状态,甚至在一个字节传送的期间。
SCI: SCI 串行总线命令接口包含了一个指令字节、一个地址字节和一个16 位的数据字,SCI 的字节数据总是高位在前低位在后的。
指令字节包括读指令(0x03)和写指令(0x02)。
补充DREQ脚:如果它为高电平,则 VS1053可以接收最少32字节的SDI数据或者接收一条SCI命令。当流缓冲区太满和SCI命令正在执行的期间,DREQ会转换到低电平,此时应该停止向VS1003发送数据和新命令。
SCI读操作时序
首先,XCS 信号线被拉到低电平来片选此设备。随后,发送读指令(0x03)以及8位宽度的地址。在地址被读取之后,SI信号线上发送的任何数据都将被芯片忽略。而被确认的地址中的十六位宽度数据将在SO信号线上移动输出。
SCI写操作时序
首先,XCS 信号线被拉到低电平来片选此设备。随后,发送写指令(0x02)以及8位宽度的地址,最后十六宽度数据在SI信号线上移动写入。
注意:在最后一个位元发送结束后,在这个寄存器被更新的整个期间,DREQ会被拉到低电平并维持此状态, 即图中标识为“execution”的部分。这个时间长短依赖于各寄存器和它所包含的内容。如果该时间长度最大值远超过微控制器可以提供下一条SCI命令或SDI字节所需的时间, 则在下一次SCI / SDI的操作开始之前,必须先检查DREQ的状态。
VS1053 SCI寄存器组
总共有16个寄存器
下面介绍一些寄存器:
1. MODE
用来控制VS1053的运作,它的缺省值是0x0800(SM_SDINEW被设定)。
当SM_DIFF被设置,将会使左通道的输出反相播放。对于立体声输入这会创建一个虚拟环绕效果, 而单声道输入将会创建一个差分的左/右信号。
2. STATUS
包含了 VS1053 当前状态的信息。
3. BASS
这个低音加强VSBE是一个强劲的低音增强DSP算法,它将试着让用户的耳机获得尽量多的输出又不 会产生削波失真。
4. CLOCKF
这个寄存器用来设置时钟频率、倍频等相关
重点说明 SC_FREQ,SC_FREQ 是以 4KHz 为步进的一个时钟寄存器,当外部时钟不是 12.288M的时候,其计算公式为:
SC_FREQ=(XTALI-8000000)/4000
XTALI 的单位为 Hz,CLKI 是内部时钟频率,XTALI 是外部晶振的时钟频率,一般vs1053使用的是 12.288M 的晶振,在这里设置此寄存器的值为 0X9800,也就是设置内部时钟频率为输入时钟频率的 3 倍,倍频增量为 1.0 倍。
5. DECODE_TIME
在解码正确的数据时,当前解码所用的时间长度被以秒为单位储存在此寄存器内。用户可以修改此寄存期的值。在这种情况下新数值应该要写两次,以保证它不会被固件覆盖掉。一次写SCI_DECODE_TIME 的动作会重置byteRate的计算。SCI_DECODE_TIME 在每次硬件和软件复位后被重置。当一个文件解码结束时它将不再被清除,这样允许在自动循环和多个文件播放时让编码时间继续记录。
6. SCI_WRAM
用来上载应用程序和数据到指令和数据的RAM中。起始地址必须在首次读/写SCI_WRAM 操作之前事先写入SCI_WRAMADDR来初始化。在一次SCI_WRAM的读/写操作中可以传送一个16位的数 据和 32 位长度的指令字,而每个指令字必须用两次连续的读 / 写操作来完成。字节的顺序采用大端模 式(big‐endian:即高字节在前)。在每个完整的字读 / 写操作之后,内部的(地址)指针会自动增加。
7. SCI_WRAMADDR
用来设置随后的SCI_WRAM读/写操作的程序地址。地址偏移量的0用于X,0x4000 用于Y,而0x8000则是指令存储器。并且外设寄存器也可以访问。
8. HDAT0 和 HDAT1
- 对于WAV文件HDAT1包含数值0x7665( “ve” ),HDAT0包含着所有支持的RIFF WAVE 格式所测量出的字节/秒的数据速率:单声道或双声道8位或16位PCM、单声道或双声道IMA ADPCM。 要获得此文件的比特率,用这个值乘以8。
- 对于AAC ADTS流HDAT1包含数值0x4154( “AT” ),对于AAC ADIF文件HDAT1包含数值0x4144( “AD” )。对于AAC.mp4 / .m4a文件HDAT1包含数值0x4D34( “M4” ), HDAT0包含着字节/秒的平均数据速率。要获得此文件的比特率,用这个值乘以8。
- 对于WMA文件HDAT1包含数值0x574D( “WM” ),HDAT0包含着所测量的字节/ 秒的数据速率。要获得此文件的比特率,用这个值乘以8。
- 对于MIDI文件HDAT1包含数值0x4D54( “MT” ),HDAT0包含着字节/秒的平均数 据速率。要获得此文件的比特率,用这个值乘以8。
- 对于Ogg Vorbis文件HDAT1包含数值0x4F67( “Og” ),HDAT0包含着字节/秒的平 均数据速率。要获得此文件的比特率,用这个值乘以8。
- 对于MP3文件HDAT1会是在0xFFE0和0xFFFF之间。HDAT1 / 0包含了下列信息:
9. SCI_VOL
用于播放器的硬件音量控制。这个音量寄存器的高字节是控制左通道音量的,低字节是控制右通道音量的。对每个通道,数值在0..254的范围内设置可以实现在最大音量级别内的微调(步长0.5 dB)。左通道数值是通过乘上256后再加到这个数值上的。所以,最大的音量是0、无声是0xFEFE。设置SCI_VOL 为0xFFFF 将会使模拟单元进入掉电模式。
VS1053 操作
硬件复位
当 XRESET 信号被驱动到低电平,VS1053b 将重置所有的控制寄存器和内部状态到它们的初始值。XRESET 信号是和任何的外部时钟异步运作的。
在XRESET 有效时,所有的输出引脚会转变到它们的缺省状态。所有的输入引脚转变到高阻状态(到输入状态)。除了SO外,其它的仍然由XCS来控制。
在硬件复位(或上电)之后,DREQ将在低电平上停留最少22000个时钟周期,对于运行在12.288 MHz 的 VS1053 上来说,大约是 1.8 毫秒。随后用户应该在开始解码之前事先设置 SCI_MODE、SCI_BASS、SCI_CLOCKF 和 SCI_VOL 这几个基本软件寄存器。如果输入时钟是 24…26MHz,应该在芯片复位之后尽快设置 SM_CLK_RANGE,这个操作并不需要等待DREQ信号。
软件复位
有些情况下,解码器需要用软件来复位。这是通过激活SCI_MODE寄存器中的SM_RESET位来实现的 ,然后最少需要等待2微秒后再去查询DREQ状态。DREQ将在低电平上停留最少22000 个时钟周期,对于运行在12.288 MHz的VS1053上来说,大约是1.8毫秒。然后等DREQ上升变为高, 就可以像平常那样继续播放了。
播放和解码
在VS1053 正常的操作模式下,SDI 数据被解码。解码的采样数据由内置的DAC转换成模拟量。如 果没有解码数据被找到,SCI_HDAT0和SCI_HDAT1会被设置为0。(如果想写一个自动播放下一首音乐的逻辑就可以判断SCI_HDAT0和SCI_HDAT1是否为0来确定当前数据是否被解码完)
VS1053 驱动代码
软件模拟SPI读写
在代码中补全宏定义的IO口读写即可,视单片机运行频率可以适当在代码中添加延时函数以满足SPI的时序要求。
#define SPI_SCLK(x) //SPI_SCK引脚的写功能 0为低电平,1为高电平
#define SPI_MOSI(x) //SPI_MOSI引脚的写功能 0为低电平,1为高电平
#define SPI_MISO //SPI_MISO引脚的读功能 0为低电平,1为高电平
unsigned char SPI_transfer(unsigned char dat)
{
unsigned char i;
unsigned char value = 0;
for (i = 0; i < 8; i++)
{
SPI_SCLK(0);
// Delay;
SPI_MOSI(((dat & 0x80) ? 1 : 0));
// Delay;
SPI_SCLK(1);
dat <<= 1;
value <<= 1;
if (SPI_MISO)
value |= 0x01;
}
return value;
}
VS1053 控制的一些宏定义
#define VS1053_RST(x) //VS1053_RST引脚的写功能 0为低电平,1为高电平
#define VS1053_XCS(x) //VS1053_XCS引脚的写功能 0为低电平,1为高电平
#define VS1053_XDCS(x) //VS1053_XDCS引脚的写功能 0为低电平,1为高电平
#define VS1053_DQ //VS1053_DREQ引脚的读功能 0为低电平,1为高电平
#define VS_WRITE_COMMAND 0x02 /* VS1053写指令 */
#define VS_READ_COMMAND 0x03 /* VS1053读指令 */
#define SPI_MODE 0x0 /* 模式控制 */
#define SPI_STATUS 0x1 /* 状态 */
#define SPI_BASS 0x2 /* 低音/高音控制器 */
#define SPI_CLOCKF 0x3 /* 时钟频率加乘数 */
#define SPI_DECODE_TIME 0x4 /* 解码时间长度 */
#define SPI_AUDATA 0x5 /* 各种音频数据 */
#define SPI_WRAM 0x6 /* RAM读写 */
#define SPI_WRAMADDR 0x7 /* RAM读写的基址 */
#define SPI_HDAT0 0x8 /* 流的数据标头0 */
#define SPI_HDAT1 0x9 /* 流的数据标头1 */
#define SPI_AIADDR 0xa /* 应用程序的起始地址 */
#define SPI_VOL 0xb /* 音量控制 */
#define SPI_AICTRL0 0xc /* 应用程序控制寄存器0 */
#define SPI_AICTRL1 0xd /* 应用程序控制寄存器1 */
#define SPI_AICTRL2 0xe /* 应用程序控制寄存器2 */
#define SPI_AICTRL3 0xf /* 应用程序控制寄存器3 */
#define SM_DIFF 0x01 /*差分 0:正常的相同音频 1:左通道反相 */
#define SM_JUMP 0x02 /*允许MPEG layers I&II 0:不允许 1:允许 */
#define SM_RESET 0x04 /*软件复位 0:不用复位 1:复位 */
#define SM_OUTOFWAV 0x08 /* 取消当前的文件解码 0:不取消 1:取消 */
#define SM_PDOWN 0x10 /* EarSpeaker低设定 0:关闭 1:激活 */
#define SM_TESTS 0x20 /* 允许SDI测试 0:不允许 1:允许 */
#define SM_STREAM 0x40 /*流模式 0:不是 1:是 */
#define SM_PLUSV 0x80 /**< VS10xx register */
#define SM_DACT 0x100 /* DCLK的有效边缘 0:上升沿 1:下降沿 */
#define SM_SDIORD 0x200 /** SDI位顺序 0:MSb在前 1:MSb在后 */
#define SM_SDISHARE 0x400 /* 共享SPI片选 0:不共享 1:共享 */
#define SM_SDINEW 0x800 /*VS1002本地SPI模式 0:非本地模式 1:本地模式 */
#define SM_ADPCM 0x1000 /*ADPCM录音激活 0:不激活 1:激活 */
#define SM_ADPCM_HP 0x2000 /**< VS10xx register */
#define SM_LINE1 0x4000 /* 咪/线路1选择 0:MICP 1:LINE1 */
#define SM_CLK_RANGE 0x8000 /* 输入时钟范围 */
#define I2S_CONFIG 0XC040
#define GPIO_DDR 0XC017
#define GPIO_IDATA 0XC018
#define GPIO_ODATA 0XC019
VS1053 初始化
主要是用于引脚的初始化,如果没有可以不要这个函数
void vs1053_init(void)
{
// 可以在这里写主机IO引脚初始化的功能
vs1053_reset();
}
VS1053 写寄存器
void vs1053_writeRegister(unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte)
{
while (!VS1053_DQ); // 等待DREQ为高
VS1053_XDCS(1);
VS1053_XCS(0);
SPI_transfer(VS_WRITE_COMMAND);
SPI_transfer((addressbyte));
SPI_transfer(highbyte);
SPI_transfer(lowbyte);
VS1053_XCS(1);
}
VS1053 读寄存器
unsigned int vs1053_readRegister(unsigned char addressbyte)
{
unsigned int result = 0;
unsigned char highbyte = 0;
unsigned char lowbyte = 0;
VS1053_XDCS(1);
VS1053_XCS(0);
SPI_transfer(VS_READ_COMMAND);
SPI_transfer(addressbyte);
highbyte = SPI_transfer(0xff);
lowbyte = SPI_transfer(0xff);
result = (((unsigned int)highbyte << 8) | lowbyte);
VS1053_XCS(1);
return result;
}
VS1053 读RAM
unsigned int vs1053_readRAM(unsigned int address)
{
unsigned int res;
vs1053_writeRegister(SPI_WRAMADDR, address >> 8, address & 0xff);
res = vs1053_readRegister(SPI_WRAM);
return res;
}
VS1053 写RAM
void vs1053_writeRAM(unsigned int address, unsigned int data)
{
vs1053_writeRegister(SPI_WRAMADDR, address >> 8, address & 0xff);
while (!VS1053_DQ);
vs1053_writeRegister(SPI_WRAM, data >> 8, data & 0xff);
while (!VS1053_DQ);
}
VS1053 硬件复位
int vs1053_reset(void)
{
unsigned char retry = 0;
VS1053_RST(0);
delay_ms(20); // 注意延时
VS1053_XCS(1);
VS1053_XDCS(1);
VS1053_RST(1);
while (!VS1053_DQ && retry < 200)
{
retry++;
delay_us(50); // 注意延时
}
delay_ms(20);
if (retry >= 200)
{
return 1; // 复位失败
}
else
{
return 0; // 复位成功
}
}
VS1053 软件复位
void vs1053_softReset(void)
{
unsigned char retry = 0;
while (!VS1053_DQ);
SPI_transfer(0Xff);
retry = 0;
while (vs1053_readRegister(SPI_MODE) != 0x0800)
{
vs1053_writeRegister(SPI_MODE, 0x08, 0x04);
delay_ms(2);
if (retry++ > 100)
{
break;
}
}
while (!VS1053_DQ);
retry = 0;
while (vs1053_readRegister(SPI_CLOCKF) != 0X9800)
{
vs1053_writeRegister(SPI_CLOCKF, 0X98, 0x00);
if (retry++ > 100)
{
break;
}
}
delay_ms(20);
}
VS1053 获取解码时间
unsigned short vs1053_getDecodeTime(void)
{
unsigned int dt = 0;
dt = vs1053_readRegister(SPI_DECODE_TIME);
return dt;
}
VS1053 设置音量
void vs1053_setVolume(unsigned char leftchannel, unsigned char rightchannel)
{
vs1053_writeRegister(SPI_VOL, leftchannel, rightchannel);
}
VS1053设置高低音
(参考自正点原子代码)
/**
* @brief 设置高低音(音调)
* @param bfreq : 低频上限频率 2~15(单位:10Hz)
* @param bass : 低频增益 0~15(单位:1dB)
* @param tfreq : 高频下限频率 1~15(单位:1KHz)
* @param treble : 高频增益 0~15(单位:1.5dB,小于9的时候为负数)
* @retval 无
*/
void vs10xx_setTone(unsigned char bfreq, unsigned char bass, unsigned char tfreq, unsigned char treble)
{
unsigned int bass_set = 0; /* 暂存音调寄存器值 */
signed char temp = (signed char)treble - 8;
bass_set = temp & 0X0F; /* 高音设定 */
bass_set <<= 4;
bass_set += tfreq & 0xf; /* 高音下限频率 */
bass_set <<= 4;
bass_set += bass & 0xf; /* 低音设定 */
bass_set <<= 4;
bass_set += bfreq & 0xf; /* 低音上限 */
vs1053_writeRegister(SPI_BASS, bass_set >> 8, bass_set & 0xff); /* BASS */
}
VS1053 设置音效
(参考自正点原子代码)
/**
* @brief 设定音效
* @param effect : 0, 关闭; 1, 最小; 2, 中等; 3, 最大;
* @retval 无
*/
void vs10xx_setEffect(unsigned char effect)
{
unsigned int temp;
temp = vs1053_readRegister(SPI_MODE); /* 读取SPI_MODE的内容 */
temp &= ~(1 << 4); /* 取消 LO */
temp &= ~(1 << 7); /* 取消 HI */
if (effect & 0X01)
{
temp |= 1 << 4; /* 设定LO */
}
if (effect & 0X02)
{
temp |= 1 << 7; /* 设定 HI */
}
vs1053_writeRegister(SPI_MODE, temp >> 8, temp & 0xff); /* 设定模式 */
}
VS1053 获取码率
(参考自正点原子代码)
/* 各种音频头标志
* WAV HEAD0 : 0X7761 HEAD1 : 0X7665
* MIDI HEAD0 : other info HEAD1 : 0X4D54
* WMA HEAD0 : data speed HEAD1 : 0X574D
* MP3 HEAD0 : data speed HEAD1 : ID
* 比特率预定值,阶层III
*/
const uint16_t g_bitrate[2][16] =
{
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0},
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}};
/**
* @brief 返回当前音频的码率
* @param 无
* @retval 当前音频码率(Kbps)
*/
uint16_t vs1053_getBitrate(void)
{
uint32_t head0;
uint16_t head1;
head0 = vs1053_readRegister(SPI_HDAT0);
head1 = vs1053_readRegister(SPI_HDAT1);
switch (head1)
{
case 0X4D54: /* MIDI格式 */
case 0X4154: /* AAC_ADTS */
case 0X4144: /* AAC_ADIF */
case 0X4D34: /* AAC_MP4/M4A */
case 0X4F67: /* OGG */
case 0X574D: /* WMA格式 */
return head0 / 125; /* 相当于 * 8 / 1000, 得到Kbps单位的音频码率 */
case 0x7665: /* WAV格式 */
if (head0 == 0XFFFF) /* head0 = 0XFFFF, 说明 码率超出了uint16的表达范围 */
{
return 1411; /* 对于超出量程范围的WAV文件, 直接默认1411码率 */
}
case 0X664C: /* FLAC格式 */
/* 由于 VLSI没有明确给出FLAC的码率计算公式, 这里使用经验计算公式
* (head0 * 32) / 1000, 该公式是根据head0实际的值以及音频实际采样率
* 推导出来的, 如果VLSI有明确的计算方法了, 后续再修正这个公式
*/
return (head0 * 32) / 1000; /* 得到Kbps单位的音频码率 */
default: /* MP3格式, 仅做了阶层III的表 */
{
head1 >>= 3;
head1 = head1 & 0x03;
if (head1 == 3)
{
head1 = 1;
}
else
{
head1 = 0;
}
return g_bitrate[head1][head0 >> 12];
}
}
}
VS1053 发送音频数据
(参考自正点原子代码)
/**
* @brief 发送一次音频数据
* @note 固定为32字节长度
* @param buf : 音频数据缓冲区首地址
* @retval 发送结果
* @arg 0, 发送成功
* @arg 1, VS10XX还不需要数据, 本次发送未完成
*
*/
unsigned char vs1053_sendMusicData(unsigned char *buf)
{
unsigned char n;
if (VS1053_DQ != 0) /* 是否需要发送数据给 VS10XX ? */
{
VS1053_XDCS(0);
for (n = 0; n < 32; n++) /* 发送32字节音频数据 */
{
SPI_transfer(buf[n]);
}
VS1053_XDCS(1);
}
else
{
return 1;
}
return 0; /* 成功发送了 */
}
VS1053 切歌(以防出现噪声)
(参考自正点原子代码)
/**
* @brief 得到需要填充的数字
* @note 在歌曲切换的时候使用
* @param 无
* @retval 需要填充的数字
*/
static unsigned int vs1053_getFillBate(void)
{
return vs1053_readRAM(0X1E06); /* 填充字节 */
}
/**
* @brief 切歌
* @note 通过此函数切歌, 不会出现切换“噪声”
* @param 无
* @retval 无
*/
void vs1053_restartPlay(void)
{
unsigned int temp;
unsigned int i;
unsigned char n;
unsigned char vsbuf[32];
for (n = 0; n < 32; n++)
{
vsbuf[n] = 0; /* 清零 */
}
temp = vs1053_readRegister(SPI_MODE); /* 读取SPI_MODE的内容 */
temp |= 1 << 3; /* 设置SM_CANCEL位 */
temp |= 1 << 2; /* 设置SM_LAYER12位,允许播放MP1,MP2 */
vs1053_writeRegister(SPI_MODE, temp >> 8, temp & 0xff); /* 设置取消当前解码指令 */
for (i = 0; i < 2048;) /* 发送2048个0,期间读取SM_CANCEL位.如果为0,则表示已经取消了当前解码 */
{
if (vs1053_sendMusicData(vsbuf) == 0) /* 每发送32个字节后检测一次 */
{
i += 32; /* 发送了32个字节 */
temp = vs1053_readRegister(SPI_MODE); /* 读取SPI_MODE的内容 */
if ((temp & (1 << 3)) == 0)
{
break; /* 成功取消了 */
}
}
}
if (i < 2048) /* SM_CANCEL正常 */
{
temp = vs1053_getFillBate() & 0xff; /* 读取填充字节 */
for (n = 0; n < 32; n++)
{
vsbuf[n] = temp; /* 填充字节放入数组 */
}
for (i = 0; i < 2052;)
{
if (vs1053_sendMusicData(vsbuf) == 0)
{
i += 32; /* 填充 */
}
}
}
else
{
vs1053_softReset(); /* SM_CANCEL不成功,坏情况,需要软复位 */
}
temp = vs1053_readRegister(SPI_HDAT0);
temp += vs1053_readRegister(SPI_HDAT1);
if (temp) /* 软复位,还是没有成功取消,放杀手锏,硬复位 */
{
vs1053_reset(); /* 硬复位 */
vs1053_softReset(); /* 软复位 */
}
}
VS1053 重设解码时间
void vs1053_resetDecodeTime(void)
{
vs1053_writeRegister(SPI_DECODE_TIME, 0x00, 0x00);
vs1053_writeRegister(SPI_DECODE_TIME, 0x00, 0x00); /* 操作两次 */
}
有上面的代码就以及可以播放一首音乐了,只需要将存放音频数据缓冲区的首地址传给vs1053_sendMusicData,它会自动读取传入地址起后面的32个字节数据,当向VS1053写入成功后就将缓冲区地址偏移32个字节继续发送,直至缓冲区内容发送完毕。
比如下面示例代码:
if (vs1053_sendMusicData(musicbuf + i) == 0) /* 给VS1053发送音频数据 */
{
i += 32;
}
else
{
//发送音频数据之余处理其他逻辑
}
拓展:MIDI合成器
VS1053支持以下midi乐器
在 VS10XX Applications 中可以找到通过SPI启动实时MIDI的补丁代码,如下所示:
/* User application code loading tables for VS10xx */
#if 0
void LoadUserCode(void) {
int i = 0;
while (i<sizeof(plugin)/sizeof(plugin[0])) {
unsigned short addr, n, val;
addr = plugin[i++];
n = plugin[i++];
if (n & 0x8000U) { /* RLE run, replicate n samples */
n &= 0x7FFF;
val = plugin[i++];
while (n--) {
WriteVS10xxRegister(addr, val);
}
} else { /* Copy run, copy n samples */
while (n--) {
val = plugin[i++];
WriteVS10xxRegister(addr, val);
}
}
}
}
#endif
#ifndef SKIP_PLUGIN_VARNAME
#define PLUGIN_SIZE 28
const unsigned short plugin[28] = { /* Compressed plugin */
#endif
0x0007, 0x0001, 0x8050, 0x0006, 0x0014, 0x0030, 0x0715, 0xb080, /* 0 */
0x3400, 0x0007, 0x9255, 0x3d00, 0x0024, 0x0030, 0x0295, 0x6890, /* 8 */
0x3400, 0x0030, 0x0495, 0x3d00, 0x0024, 0x2908, 0x4d40, 0x0030, /* 10 */
0x0200, 0x000a, 0x0001, 0x0050,
#ifndef SKIP_PLUGIN_VARNAME
};
#endif
改成下面这样后也能用:
const unsigned short gVS1053_MIDI_Patch[28] = {
0x0007, 0x0001, 0x8050, 0x0006, 0x0014, 0x0030, 0x0715, 0xb080, /* 0 */
0x3400, 0x0007, 0x9255, 0x3d00, 0x0024, 0x0030, 0x0295, 0x6890, /* 8 */
0x3400, 0x0030, 0x0495, 0x3d00, 0x0024, 0x2908, 0x4d40, 0x0030, /* 10 */
0x0200, 0x000a, 0x0001, 0x0050};
void vs1053_loadMidiPlugin(void)
{
int i = 0;
while (i < sizeof(gVS1053_MIDI_Patch) / sizeof(gVS1053_MIDI_Patch[0]))
{
unsigned short addr, n, val;
addr = gVS1053_MIDI_Patch[i++];
n = gVS1053_MIDI_Patch[i++];
while (n--)
{
val = gVS1053_MIDI_Patch[i++];
vs1053_writeRegister(addr, val >> 8, val & 0xff);
}
}
}
在代码中执行vs1053_loadMidiPlugin就可以开启实时MIDI。
再封装一个MIDI初始化
VS1053 MIDI合成器初始化
void vs1053_initForMidiFmt(void)
{
vs1053_softReset();
vs1053_loadMidiPlugin();
vs10xx_setTone(15, 15, 15, 15);
}
VS1053 发送MIDI信号
void vs1053_midiSendByte(unsigned char data)
{
SPI_transfer(0x00);
SPI_transfer(data);
}
void vs1053_midiWriteData(unsigned char cmd, unsigned char high, unsigned char low)
{
while (!VS1053_DQ)
;
VS1053_XDCS(0);
vs1053_midiSendByte(cmd);
if ((cmd & 0xF0) <= 0xB0 || (cmd & 0xF0) >= 0xE0)
{
vs1053_midiSendByte(high);
vs1053_midiSendByte(low);
}
else
{
vs1053_midiSendByte(high);
}
VS1053_XDCS(1);
}
void vs1053_midiNoteOn(unsigned char channel, unsigned char note, unsigned char rate)
{
vs1053_midiWriteData((0x90 | channel), note, rate);
}
void vs1053_midiNoteOff(unsigned char channel, unsigned char note, unsigned char rate)
{
vs1053_midiWriteData((0x80 | channel), note, rate);
}
void vs1053_programChange(unsigned char channel, unsigned char instrument)
{
vs1053_midiWriteData((0xC0 | channel), instrument, 0);
}
以钢琴为例:
- vs1053_midiNoteOn:可以想象为按下钢琴键,channel为midi通道(0~15),note为midi中的音高编号(0~127),rate为速度(0~127,或者理解为音量也行,因为值越小发出的声音也越小)
- vs1053_midiNoteOff:可以想象为弹起钢琴键,channel为midi通道(0~15),note为midi中的音高编号(0~127),rate为速度
注:vs1053_midiNoteOn 的rate为 0 时等同于 vs1053_midiNoteOff。
- vs1053_programChange:切换对应通道(channel)的乐器,乐器在最后的代码头文件有定义。
详细MIDI的内容可以参考下面:
最后附上完整代码:
百度网盘提取码:1053