-
播放MP3
这里的MP3的存储介质是SD卡。现在要做的事情是将MP3文件从SD卡中读出来,送到8962的缓冲区,缓冲区的大小设置为512Byte,一般一次读
个扇区,然后将数据发往VS1003。由于VS1003有32字节的数据缓冲区,一次可以发32个字节的数据,然后检测DREQ,当DREQ
为高时送下一个32字节的数据,直到发完为止。DREQ为高表明VS1003可以接受新的数据,如果不考虑DREQ直接给VS1003发数据的话,会出现音乐断断续续的情况。
这里,先把音乐数据包含在程序里,存在data.h文件里。具体程序如下:
#include
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/sysctl.h"
#include "driverlib/systick.h"
#include "utils/cmdline.h"
#include "utils/uartstdio.h"
#include "fatfs/src/ff.h"
#include "fatfs/src/diskio.h"
#include "data.h"
#include "vs1003.h"
int
main(void)
{
unsigned long ulindex;
unsigned short i;
SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
SYSCTL_XTAL_8MHZ);
vs1003_io_init();
vs1003_init();
VsSineTest();
//vs1003_init();
//UARTprintf("SysCtlClock:%d\n",SysCtlClockGet());
while(1)
{
ulindex=0;
Mp3SelectData();
while(ulindex < 15000)
{
while(VS_DREQ_READ() == 0); //等待DREQ为高
for(i=0;i
-
5.全面测试VS1003
查看芯片供电是否正常;
模拟部分是否正常:VS1003的所有DVDD,AVDD管脚以及xReset,TEST(Pin 32)接+3.0V,然后测量RCAP应在1.3V左右,否则芯片模拟部分未正常工作。
检查SCI命令是否正确。给音量控制寄存器SCI_VOL循环写入最高值和最低值,正常情况下能从耳机听到滴滴的声音,具体步骤如下:
拉低xCS;
设置音量最高:SCI_VOL=0x0000;
拉高xCS;
等待500ms,这个时间控制在0.5到1s之间,太大或太小可能都无法听到;
拉低xCS;
这只音量最低:SCI_VOL=0xffff;
等待500ms,道理同上;
拉高xCS;
循环,否则以上步骤无法识别。
可以对SCI的读写做进一步的测试:
拉低xCS;
写音量控制寄存器:SCI_VOL=0xA2F5;
适当延时,等待DREQ为高
读音量控制寄存器,看读回的值是否与写入的一致,如果不为0xA2F5的话,说明SCI读写有问题。
6.正弦测试
Vs1003的SPI总线用来传送MP3数据和控制命令。当要传送MP3数据时,xDCS须设置为低电平,而xCS置1.此时SPI总线称作SDI(串行数据接口)。VS1003拥有一下几种测试模式:
存储器测试,SCI总线测试和正弦测试。
所有这些测试都有相同的步骤:
硬件复位,
置位模式寄存器SPI_MODE的bit5:SM_TEST,
发送测试命令到SDI总线上。
测试命令总共包含8个字节的数据,前4个字节为命令代码,后4个字节为0.正弦测试属于芯片内部的测试功能,如果写SDI无误的话,可以从耳机里听到单一频率的正弦音(可以通过命令更改频率),强烈建议大家对此项步骤测试时不要将耳塞直接塞入耳中,因为系统不同可能导致声音大小会不同,有可能及其刺耳。
正弦测试步骤如下:
1)进入VS1003的测试模式:SPI_MODE=0x0820;
2)等待DREQ为高
3)xDCS = 1(xCS置1),选择vs1003的数据接口
4)向vs1003发送正弦测试命令:0x53 0xef 0x6e n 0x00 0x00 0x00 0x00,其中
n = 0x24, 设定vs1003所产生的正弦波的频率值,具体计算方法见vs1003
的datasheet
wait(500);延时500ms
退出正弦测试,发送命令:0x45 0x78 0x69 0x74 0x00 0x00 0x00 0x00;
wait(500);延时500ms
循环
如果这一步通过了,说明你的VS1003已经做好了为你播放MP3的准备。下面的工作就很轻松了,把MP3的文件数据有条不紊的发给VS1003,让它为你完成MP3的解码和播放任务。至此,VS1003的驱动任务就完成了。
代码如下:
void VsSineTest(void)
{
Mp3PutInReset(); //xReset = 0 复位vs1003
wait(100); //延时100ms
SPIPutChar(0xff);//发送一个字节的无效数据,启动SPI传输
Mp3DeselectControl();
Mp3DeselectData();
Mp3ReleaseFromReset();
wait(100);
Mp3WriteRegister(SPI_MODE,0x08,0x20);//进入vs1003的测试模式
while(VS_DREQ_READ() == 0); //等待DREQ为高
Mp3SelectData(); //xDCS = 1,选择vs1003的数据接口
//向vs1003发送正弦测试命令:0x53 0xef 0x6e n 0x00 0x00 0x00 0x00
//其中n = 0x24, 设定vs1003所产生的正弦波的频率值,具体计算方法见vs1003的datasheet
SPIPutChar(0x53);
SPIPutChar(0xef);
SPIPutChar(0x6e);
SPIPutChar(0x24);
SPIPutChar(0x00);
SPIPutChar(0x00);
SPIPutChar(0x00);
SPIPutChar(0x00);
wait(500);
Mp3DeselectData();//程序执行到这里后应该能从耳机听到一个单一频率的声音
//退出正弦测试
Mp3SelectData();
SPIPutChar(0x45);
SPIPutChar(0x78);
SPIPutChar(0x69);
SPIPutChar(0x74);
SPIPutChar(0x00);
SPIPutChar(0x00);
SPIPutChar(0x00);
SPIPutChar(0x00);
wait(500);
Mp3DeselectData();
//再次进入正弦测试并设置n值为0x44,即将正弦波的频率设置为另外的值
Mp3SelectData();
SPIPutChar(0x53);
SPIPutChar(0xef);
SPIPutChar(0x6e);
SPIPutChar(0x44);
SPIPutChar(0x00);
SPIPutChar(0x00);
SPIPutChar(0x00);
SPIPutChar(0x00);
wait(500);
Mp3DeselectData();
//退出正弦测试
Mp3SelectData();
SPIPutChar(0x45);
SPIPutChar(0x78);
SPIPutChar(0x69);
SPIPutChar(0x74);
SPIPutChar(0x00);
SPIPutChar(0x00);
SPIPutChar(0x00);
SPIPutChar(0x00);
wait(500);
Mp3DeselectData();
}
-
4.vs1003模块的初始化
上述两步完成后就可以通过SPI总线对VS1003进行初始化了。初始化的一般流程如下:
硬复位。xReset=0;
延时,xDCS,xCS,xReset置一;
等待DREQ为高
软件复位:SPI_MODE=0x0804;
等待DREQ为高(软件复位结束)
设置VS1003的时钟:SCI_CLOCKF=0x9800,3倍频;
设置VS1003的采样率:SPI_AUDATA=0xBB81,采样率48K,立体声;
设置重音:SPI_BASS-0x0055;
设置音量:SCI_VOL=0x2020;
向VS1003发送4个字节无效数据,用以启动SPI发送。
代码如下:
硬件复位:
void Mp3Reset(void)
{
Mp3PutInReset();//xReset = 0 复位vs1003
wait(100);//延时100ms
//发送一个字节的无效数据,启动SPI传输
Mp3DeselectControl(); //xCS = 1
Mp3DeselectData(); //xDCS = 1
Mp3ReleaseFromReset(); //xRESET = 1
wait(100); //延时100ms
while(VS_DREQ_READ() == 0); //等待DREQ为高
wait(100); //延时100ms
Mp3SetVolume(10,10);
Mp3SoftReset();//vs1003软复位
}
软件复位:
void Mp3SoftReset(void)
{
Mp3WriteRegister (SPI_MODE, 0x08, 0x04); //软件复位
wait(1); //延时1ms
while(VS_DREQ_READ() == 0); //等待软件复位结束
Mp3WriteRegister(SPI_CLOCKF, 0x98, 0x00);//设置vs1003的时钟,3倍频
Mp3WriteRegister(SPI_AUDATA, 0xBB, 0x81); //采样率48k,立体声
Mp3WriteRegister(SPI_BASS, 0x00, 0x55);//设置重音
Mp3SetVolume(20,20);//设置音量
wait(1); //延时1ms
//向vs1003发送4个字节无效数据,用以启动SPI发送
Mp3SelectData();
SPIPutChar(0);
SPIPutChar(0);
SPIPutChar(0);
SPIPutChar(0);
Mp3DeselectData();
}
-
SPI总线接口的模拟
模拟SPI发送一个字节的数据
函数如下:
void SPIPutChar(unsigned char ucData)
{
unsigned i = 0;
for(i=0; i
-
好,现在说一下,整个程序的编写过程:
初始化VS1003
确保vs1003与处理器的正确与可靠连接。下面是vs1003的引脚图:
对于8962来说,总共需要7个IO口。用三个作为SCLK,MISO,MOSI来模拟SPI模式,这里用GPIOD的4,6 ,7引脚来模拟,即将GPIOD的4,6 ,7引脚分别接到vs1003的SCLK,MISO,MOSI。其余的连接如下:
CS接GPIOC的6号引脚
DREQ 接GPIOC的5号引脚
RESET接GPIOC的4号引脚
DCS接GPIOC的7号引脚.定义如下:
#define VS_RSTn GPIO_PIN_4 //PC4
#define VS_DREQ GPIO_PIN_5 //PC5 input
#define VS_CSn GPIO_PIN_6 //PD6
#define VS_DCn GPIO_PIN_7 //PC7
IO口模拟SPI,用的是GPIOD接口
在这里,引脚的连接如下
SCLK接GPIOD的4号引脚
MISO接GPIOD的6号引脚
MOSI接GPIOD的7号引脚
定义如下:
#define SoftSSICLK GPIO_PIN_4 //PD
#define SoftSSIRx GPIO_PIN_6
#define SoftSSITx GPIO_PIN_7
微处理器模块的正确初始化。
正确配置IO口:
注意将与DREQ连接的IO口(即GPIOC的5号引脚)配置为输入,其余配置为输出(CS,DCS和RESET);将GPIOD的4,6 ,7引脚配置为SPI总线接口。
初始化函数如下:
void vs1003_io_init(void)
{
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
//_gpio_set_input
GPIOPinTypeGPIOInput(GPIO_PORTC_BASE, VS_DREQ);
GPIOPadConfigSet(GPIO_PORTC_BASE, VS_DREQ, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
//_gpio_set_output
GPIOPinTypeGPIOOutput(GPIO_PORTC_BASE, VS_RSTn | VS_CSn | VS_DCn);
GPIOPadConfigSet(GPIO_PORTC_BASE, VS_RSTn | VS_CSn | VS_DCn, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
//_gpio_set_input
GPIOPinTypeGPIOInput(GPIO_PORTD_BASE,SoftSSIRx);//Rx,input
GPIOPadConfigSet(GPIO_PORTD_BASE, SoftSSIRx, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
//_gpio_set_output
GPIOPinTypeGPIOOutput(GPIO_PORTD_BASE, SoftSSICLK | SoftSSITx);
GPIOPadConfigSet(GPIO_PORTD_BASE, SoftSSICLK | SoftSSITx, GPIO_STRENGTH_4MA, GPIO_PIN_TYPE_STD);
//SPIPutChar(0xff);
//SPIPutChar(0xff);
//SPIPutChar(0xff);
//UARTprintf("vs1003_io_init\n");
}
[ 本帖最后由 xielijuan 于 2010-12-24 14:20 编辑 ]
-
最后,SPI接口的一个缺点:没有指定的流控制,没有应答机制确认是否接收到数据。
[ 本帖最后由 xielijuan 于 2010-12-24 14:18 编辑 ]
-
啊哈~~谢谢老师~~~;P
-
谢谢!!我也是要驱动VS1003的!真是太感谢了~~;P
-
嗯,要先格式化你的SD卡,我光盘里的事例程序是有用的。还有就是,串口助手也可能有问题,我曾经给就被困扰过,换个串口工具试试~
-
:$ 呵呵,其实我很少写这么长的东西……谢谢~~
-
:lol 我不是高手,新手~~多多指教~~
-
;P 谢~~~顶~~~
-
谢谢~~:handshake ,新手新手,多多交流~~~
-
呵呵~谢谢~~还没有呢;P 因为我申请的任务事做一个MP3,所以,下一步得先学习扩展芯片解码音频文件,共同学习~~~
-
(6) disk_timerproc (void)
设备时钟中断程序。
void disk_timerproc (void)
{
// BYTE n, s;
BYTE n;
n = Timer1; /* 100Hz递减计时器 */
if (n) Timer1 = --n;
n = Timer2;
if (n) Timer2 = --n;
}
(7)DWORD get_fattime(Void);
实时时钟函数。返回一个32位无符号整数,时钟信息包含在这32位中,如下所示:
bit31:25 年(O..127)从1980年到现在的年数
bit24:21 月(1…12)
bit20:16 日(1..31)
bitl5.1] 时(O..23)
bitl0:5 分(O..59)
bit4:0 秒/2(0..29)
如果用不到实时时钟,也可以简单地返回一个数。正确编写完DiskIO,移植工作也就基本完成了。
DWORD get_fattime (void)
{
return ((2007UL-1980)
-
(4)DRESULT disk_write(BYTE drv,const BYTE*buff,DWORD sector,BYTE count);
写扇区函数。在SD卡写接口函数的基础上编写,*buff存储要写入的数据,sector是开始写的起始扇区count是需要写的扇区数。1个扇区512个字节。执行无误返回O,错误返回非0。
DRESULT disk_write (
BYTE drv, /* 物理磁盘号 */
const BYTE *buff, /* 指向数据缓存的指针*/
DWORD sector, /* 起始扇区号(LBA) */
BYTE count /* 扇区计数器 (1..255) */
)
{
if (drv || !count) return RES_PARERR;
if (Stat & STA_NOINIT) return RES_NOTRDY;
if (Stat & STA_PROTECT) return RES_WRPRT;
if (!(CardType & 4)) sector *= 512; /* 如果需要,转换为地址字节*/
SELECT(); /*CS为低,选中 */
if (count == 1) { /* 单块写*/
if ((send_cmd(CMD24, sector) == 0)
&& xmit_datablock(buff, 0xFE))
count = 0;
}
else { /* 多快写 */
if (CardType & 2) {
send_cmd(CMD55, 0); send_cmd(CMD23, count); /* ACMD23 */
}
if (send_cmd(CMD25, sector) == 0) {
do {
if (!xmit_datablock(buff, 0xFC)) break;
buff += 512;
} while (--count);
if (!xmit_datablock(0, 0xFD)) /*停止传送标记 */
count = 1;
}
}
DESELECT(); /* CS置位 */
rcvr_spi(); /* Idle (Release DO) */
return count ? RES_ERROR : RES_OK;
}
(5)DRESULT disk_ioctl(BYTE drv,BYTE ctrl,VoiI*buff);
存储媒介控制函数。ctrl是控制代码,*buff存储或接收控制数据。可以在此函数里编写自己需要的功能代码,比如获得存储媒介的大小、检测存储媒介的上电与否存储媒介的扇区数等。如果是简单的应用,也可以不用编写,返回O即可。
DRESULT disk_ioctl (
BYTE drv, /* 物理磁盘号*/
BYTE ctrl, /*控制代码 */
void *buff /* 发送接收的控制数据缓冲区*/
)
{
DRESULT res;
BYTE n, csd[16], *ptr = buff;
WORD csize;
if (drv) return RES_PARERR;
res = RES_ERROR;
if (ctrl == CTRL_POWER) {
switch (*ptr) {
case 0: /*子控制代码=0 (关闭) */
if (chk_power())
power_off(); /* Power off */
res = RES_OK;
break;
case 1: /*子控制代码=1 (开启)*/
power_on(); /* Power on */
res = RES_OK;
break;
case 2: /*子控制代码=2 (power get)*/
*(ptr+1) = (BYTE)chk_power();
res = RES_OK;
break;
default :
res = RES_PARERR;
}
}
else {
if (Stat & STA_NOINIT) return RES_NOTRDY;
SELECT(); /* CS = L */
switch (ctrl) {
case GET_SECTOR_COUNT : /* 获取磁盘扇区数*/
if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) {
if ((csd[0] >> 6) == 1) { /* SDC ver 2.00 */
csize = csd[9] + ((WORD)csd[8] 7) + ((csd[9] & 3) > 6) + ((WORD)csd[7]
-
SELECT(); /* CS为低电平,代表选中了SD卡*/
ty = 0;
if (send_cmd(CMD0, 0) == 1) { /* 进入空闲状态 */
Timer1 = 100; /* 初始化超时 1000 msec */
if (send_cmd(CMD8, 0x1AA) == 1) { /* SDC Ver2+ */
for (n = 0; n < 4; n++) ocr[n] = rcvr_spi();
if (ocr[2] == 0x01 && ocr[3] == 0xAA) { /*这个卡可以工作在2.7-3.6范围 do {
if (send_cmd(CMD55, 0)
-
第四步. 编写DiskIO
编写好存储媒介的接口代码后,就可以编写DiskIO了,DiskIO结构如图所示。
FatFs的移植实际上需要编写7个接口函数,分别是:
(1)DSTATUS disk_initialize(BYTE drv);
存储媒介初始化函数。由于存储媒介是SD卡,所以实际上是对SD卡的初始化。drv是物理磁盘号码,由于FatFs只支持一个物理磁盘,所以drv应恒为O。执行无误返回0,错误返回非O。程序如下
DSTATUS disk_initialize (
BYTE drv /* 物理磁盘号*/
)
{
BYTE n, ty, ocr[4];
if (drv) return STA_NOINIT; /* 只支持一个磁盘*/
if (Stat & STA_NODISK) return Stat; /*插槽里没有磁盘 */
power_on(); /* 打开插槽电源*/
send_initial_clock_train();
[ 本帖最后由 xielijuan 于 2010-11-28 22:48 编辑 ]
-
第三步.基本接口函数
在具备SPI读/写接口的基础上编写SD卡接口代码,需要编写3个基本接口函数:
① 向SD卡发送1条命令:
Static BYTE send_cmd (BYTE cmd, DWORD arg)②向SD卡发送1个数据包:
②向SD卡发送1个数据包:
Static BOOL xmit_datablock (const BYTE *buff, BYTE token)
③从SD卡接收1个数据包:
Static BOOL rcvr_datablock (BYTE *buff, UINT btr)
这三个函数,也是在这个文件中,具体怎么实现的这里简要说一下:
(1) 向SD卡发送1条命令:
static
BYTE send_cmd (
BYTE cmd, /* 命令字节 */
DWORD arg /* 参数*/
)
{
BYTE n, res;
if (wait_ready() != 0xFF) return 0xFF;
/* 发送命令包*/
xmit_spi(cmd); /* Command */
xmit_spi((BYTE)(arg >> 24)); /* Argument[31..24] */
xmit_spi((BYTE)(arg >> 16)); /* Argument[23..16] */
xmit_spi((BYTE)(arg >> 8)); /* Argument[15..8] */
xmit_spi((BYTE)arg); /* Argument[7..0] */
n = 0;
if (cmd == CMD0) n = 0x95; /* CRC for CMD0(0) */
if (cmd == CMD8) n = 0x87; /* CRC for CMD8(0x1AA) */
xmit_spi(n);
/* 接收命令响应*/
if (cmd == CMD12) rcvr_spi(); /* 如果停止读写,跳过一个字节*/
n = 10; /*10次超时等待一个有效应答 */
do
res = rcvr_spi();
while ((res & 0x80) && --n);
return res; /*返回一个响应值 */
}
②向SD卡发送1个数据包:
static
BOOL xmit_datablock (
const BYTE *buff, /*被传送的512块数据块 */
BYTE token /* 数据/停止标记*/
)
{
BYTE resp, wc;
if (wait_ready() != 0xFF) return FALSE;
xmit_spi(token); /*传送数据标记 */
if (token != 0xFD) { /* 数据传送*/
wc = 0;
do { /* 传送512字节数据块给存储卡*/
xmit_spi(*buff++);
xmit_spi(*buff++);
} while (--wc);
xmit_spi(0xFF); /* 循环*/
xmit_spi(0xFF);
resp = rcvr_spi(); /* 接收一个响应数据 */
if ((resp & 0x1F) != 0x05) /*如果不接收,返回错误 */
return FALSE;
}
return TRUE;
}
③从SD卡接收1个数据包:
static
BOOL rcvr_datablock (
BYTE *buff, /* 数据缓冲器,用来存数接收到的数据*/
UINT btr /* 字节计数器*/
)
{
BYTE token;
Timer1 = 10;
do { /* 等待数据包*/
token = rcvr_spi();
} while ((token == 0xFF) && Timer1);
if(token != 0xFE) return FALSE; /* 如果不是有效的数据标记,返回错误*/
do { /* 接收数据块到缓冲器*/
rcvr_spi_m(buff++);
rcvr_spi_m(buff++);
} while (btr -= 2);
rcvr_spi(); /* 停止循环*/
rcvr_spi();
return TRUE; /* 返回成功 */
}
//下面是一个等待函数,等待卡准备好
static
BYTE wait_ready (void)
{
BYTE res;
Timer2 = 50; /*超时等待500ms*/
rcvr_spi();
do
res = rcvr_spi();
while ((res != 0xFF) && Timer2);
return res;
}
-
static
void DESELECT(void)
{
GPIOPinWrite(SDC_CS_GPIO_PORT_BASE, SDC_CS, SDC_CS);
}
//这个函数的功能刚好上一个相反,是关闭的作用。
第二步.现在就可以进行读/写了。
//下面是一些变量的定义
static volatile
DSTATUS Stat = STA_NOINIT; /* 磁盘状态 */
static volatile
BYTE Timer1, Timer2; /* 100Hz 的递减计时器*/
static
BYTE CardType; /* b0: MMC(MultiMediaCard,多媒体存储卡), b1:SD卡控制, b2:块寻址,这个变量是存储卡的类型的变量 */
static
BYTE PowerFlag = 0; /*显示电源是否打开 */
通过sPI发送一个字节到存储卡
static
void xmit_spi(BYTE dat)
{
DWORD rcvdat;
SSIDataPut(SDC_SSI_BASE, dat); /* 将数据写入发送队列tx fifo */
SSIDataGet(SDC_SSI_BASE, &rcvdat); /* 写的时候刷新读取的数据 */
}
通过sPI从存储卡读取一个字节
static
BYTE rcvr_spi (void)
{
DWORD rcvdat;
SSIDataPut(SDC_SSI_BASE, 0xFF); /* 写入虚拟数据*/
SSIDataGet(SDC_SSI_BASE, &rcvdat); /*从接收队列 rx fifo读取数据 */
return (BYTE)rcvdat;
}
static
void rcvr_spi_m (BYTE *dst)
{
*dst = rcvr_spi();
}
[ 本帖最后由 xielijuan 于 2010-11-28 22:42 编辑 ]