或许是一个通用的VS1053的驱动

(部分代码参考自正点原子和网络)

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是否可以接收数据。 
SCKSI(MOSI)SO(MISO)则是VS1053的SPI接口,他们
XCSXDCS的控制下面来执行不同的数据通信。

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的内容可以参考下面:

MIDI协议

MIDI code手册

MIDI规范手册

最后附上完整代码:

蓝奏云 VS1053

百度网盘 VS1053

百度网盘提取码:1053

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值