最近整理了这方面的资料,发现有些东西值得拿出来共享一下,所以在此整理下资料和对比两次所用的一些模块和资料。
1.曾经的开发进程:http://forum.eepw.com.cn/thread/220095/1
主控:瑞萨RL78/G13
音频解码模块:VS1003B
显示:TFT彩屏2.4寸
SD卡:1G的SD卡,FAT32文件系统
文件级操作所用文件:精简版znFAT
功能:音乐播放,以及图片浏览和文本阅读
2.曾经共享过的帖子:http://forum.eepw.com.cn/thread/235725/1
主控:STM32F103ZET6音频解码模块:VS1053
显示:TFT彩屏2.8寸
SD卡:4G的SD卡,FAT32文件系统
文件级操作所用文件:znFAT完整版
操作系统:ucos_ii
界面:ucgui
在音频解码模块方面,两个模块的操作方法基本上一样,只是我们除了让它解码音频文件,一般都希望能够读取出频谱数据,这样更加绚丽。两款模块都是先写入补丁代码之后,便可以读取出14个频谱数据。在做第二的时候以为俩模块要写入的补丁文件是一样的,结果是木有任何反应,之后在网上搜到了两外一版补丁文件。
VS1003b的补丁代码可以查看代码里的plug.h。写入补丁以及读取频谱的函数如下:
void WriteVS1003Plug() { uint16 i; VS1003SetXDCS(); for(i = 0;i < 945;i++) { VS1003WriteRegister(atab[i],(dtab[i] >> 8),(dtab[i] & 0xff)); } } void ReadVs1003Plug(uint8 *rBuffer) { uint8 i; VS1003SetXDCS(); VS1003WriteRegister(VS_WRAMADDR,0x18,0x04); for(i = 0;i < 14;i++) { *rBuffer++ = VS1003ReadRegister(VS_WRAM); } VS1003ClrXDCS(); }
VS1053的补丁文件见代码里的SPEC_REW.H,写入与读取函数如下:
void WriteVS1003Plug() { u16 i; for(i = 0;i < 970;i++) { VS_WR_Cmd(atab[i],dtab[i]); } } #define Spec_Base 0x1810 u8 ReadVs1003Plug(u16 *p) { u8 byteIndex=0; u16 bands; OS_CPU_SR cpu_sr=0; OS_ENTER_CRITICAL(); VS_WR_Cmd(SPI_WRAMADDR,Spec_Base+2); bands=VS_RD_Reg(SPI_WRAM); bands=14; VS_WR_Cmd(SPI_WRAMADDR,Spec_Base+4); for (byteIndex=0;byteIndex<14;byteIndex++) { *p++=VS_RD_Reg(SPI_WRAM); } OS_EXIT_CRITICAL(); return bands; }
SD卡第一次用的是1G的SD卡,第二次用的是4G的SD卡,,这就有本质的区别了哟,在用4G的SD卡时还没有大容量SD卡的概念,所以直接用之前的代码去操作,结果就是咋整都整不出来,在网上逛了逛发现这俩所用的协议是不同的。所以之后采用了SD卡的v2.0协议去操作4G的SD卡.以下是我对V2.0协议的一些总结:
现在使用的4G的SD卡,小于或等于2G的卡是属于标准SD卡,而大于2G的卡小于32G的卡是大容量SD卡,也就是SDHC卡。对于SDHC卡的初始化和操作要使用V2.0协议。首先是一个流程图,这个图在官方资料上有:
SPI模式下SD卡部分操作指令
命令 |
参数 |
回应 |
描述 |
CM0(0X00) |
NONE |
R1 |
复位SD卡 |
CMD9(0X09) |
NONE |
R1 |
读取卡特定寄存器 |
CMD10(0X0A) |
NONE |
R1 |
读取卡标志寄存器 |
CMD16(0X10) |
块大小 |
R1 |
设置块的大小(字节数) |
CMD17(0X11) |
地址 |
R1 |
读取一块的数据 |
CMD24(0X18) |
地址 |
R1 |
写入一块的数据 |
CMD41(0X29) |
NONE |
R1 |
开始卡的初始化 |
CMD55(0X37) |
NONE |
R1 |
引用命令的前命令 |
CMD59(0X3B) |
最后一位有效 |
R1 |
设置CRC开启(1)或关闭(0) |
SD卡R1回应格式:
BIT7 |
BIT6 |
BIT5 |
BIT4 |
BIT3 |
BIT2 |
BIT1 |
BIT0 |
0 |
参数错误 |
地址错误 |
连续擦除错误 |
命令CRC错误 |
非法命令 |
擦除复位 |
IDLE状态 |
卡会根据不同的时候处在不同的状态
第一步操作: 复位
SD卡上电后先发送(>74个时钟),因为SD卡有个供电电压上升过程需要大约64个时钟,之后的10个时钟是用来与SD卡同步(参考《例说STM32》)。参考代码:
for(count = 0;count < 15;count++)
SPI_WriteReadByte(0xff); //产生74个以上的脉冲
SD卡默认是SD模式,现在用STM32去操作,切换为SPI模式后更好操作。所以在片选为低时发送CMD0,此时卡进入IDLE状态,因为CMD0回应的命令是R1,根据上面R1的回应格式可以看出我们自需要检查最低位就知道是否处于IDLE状态。参考代码:
do
{
tmp = SD_WriteCommand(CMD0,0,0X95); //发送SD
count++;
}while((tmp != 0x01) && (count < DISPLAY_COUNT));
第二步操作发送CMD8来分辨卡的类型,是V2.0卡还是V1.0卡或MMC卡,还可以检测CMD8响应返回的数据判断是否支持给定的工作电压范围。
根据流程图可以看出。
1.如果SD卡支持当前的电压就会返回R7,并包含CMD8的参数部分,其中包括:Check voltage和check pattern。
2.如果SD卡不支持当前的工作电压则不会返回任何响应信息,继续处在IDLE状态。如果是V1.0x的SD卡也不会有响应。
3.在PLV2.0(physical layer version2.0)下,在首次执行ACMD41之前,必须执行CMD8指令,用以初始化SDHC卡,SDHC卡根据是否接收到CMD8指令来鉴别控制器是否支持PLV2.0协议。使用低电压的控制器也必须在ACMD41命令之前发送CMD8,避免可以工作在两种电压模式下的SD卡因为没有接收到CMD8, 而默认工作在高电压环境下,被误认为是只支持高电压工作模式。
Application Note:
It is recommended to use ‘10101010b’ for the ‘check pattern’.
R7的格式:
从上面可以看到,R7为5个字节,在发送CMD8后,SD卡响应,发送回来的第一个字节就是R1,之后的4个字节中就包含了Check voltage和check pattern。下面是仿真的结果:
判断Check voltage按照如下标准:
根据流程图就可以看出发送CMD8后SD卡的类型基本上分了两类。
ACMD指令的HCS(Host Capacity Support)位如果设定为1的话,表明控制器支持SDHC卡,否则表示不支持。
SD卡初始化和识别过程:
在CMD8命令发送之后的ACMD41指令其功能有所扩展,在参数里多了HCS部分,在响应里面多了CCS(Card Capacity Status)部分。HCS参数会被不响应CMD8命令的SD卡所抛弃。控制器向不响应CMD8的卡发送ACMD41指令时,HCS位应该设置为零0。如果向SDHC卡发送HCS位为0的ACMD41命令,SDHC卡返回的响应,其busy标识位永远为0,代表忙状态。HCS标识位用来表明SD卡是否已经完成初始化,如果未完成,HCS为零,否则为1,如果HCS为0,控制器会重复发送ACMD41指令,SD卡只检查首次接收到的ACMD41指令的HCS位。
按照流程图,现在发送ACMD41可以用来分辨是否为高容量卡。
SD_WriteCommand(CMD41,0x40000000,0X01);//发送ACMD41,流程图写的如果主机支持大容量就将HCS置1.所以发送0x40000000
因为ACMD41为应用型指令,所以前面要加上CMD55一起使用。
SD_WriteCommand(CMD55,0,0x01);
SD_WriteCommand(CMD41,0x40000000,0X01);
一直发送这两条指令,直到卡准备好。处于Ready状态。
所以接收到响应为0之后就可以开始读取OCR信息。并检测bit30位(CCS位),如果此位是1就是高容量卡,如果为0则是标准SD卡。读取OCR的指令是CMD58,其响应是R3:
前面的R1应该为0。
之后再接收4个字节的数据。并判断bit30位。就可以判断是SDV2.0还是SDV2.0HC。初始化完成。
最后要注意的是扇区的地址是不同的。
图片解码方面都是只解码了BMP格式文件。
第一步:判断是否为BMP格式图片
根据BMP格式的编码信息可以得到,只要是BMP格式的图片,前两个字节就是'B'和'M'。大家可以看编码信息,第一个字节和第二个字节就是B和M的ASCii码。
第二步:得出图像数据的起始位置相对于文件开头的偏移量
这一步的目的是什么呢,就是确定真正的图像数据是从第几个字节开始的。
在BMP格式编码信息的前14个字节里就有偏移量这个信息,那么到底是那几个字节是偏移量的信息呢。现在看看编码格式:
BMP的文件信息: 文件开头的14个字节
WORD BMPId;BMP文件标志,其值固定为0x4d42,即“BM”(两个字节)
DWORD FileSize;BMP文件大小,以字节为单位 (4个字节)
WORD Reserved1;BMP文件保留字,必须为0 (两个字节)
WORD Reserved2;BMP文件保留字,必须为0 (两个字节)
DWORD ImageOffset;图像数据的起始位置相对于文件开头的偏移量 (4个字节)
偏移量的信息是在这14个字节数据中的最后4个。
第三步:得出BMP格式图片是多少位和图片的像素
在上面的14个字节后还有40个字节的信息可以给我们分析。我们只需要分析出我们需要的信息。现在看下面的信息有哪些:
Bmpinfoheader 文件信息之后的40个字节
DWORD HeaderSize;BMP图像信息大小(40或12),以字节为单位 (4个字节)
DWORD ImageWidth;BMP图像宽度,以像素为单位 (4个字节)
DWORD ImageHeight;BMP图像高度,以像素为单位 (4个字节)
WORD EquipLevel;目标设备的级别(色彩平面数),固定为1 (2个字节)
WORD BitsPerPixel;每个像素所需要的位数,1,4,8,24 (2个字节)
DWORD EncodeType; 压缩类型,0(不压缩),1(BI_RLE8),2(BI_RLE4) (4个字节)
DWORD ImageSize; BMP位图大小,以字节为单位 (4个字节)
DWORD XPixelPerMeter设备水平分辨率(每米像素数) (4个字节)
DWORD YPixelPerMeter设备垂直分辨率(每米像素数) (4个字节)
DWORD ColorUsed实际使用色彩数目,若为0,则由位数定 (4个字节)
DWORD ColorImportant图像中重要的色彩数目。为0,表示调色板内所有的颜色都是重要的 (4个字节)
第四步:根据不同的位图来用不同的函数求出彩屏需要的RGB显示值
我们知道的偏移量,那么真正要显示的数据就是,从偏移量那里开始后面的字节就是我们图片的显示数据。显示数据的大小就是我们上面所求的BMP位图大小。彩屏液晶上面的每个像素点都是一个16位的数据,这16位的数据包含了该点的RGB的值。那么我们怎么根据图片里的数据来求出RGB的值并转化为一个16位的数据呢!
首先说32位的BMP图片:
32位的图片是一个像素点的信息由4个字节表示。
BYTE rgbBlue; (B)
BYTE rgbGreen; (G)
BYTE rgbRed; (R)
获取歌曲信息,在第一次制作过程中采用的方式是去分析MP3的编码方式然后去读取歌曲名和相关信息。详细的可以查看进程贴。第二次是直接利用znFAT里面的长文件名读取,这样就缩短了很长的时间。
歌词显示方面,在第一次的制作中由于主控速度太慢的原因没有加入歌词显示。在第二次就加入了歌词显示功能,在处理歌词方面采用这样步骤:在播放歌曲前先获取歌词文件中所有的标签点保存下来。在歌曲播放期间就不需要再去判断标签内容了,只需要匹配时间点,那两种音频解码模块都可以读取时间单位为秒。这样处理可以节省时间但是浪费存储空间。
第一次采用的是精简版znFAT,只有两个文件,第二次采用的是完整版的。后者能对大容量SD卡进行操作并且有可裁剪性。相比fatfs来说增加了中文字符编码的转换,所以fatfs相对来说比较小,但是fatfs也可以裁剪,这点不弱于znfat。
两次的文本显示都涉及到中文和英文字符的判断,一旦将中文编码拆散就会导致之后的文字都乱码,第二次利用UCGUI的控件来装文本,还得手动插入字符,这部分挺麻烦也挺好玩。
先整理这么多,代码早已共享,可以去相应帖子查看。有些总结可能不好,欢迎拍砖