新型U盘MP3播放器的设计
作者:高金龙 杨昆 姚明月
摘要:本文设计了一款将存储器与播放器分离的新型U盘MP3播放器,系统采用STC12C5A单片机与ch375组成USB的host模式用于U盘的读取,采用VS1003MP3解码芯片用于MP3歌曲的解码。实验证明,将存储器与播放器分离后工作良好,使用方便。
关键字:存储器播放器分离、ch375、vs1003、MP3解码。
1.引言
随着电子技术的快速发展,电子类产品尤其是消费类电子产品跟新换代的速度越来越快。MP3就是对人们生活很重要的一款消费类电子产品。近年来,MP3产品朝着小巧、轻便、大存储、高音质方向快速发展。与此同时新的问题也日渐显示出来,存储器与播放器的一体化虽然使MP3变得小巧,但若想存更多的歌曲只能选择购买新的产品了,造成很大的浪费。同时,一体化也使MP3在车载MP3方向的应用受到限制。于是,存储器与播放器分离将成为MP3发展的新方向。我们的设计采用STC12C5A单片机与ch375组成USB的host模式用于U盘的读取,采用VS1003MP3解码芯片用于MP3歌曲的解码。从而将存储器与播放器分离并且良好的工作。
2.硬件介绍
2.1单片机最小系统。
图1 单片机最小系统
本文采用宏晶科技生产的12C5A56S2单片机。因为它拥有增强性51内核,速度比一般51单片机快8~10倍。56K的片内flash程序存储器和1280字节的片内RAM存储器使其能够运行更大的程序,处理更多的变量。
2.2 CH375U盘读写系统
图2 CH375外围电路
CH375 是一个由南京沁恒公司生产的USB总线的通用接口芯片,支持USB-HOST主机方式和USB-DEVICE/SLAVE 设备方式。在本地端,CH375 具有8 位数据总线和读、写、片选控制线以及中断输出,可以方便地挂接到单片机/DSP/MCU/MPU等控制器的系统总线上。在USB 主机方式下,CH375还提供了串行通讯方式,通过串行输入、串行输出和中断输出与单片机/DSP/MCU/MPU 等相连接。CH375 的USB 设备方式与CH372 芯片完全兼容,CH375 包含了CH372 的全部功能。CH375 的USB主机方式支持常用的USB全速设备,外部单片机可以通过CH375按照相应的USB 协议与USB 设备通讯。CH375 还内置了处理Mass-Storage 海量存储设备的专用通讯协议的固件,外部单片机可以直接以扇区为基本单位读写常用的USB 存储设备(包括USB 硬盘/USB 闪存盘/U 盘)。
图3 CH375读写
2.3 特点
● 低速和全速USB-HOST 主机接口,兼容USB V2.0,外围元器件只需要晶体和电容。
● 低速和全速USB 设备接口,完全兼容CH372 芯片,支持动态切换主机与设备方式。
● 主机端点输入和输出缓冲区各64 字节,支持12Mbps 全速USB 设备和1.5Mbps 低速设备。
● 支持USB 设备的控制传输、批量传输、中断传输。
● 自动检测USB 设备的连接和断开,提供设备连接和断开的事件通知。
● 内置控制传输的协议处理器,简化常用的控制传输。
● 内置固件处理海量存储设备的专用通讯协议,支持Bulk-Only传输协议和SCSI、UFI、RBC 或
等效命令集的USB 存储设备(包括USB 硬盘/USB 闪存盘/U 盘/USB 读卡器)。
● 通过U 盘文件级子程序库实现单片机读写USB 存储设备中的文件。
● 并行接口包含8 位数据总线,4 线控制:读选通、写选通、片选输入、中断输出。
● 串行接口包含串行输入、串行输出、中断输出,支持通讯波特率动态调整。
● 支持5V 电源电压和3.3V 电源电压,支持低功耗模式。
● 采用SOP-28无铅封装,兼容RoHS,提供SOP28到DIP28 的转换板,引脚基本兼容CH374芯片。
2.4 VS1003MP3解码系统
图4 VS1003MP3解码系统
VS1003是由芬兰VLSI公司出品的一款单芯片的MP3/WMA/MIDI音频解码和ADPCM编码芯片,其拥有一个高性能低功耗的DSP处理器核VS_DSP,5K的指令RAM,0.5K的数据RAM,串行的控制和数据输入接口,4个通用IO口,一个UART口;同时片内带有一个可变采样率的ADC、一个立体声DAC以及音频耳机放大器。
支持的音频的编解码格式为:
解码:MP3(mpeg1和mpeg2,层3),MP3+V,WMA,WAV,MIDI,SP-MIDI;
编码:IMA ADPCM(单声道),麦克风和线入(Line input)两种输入方式。
VS1003共有16个16位的寄存器,地址分别为0x0 – 0xF;除了模式寄存器(MODE,0x0)和状态寄存器(STATUS,0x1)在复位后的初始值分别为0x800和0x3C外,其余的寄存器在VS1003初始化后的值均为0。
3. 软件介绍
本文在软件编程方面将该系统分为两大的部分,即CH375 U盘部分和VS1003解码部分。下面将分别对它们进行介绍。
3.1 CH375 U盘
在CH375部分本文需要用到读写扇区函数及FAT文件系统的相关函数。CH375对U盘的读写可以是字节级的读取,也可以是扇区级的读取,为了提高U盘的读取速度以使MP3播放流畅,本文采用扇区级读取。函数流程图如下:
3.2 ZNFAT文件系统
文件系统是用于识别打开并读取MP3文件,本系统采用的是被广泛使用的ZNFAT文件系统,该系统通过两个结构体变量将文件系统与U盘读写函数挂接以供单片机调用。通过ZNFAT系统可以获得存储器的大小、类型和扇区数,还可以得到文件的名称、大小、创建时间和偏移量等信息。当然也可以通过该文件系统在U盘中查找、创建、删除文件。该设计主要功能是将U盘中的MP3文件按扇区读出并送vs1003解码。
ZNFAT中包括了诸如打开文件、查找文件、读取文件内容等各种待调用的子函数。如获取文件剩余容量子函数:
#ifdef znFAT_GET_REMAIN_CAP
UINT32 znFAT_Get_Remain_Cap(void)
{
znFAT_ReadSector(1+pArg->BPB_Sector_No,znFAT_Buffer);
if(((struct FSInfo *)znFAT_Buffer)->Free_Cluster[0]==0xff
&& ((struct FSInfo *)znFAT_Buffer)->Free_Cluster[1]==0xff
&& ((struct FSInfo *)znFAT_Buffer)->Free_Cluster[2]==0xff
&& ((struct FSInfo *)znFAT_Buffer)->Free_Cluster[3]==0xff)
return pArg->Total_Size;
else
return
LE2BE(((struct
FSInfo *)znFAT_Buffer)->Free_Cluster,4)*pArg->SectorsPerClust*pArg->BytesPerSector;
}
#endif
3.3 VSl003
VSl003通过一个工作于从模式的SPI串行总线与主机进行数据和控制信息的交流,控制信号和数据信号的传送分别采用xCS和xDCS作为同步信号数据信号xDCS为低时通过串行接口传送音频数据,当控制信号xCS为低时通过串行接口传送控制命令,控制命令总是为16位,通过读/写不同的寄存器来实现对VSl003的控制。作为从机工作模式,VSl003通过一个信号线DREQ指示是否允许主机传送数据,当DREQ为高时,VSl003至少可以接受32 kB的数据或者控制命令,其驱动函数流程图如下:
3.4 12232F液晶
12232F是一种图形点阵液晶显示器,它主要由行驱动器/ 列驱动器及128×32全点阵液晶显示器组成。可完成16×2个(16×8点阵)ASCII码显示,也可以显示8×2个(16×16点阵)汉字。与外部CPU接口可采用并行方式控制。通过液晶显示可以显示歌名、歌词、文件系统信息等。
由于该作品所采用的STC89C5A单片机可用的I/O口并不丰富,同时提高抗干扰性,所以采用串行方式操作该液晶。
液晶操作函数见附录。
实验结果
由于实验条件有限,实验室不能提供双层PCB板的制作,所以本设计采用单层PCB印刷技术,这直接导致电路板面积增大以及飞线连接。本设计软件设计的传输均为SPI连接,抗干扰性强,所以不会造成干扰。
图X 正视图
图X 后视图
图X U盘中的文件
上电以后,FAT文件系统后自动寻找MP3文件,并按顺序播放,并在液晶屏上显示文件名。
5. 结语
本设计最大限度的利用了硬件资源,在51单片机这种并不充裕的硬件资源中实现了MP3歌曲的有序流畅的播放。CH375模块能够正常的对U盘进行识别和读取数据。VS1003解码模块解码后的音乐音质效果和连贯性都不错。存储器与播放器分离的设计使得使用者根据实际情况选择不同的U盘存储器,并且U盘读写模块还可以用于汽车电子及数据采集存储方面。
参考文献及资料
[1]单片机原理及其接口技术,第二版,清华大学出版社,胡汉才
[2]单片机高级外设,振南电子网站,于振南
附录(程序清单):
1、CH375读U盘程序:
unsigned char CH375_Read_Sector(unsigned long addr,unsigned char *pBuf)
{
unsigned char i=0,j=0,k=0;
unsigned char status,len;
unsigned int counter=0;
for(k=0;k<4;k++)
{
counter=0;
CH375_Write_Cmd(CMD_DISK_READ);//磁盘读取命令
CH375_Write_Dat(addr);
CH375_Write_Dat(addr>>8);
CH375_Write_Dat(addr>>16);
CH375_Write_Dat(addr>>24); //送入32位扇区地址
CH375_Write_Dat(1); //送入扇区数
for(i=0;i<8;i++)
//CH375的数据缓冲区为64字节,所以读取一个扇区要读8次
{
status=CH375_Wait_Int();
//等待CH375中断信号,读取状态码
if(status==USB_INT_DISK_READ)
//如果状态码是USB_INT_DISK_READ,就开始读取数据,否则发生错误
{
CH375_Write_Cmd(CMD_RD_USB_DATA); //送入读数据的命令
delay(4);
len=CH375_Read_Dat(); //先读到的是数据的长度
do
//把随后CH375送过来的数据放入pBuf中
{
pBuf[counter++]=CH375_Read_Dat();
}while(--len);
CH375_Write_Cmd(CMD_DISK_RD_GO);
//送入继续读数据的命令,CH375会将下面64个字节放入缓冲区,等待单片机来读
}
else
{
return 1; //发生错误,返回状态码
}
}
status=CH375_Wait_Int(); //读取8次以后,最后一次送入继续读数据的命令,等待CH375产生中断信号,并读取状态
if(status==USB_INT_SUCCESS) //如果状态码为USB_INT_SUCCESS,说明读取扇区成功,返回0
{
return 0;
}
else
if(status==USB_INT_DISK_ERR )
{
delay(1000);
CH375_Write_Cmd(CMD_DISK_R_SENSE); /* 获取USB存储器的容量 */
status=CH375_Wait_Int(); /* 等待中断并获取状态 */
if(status!=USB_INT_SUCCESS) /* 出现错误 */
return 1;
}
}
}
2、VS1003驱动程序:
void SPI_WriteByte(unsigned char x)
{
dat=x;
//将x的值赋给可位寻址的变量dat,以便取出各个位
SPI_SI=dat7; //取出第7个位,写到数据线上
SPI_SCL=0;
SPI_SCL=1;
//时钟线产生上升沿,数据被写入
SPI_SI=dat6;
SPI_SCL=0;
SPI_SCL=1;
SPI_SI=dat5;
SPI_SCL=0;
SPI_SCL=1;
SPI_SI=dat4;
SPI_SCL=0;
SPI_SCL=1;
SPI_SI=dat3;
SPI_SCL=0;
SPI_SCL=1;
SPI_SI=dat2;
SPI_SCL=0;
SPI_SCL=1;
SPI_SI=dat1;
SPI_SCL=0;
SPI_SCL=1;
SPI_SI=dat0;
SPI_SCL=0;
SPI_SCL=1;
}
从VS1003中读出一字节
unsigned char SPI_ReadByte()
{
SPI_SO=1;
SPI_SCL=1;
SPI_SCL=0;
dat7=SPI_SO;
SPI_SCL=1;
SPI_SCL=0;
dat6=SPI_SO;
SPI_SCL=1;
SPI_SCL=0;
dat5=SPI_SO;
SPI_SCL=1;
SPI_SCL=0;
dat4=SPI_SO;
SPI_SCL=1;
SPI_SCL=0;
dat3=SPI_SO;
SPI_SCL=1;
SPI_SCL=0;
dat2=SPI_SO;
SPI_SCL=1;
SPI_SCL=0;
dat1=SPI_SO;
SPI_SCL=1;
SPI_SCL=0;
dat0=SPI_SO;
return (dat);
}
向VS1003的功能寄存器中写入数据
void VS_Write_Reg(unsigned char addr,unsigned char hdat,unsigned char ldat)
{
VS_DREQ=1; //51单片机IO作输入时先置为1
while(!VS_DREQ); //VS1003的DREQ为高电平时才接收数据
VS_XCS=0; //打开片选,SCI有效,这样才能对功能寄存器进行读写
SPI_WriteByte(0x02); //写入操作码0x02 00000010 (功能寄存器写操作)
SPI_WriteByte(addr); //写入寄存器地址
SPI_WriteByte(hdat); //写入高字节
SPI_WriteByte(ldat); //写入低字节
VS_XCS=1; //关闭片选,SCI无效
}
从VS1003的功能寄存器中读取数据
unsigned int VS_Read_Reg(unsigned char addr)
{
unsigned int temp=0;
VS_DREQ=1;
while(!VS_DREQ); //VS1003的DREQ为高电平时才接收数据
VS_XCS=0;
//打开片选,SCI有效
SPI_WriteByte(0x03); //读出操作码0x03 00000011(功能寄存器读操作)
SPI_WriteByte(addr); //写入寄存器地址
temp=SPI_ReadByte(); //读高字节
temp<<=8;
temp|=SPI_ReadByte(); //读取低字节,与高字节拼成一个字
VS_XCS=1;
//关闭片选,SCI无效
return temp; //返回读到的值
}
3、12232液晶驱动程序:
#define U8 unsigned char
#define U16 unsigned int
#define CMD 0
#define DATA 1
sbit LCD_CS=P2^5;
sbit LCD_SID=P2^6;
sbit LCD_SCLK=P2^7;
void LCD_Send_Byte(U8 dat) //SPI操作
{
U8 i;
for(i=0;i<8;i++)
{
dat<<=1;
LCD_SID=CY;
LCD_SCLK=0;
LCD_SCLK=1;
}
}
void LCD_Send_CD(bit CD,U8 dat) //向液晶发送命令或数据,CD为0时为命令,为1时为数据
{
LCD_CS=1;
LCD_Send_Byte(CD==0?0xf8:0xfa);
LCD_Send_Byte(dat&0xf0);
LCD_Send_Byte(dat<<4);
LCD_CS=0;
}
void LCD_Init()//液晶初始化,上电时执行一次
{
LCD_Send_CD(CMD,0x30);
LCD_Send_CD(CMD,0x02);
LCD_Send_CD(CMD,0x06);
LCD_Send_CD(CMD,0x0c);
}
void LCD_String(U8 add,U8 *pt)//向指定位置写入字符串,add为地址,*pt为字符串的起始地址
{
LCD_Send_CD(CMD,add);
while(*pt!='\0')
LCD_Send_CD(DATA,*pt++);
}