基于SOPC的音乐播放系统设计

1 目的
1. 让自己能真正用SOPC做出一个实际的定西,学习基于SOPC的嵌入式软件开发的基本流程;
2. 了解一些比较实用的定西,比如SD卡,VS1003,TFT等,并且能做到同时操作,而不是简单的流水灯,蜂鸣器,数码管;
3. 了解,熟练掌握一些常用的协议,I2C、 SPI、RS232等,但本次设计中主要使用的是SPI协议;
2 我所做的工作
SOPC的构建,主要是调用一些IP核,使用类似绘制PCB原理图的方法,连线搭建硬件系统。具体参考林正红的《基于SOPC的SDRAM和FLASH的读写实验》。(有兴趣也可以参考《Nios ii 那些事儿》来完成了简单的Nios ii硬件系统设计,同时也可通过http://kingst.cnblogs.com/获得最新版本的《Nios ii 那些事儿》。)
实现了SPI模式下的SD卡的驱动,由于Nios ii硬件SPI的缺陷,其SPI的速率不能改变,而在对SD的驱动过程中,必须要改变SPI的速率才能完成初始化,所以笔者选择使用IO端口模拟4线SPI。
VS1003的驱动,其实V实现了FAT32文件系统与SD卡的挂接。本部分使用的是开源的ZNFAT32文件系统,经过裁剪,实现的功能主要是定位文件,打开文件,读取文件,创建目录,创建文件等功能。(同时也通过http://www.znmcu.cn/softshow.asp?id=47 可以获得ZNFAT32文件系统的源代码)
VS1003能对很多常见格式的音频信号进行解码,比如MP3,WMA,MIDI等,在本设计中只对MP3格式进行解码,以及从SD卡读取MP3格式的音乐送给VS1003解码后播放。
TFT液晶屏的驱动,其实一块液晶屏,有很多需要学习的地方,而在本设计中只是简单的显示了一张位图,在本设计中既没有实现触摸功能,也没有设计GUI,只是实现了从SD卡中读取BMP格式的图片(16位色)交给TFT显示。
音乐系统的实现,综合上面所有的模块,实现从SD卡提取MP3格式的音乐和BMP格式的图片,分别交由VS1003播放音乐和TFT显示图片,因为没有上操作系统的原因,暂时还不能做到同步。
3 本设计的整体构架

图 1 音乐播放系统总体构架

说明:本设计是基于DE2开发板,以Altera的Cyclone II系列的FPGA为主控制器,配上SDRAM和FLASH,使用SOPC技术,构成一个简单的SOPC系统,用于控制SD卡,TFT液晶显示屏,VS1003音频播放模块等,实现自制的简单音乐播放系统。在设计中FPGA通过分别调用Altera库中的IP核来控制Flash和SDRAM,通过模拟的SPI总线来分别控制SD卡,TFT模块,VS1003模块。能实现将SD卡中的MP3格式的音乐交由VS1003模块解码播放,BMP格式的图片交由TFT模块显示。
在设计中,SD卡的最大存储量为4G,在设计中提供的主时钟是50M,在此时钟作用下,IO的翻转速度是5M左右,模拟的SPI速度应该只能在50Kbps左右,所以本设计中VS1003模块支持的解码率应该小于等于48Kbps(数据手册上,VS1003支持的解码率为5~384Kbps)。同样是因为模拟SPI的速率问题,TFT显示屏的刷屏时间应该是0.5S左右。而播放和显示的总容量应该可以到SD卡的最大存储容量4G。
4 具体实现
4.1 硬件部分
4.1.1 SOPC系统构建
NIOS32核,SDRAM FLASH PIO 等IP核的调用。
具体的构建过程可参见林正红的《基于SOPC的SDRAM和FLASH的读写实验》。下面仅对其设置做适当的补充,主要体现在PIO核的调用和IO端口输入输出属性的设置。
本部分要注意对于SDRAM,FLASH的设置须与DE2学习板上的SDRAM,FLASH一致。存储器的类型与属性如下表所示:

地址线宽度(位)	数据线宽度(位)

FLASH 22 8
SDRSM 12 16
表 1 存储器的类型与属性

同时还要知道GPIO模拟SPI之后,各端口属性的设置,如下表所示:

属性	功能说明

模拟CS 输出 片选信号
模拟MOSI 输出 主出从入
模拟MISO 输入 主入从出
模拟TCLK 输出 时钟
表 2 IO端口属性及功能说明

下面简单的介绍PIO核的调用与设置:
设置图如下所示:

图 2 查找PIO IP核
在Altera的库中找到PIO核,双击调用该核,就会出现下图所以的界面,

图 3 设置CS的基本属性

图 4 CS的其他设置
设置PIO的宽度:根据需要设置位宽为1,由于是准备将该IO用来模拟CS信号,所以设置为仅输出。

图 5 MISO的基本设置

扫描二维码关注公众号,回复: 15578993 查看本文章

图 6 MISO的其它设置

设置PIO的宽度,根据需要设置位宽为1,由于是准备将该IO用来模拟MISO信号,所以设置为仅输入。同时不需要该IO具有中断功能,所以也没有使能中断,也没有给出仿真模型。

图 7 MOSI的基本设置

图 8 MOSI的其它设置
设置PIO的宽度:根据需要设置位宽为1,由于是准备将该IO用来模拟MOSI信号,所以设置为仅输出。

图 9 TCLK的基本设置

图 10 TCLK的其它设置
设置PIO的宽度:根据需要设置位宽为1,由于是准备将该IO用来模拟MOSI信号,所以设置为仅输出。
4.1.2 QuartusII中的整体硬件电路的实现

图 11 硬件总电路图
本设计中需要使用的硬件部分主要有NIOS II软核,PLL核,GPIO核,输入输出接口等部件。
4.2 软件部分
4.2.1 主程序的流程图:

图 12 程序流程图
4.2.2 SPI模式下SD卡驱动
IO模拟SPI的实现
void spi_write(uchar temp)
//使用SPI协议,慢速写一个字节的数据。
unsigned char spi_read()
//使用SPI协议,慢速读一个字节的数据。
void spi_write_fast(uchar temp)
//使用SPI协议,快速写一个字节的数据
实现对寄存器的读与写
unsigned char write_cad_init(unsigned char *pead)
//初始化过程中的,写命令。参数是待写入的命令码。
unsigned char write_cad(unsigned char *pead)
//不在初始化状态时,写命令。参数是待写入的命令码。
通过对寄存器的读写实现对SD的复位初始化等操作
unsigned char SD_reset()
//SD卡的复位函数,返回值为01H,则表明复位成功。
unsigned char SD_init()
//SD卡的初始化函数,返回值为00H,则表明初始化成功
通过对寄存器的读写实现模块读和模块写
unsigned char SD_write_block(unsigned long address,unsigned char *buffer)
//SD卡的模块写函数,返回值的最后三位的值为05H时,表明模块写成功
//参数:参数1指定写入的地址,参数2指定写入的数据
unsigned char SD_read_block(unsigned long address,unsigned char *buffer)
//SD卡的模块读函数,返回值为0,表明模块读成功。
//参数:参数一指定读入的地址,参数2指定读入的数据存放的位置
下面的主要的时序图:

图 13 SPI模式下,驱动SD卡的复位时序

图 14 SPI模式下,驱动SD卡的初始化时序

图 15 SPI模式下,驱动SD卡的模块读时序

图 16 SPI模式下,驱动SD卡的模块写时序
注:
本部分需要仔细参阅数据手册,了解复位(CMD0),初始化(CMD1),读(CMD16),写(CMD24)的命令码的格式,以及最后的校验码。了解它们需要的时序过程,最后能实现的功能,同时了解如何判断这些功能实现了没。在本部分一定要注意对SD卡的初始化需要慢速SPI,而读写就需要使用快速SPI。慢速是为了让SD初始化完成,而快速则是为了让SD卡尽可能发挥的作用。当然要想速度更快,则需使用SD模式来驱动SD卡。因为SPI模式下,SD卡是通过一根线来传输数据,而SD模式下,SD卡是通过四根线来传输数据的。但是SD模式的驱动相对比较复杂,所以我选择驱动SD卡工作在SPI模式下。
要了解SPI协议的基本内容:片选为低时,时钟上升沿,写数据;时钟下降沿,读数据。
同时要严格按照读写的时序操作,要根据SD卡返回的数据,做进一步的处理,特别需要注意补充时钟。
在SPI模式下驱动的SD卡,支持最大的容量是4G。

4.2.3 VS1003 驱动
1)IO模拟SPI的实现
void vs_spi_write_byte(unsigned char data)
// 功能:使用SPI协议 ,写一个字节。
uchar vs_spi_read_byte()
// 功能:使用SPI协议,读一个字节
//返回读出的数据。
2)对寄存器的读写:
void vs_write_reg(uchar address ,uchar datah,uchar datal)
//功能:向寄存器中写入命令码
//参数说明:参数1寄存器的名字,因为VS1003的命令码是32位的,所以//参数2,3 是命令码。
uint vs_read_reg(uchar address)
//功能:从寄存器中读出状态
3)初始化:
void vs_init()
//功能:VS1003的初始化
4)主要时序图:

图 17 VS1003的字读时序

图 18 VS1003的字写时序
注:在本部分中须注意,VS1003的缓存是32BYTE的,所以可以通过考虑快速写入,留出一定的时间来用于同步显示图片,从而达到在没有操作系统的时候,进行伪多任务操作。
4.2.4 TFT驱动
1) 对寄存器的写:
void write_reg(uint reg,uint data)
//功能:使用SPI协议,写寄存器
//参数:参数1指定寄存器,参数2指定命令码
2) 写数据:
void write_data_start()
//功能:开始写数据
void write_data(uint data)
//功能:写数据
void write_end()
//功能:写数据结束
3) 复位:
void tft_reset()
//功能:TFT复位
4) 初始化:
void tft_init()
//功能:TFT的初始化
5) 设置窗口:
void tft_setwindow(uint startx,uint starty,uint endx,uint endy )
//功能:设置显示的窗口,参数是左上角,右下角的横纵坐标。
6) 以某种颜色清屏:
void tft_clearscreen(uint color);

图 19 TFT主控芯片SSD1289的驱动时序图
注:在本部分中须注意位图文件在SD卡中的存储形式,直接是二进制文件,所以不需要解码,直接显示。但考虑到在SD卡中图片的RGB只能是555模式的,而本来16位色的RGB是565模式的,所以在显示的过程中需要考虑RGB模式的转换。所以我在这个地方进行了简单处理,只做16位色的位图显示,如果是24位时RGB模式的转换相对比较复杂。
4.2.5 文件系统的挂接
1) SD卡的挂接
定义两个指针:
struct znFAT_Init_Arg *pArg;
struct znFAT_Init_Arg sdcard;
完成SD卡与FAT32的挂接:pArg = &sdcard
同时把SD卡的模块读,模块写与FAT32的读写挂接起来:
UINT8 znFAT_ReadSector(UINT32 addr,UINT8 *buf)
{
return SD_read_block(addr,buf);
}
//功能:文件系统读扇区
UINT8 znFAT_WriteSector(UINT32 addr,UINT8 *buf)
{
return SD_write_block(addr,buf);
}
//功能:文件系统写扇区
2) VS1003的挂接
void vs1003_mp3(struct FileInfoStruct *pfi,UINT32 offset)
// 功能: 文件系统从指定的地方区数据,交由VS1003
//参数:参数1,文件信息结构体的指针变量,参数2,指定的地方
3) TFT的挂接
void picture(struct FileInfoStruct *pfi,UINT32 offset)
// 功能: 文件系统从指定的地方区数据,交由TFT
//参数:参数1,文件信息结构体的指针变量,参数2,指定的地方
注:在本部分中,需要基本了解ZNFAT的构建思想(但不需要完全理解ZNFAT文件系统是怎么写出来的),这样才能基于ZNFAT文件系统写出自己的接口函数,本设计中的VS1003模块,TFT液晶屏就是通过一个接口函数与FAT32文件系统挂接起来的。
5.调试过程遇到的问题和解决方式
在调试过程中问题非常的多,下面重点介绍几个我觉得很典型的错误:
5.1 SD卡驱动部分的问题
5.1.1复位始终不能成功
现象:下载程序之后,没有任何的反应,经调试发现程序一直在
while(temp!=0x01); //成功则返回01H处死循环。
原因:

知道是使用SPI协议驱动,但没有注意到在初始化过程中需要使用慢速SPI;
在研究时序时不认真,没有注意到补充时钟的重要性;
校验码没有仔细处理,特别是CMD0的校验码是0x95H,尽管校验码没有什么实际的作用但是还是要正确使用;
刚开始不知道怎么判断CMD0是否写入成功,没有看到返回值01H。
解决办法:
在使用端口模拟的SPI协议中,都加入了10ms左右的延时,在CS拉低之前补充75个时钟周期,CMD0写入完成之后补充8个时钟周期,再读数据输出口的值,若为01H,则表明复位成功。
5.1.2模块写不成功
原因:

因为觉得看到数据手册上面写的校验码不用考虑(don’t care),所以我就只写没有写校验码;
一次写入长度的默认是512BYTE,我也通过CMD21将其改为其他长度,最后在移植文件系统时,才了解一些SD的硬件结构,发现SD卡的一个扇区的容量正好是512BYTE,所以为了降低后续工作的难度,又将长度改回了512BYTE。
开始标志其实可以为任意值,但我居然把标志位设置的与需要写入的数据冲突了。
需要根据数据输出的状态来,判断是否进行下一步的操作,也就是必须检测到命令写入标志之后,才能写入512BYTE的数据,补充8个时钟周期之后,才开始检测写入成功标志。
解决办法:
加上了2个校验码(FFH),没有设置CMD21,将起始标志位改为了FEH,同时在调试的过程中分为了两部,先调试命令是否写入成功,再调试数据是否写入成功。另外,在快速写入的工程中,需要使用快速SPI,所以将慢速SPI中的10ms左右的延时去掉。
5.2 文件系统理解与挂接
在单独调试文件系统和SD卡时都能得到预期的结果,但是在将SD卡与文件系统挂接之后就得不到预期的结果了。
定义两个设备初始化结构体:
struct znFAT_Init_Arg *pArg;
struct znFAT_Init_Arg sdcard;
调试过程中发现,设备初始化结构体指针一直指空。
解决办法:
将设备初始化结构体sdcard的地址赋给设备初始化结构体指针pArg,即: pArg = &sdcard ,完成SD卡与文件系统的挂接。
所以说在定义了某些变量时一定要记得初始化,出现空的变量有时会影响整个程序的调试与运行。
5.3 VS1003的驱动
驱动方式与SD卡的驱动方式类似,而且更为简单,不需要区分快速SPI和慢速SPI,直接全部使用快速SPI。在有前面的基础之后驱动过程中没有太大的问题!主要问题是在数据手册上没有找到写命令码0x02,读命令码0x03.而如果没有这两个命令码的话,整个驱动程序是完全不可能实现的。所以我觉得不一定要完全依靠数据手册,可以多方搜集资料。
5.4 TFT的驱动
5.4.1 用户手册理解错误

图 20 74HC573在TFT模块中总线的使用
在驱动TFT模块的工程中,将用户手册上的原理图理解错误。看到锁存器74HC573,就以为是TFT的数据总线是8位的。所以在开发硬件部分时,将数据总线设置成了8位的。在软件开发完成之后,下载到DE2开发板时,运行,没有任何现象。使用示波器检测数据口的电平,发现TFT的数据总线上,有一部分没有接收到数据。所以就判断自己的总线的位宽设置不正确。进一步研究TFT模块的原理图发现,确实是自己将原理图理解错误。
原理图所表达的意思时,该模块可以支持16位和8位。而在模块上,根本就没有焊接74HC573,所以此种情况下只能使用16位的数据总线。
解决办法:
在硬件设计中,将TFT的数据总线设置为16位的,在软件设计中,修改数据的传输方式。
5.4.2 数据手册有错
在TFT的驱动过程中,需要开发一些基本的窗口设置函数,这样就需要使用指示坐标的寄存器。但是数据手册,有点错误。具体如下图所示:

图 21 SSD1289的错误

6 预期与结果
本来打算是做一个比较完整的基于SOPC的音乐播放器,想要实现如下功能:
播放MP3
显示BMP
具有中文GUI
带有控制功能
实现PCB的制作与调试
最先接触这个题目是因为以前实验室的一个师兄,他就是依靠这样一个类似的系统去应聘周立功公司的,所以我就想自己做个程序试试。刚开始到网上搜资料的时候感觉还是比较难找,后来发现了一个比较好的网站www.ourdev.com,资料会稍微多点,这个上面我学到了很多。当然师兄也给了我很大的帮助,使自己能在3个月内,基本做出了一个音乐播放器的雏形。尽管功能只实现了很小的一部分,但仍感觉有点喜悦感。
现在回想一下,其实也不是特别的难,主要是要学会分解,现在基本分为了4个模块,各个模块之间的前后继承关系,使用VS1003模块和TFT模块的基础是FAT32文件系统,FAT32文件系统的基础是SD卡的驱动。所以,在本设计中最基础,最核心,最难的部分应该是SD卡的驱动以及FAT32文件系统的理解。我建议在初学的时候可以先看看于振南关于SD卡和FAT32文件系统的视频讲解(详见www.znmcu.cn)。在SD卡驱动完成,FAT32文件系统挂接成功之后,接下来的VS1003的驱动,TFT的驱动就比较简单了。各个模块的驱动都完成之后,最后系统的组合联调就很容易了,在主程序要做的就只是对所有IO属性的设定,各个模块的初始化以及文件系统的调用。

7 附录
7.1 参考资料
《DE2_user.pdf》
《SDcard.pdf》
《VS1003.pdf》
《VS1003_user.pdf》
《SSD1289.pdf》
《TFT_user.pdf》
《NIOS II 那些事儿》
振南电子 www.znmcu.com
中国电子开发网www.ourdev.cn

猜你喜欢

转载自blog.csdn.net/ambiguous__/article/details/130798097