本文将给出通过Vivado IDE开发Zynq平台上PS裸机应用程序的流程。通过与本系列博客(三)对比,读者将看到Vivado开发更高效、快捷。
MP3我们都听过,现在我们可以用ZED-Board来听。板子上有音频芯片ADAU1761,可以实现录音、放音,但不具有MP3解码功能。Zynq 双核ARM9做MP3软件解码应该是可以实现的,但是博主本人有一颗VS1003,可以实现MP3硬件解码,软件将得以简化,对MP3解码原理感兴趣的可以深入研究如何利用CortexA9+ADAU1761实现MP3播放。电路图如下:
利用Zynq MIO实现VS1003控制,这样只和PS有关,PL完全可以丢弃。在本节基础上,读者可以尝试将SPI模块移到PL上实现,这样可以降低PS部分IO读写频率,提高CPU利用率。实物连接图如下:
Zynq板子外接用排母,为了使用杜邦线,需要一个双公排针,可以用普通单排2.54mm排针压制而成
下面介绍软件开发流程。建立Vivado工程,命名为MP3Player,过程遵循上节Vivado建立工程步骤,略。
进入IDE后,点击左侧流程管理器中的IPI Integrator下的Create Block Design。 这个工具是2013.1版本后才出现的,将取代XPS完成系统集成。
在编辑区右键,选择Add IP...,名称保持默认design_1.bd
搜索框中输入zynq,双击第一个,添加IP到电路图中。
添加完成后,自动进行布线连接,点下图中圆圈区域 Run Block Automation。
等待完成,结果如下图所示。
可以看到,DDR和固定IO自动进行了连接。这是因为我们建立工程时选择了ZedBoard DVK,这样就能按照板子描述自动连接引脚到相应外设。
另外看到,默认状态下使能了M_AXI_GP0,可以将PL部分带AXI从接口的IP连接到PS进行控制。本节不需要,所以必须禁用,否则验证设计时会报错。双击方块,见下图
看到了熟悉又陌生的画面,有些像XPS中Zynq视图,但精简了很多。单击左侧“PS-PL Configuration",界面如下:
将AXI GP0接口后的勾取消选择,确认,回到IPI。
验证设计,在空白处右键,点击Validate Design。无误,点确认即可。
在上图位置点Generate Block Design,确认。
在Sources窗口中找到design_1,右键选择生成顶层HDL包装。确认。
直接点左侧流程中的Generate Bitstream,一步到位。完成比特流大约需要5~8min。
完成后,先Open Implementated Design,再导出到SDK。
完成后,先Open Implementated Design,再导出到SDK。如果没有做这一步,上图中第二项会变成灰色。
后面就是SDK开发了,和本系列教程(三)中相同。建立Application工程,C工程,模板helloworld。将代码改为下面:
#include <stdio.h> #include "platform.h" #define MIO_BASE 0xE000A000 #define DATA0 0x40 #define DATA0_RO 0x60 #define DIRM_0 0x204 #define OEN_0 0x208 void delay(unsigned int t) { unsigned int i,j; for(j=0;j<t;j++) { for(i=0;i<600;i++); } } /*---------------------------------------------------------------------------------------------------------*/ /* MAIN function */ /*---------------------------------------------------------------------------------------------------------*/ #define VS_XRESET_0 DrvGPIO_ClrBit(MIO_BASE + DATA0,12) #define VS_XRESET_1 DrvGPIO_SetBit(MIO_BASE + DATA0,12) #define VS_DREQ DrvGPIO_GetBit(MIO_BASE + DATA0_RO,11) #define VS_XDCS_0 DrvGPIO_ClrBit(MIO_BASE + DATA0,10) #define VS_XDCS_1 DrvGPIO_SetBit(MIO_BASE + DATA0,10) #define VS_XCS_0 DrvGPIO_ClrBit(MIO_BASE + DATA0,13) #define VS_XCS_1 DrvGPIO_SetBit(MIO_BASE + DATA0,13) #define SPI_MOSI_0 DrvGPIO_ClrBit(MIO_BASE + DATA0,0) #define SPI_MOSI_1 DrvGPIO_SetBit(MIO_BASE + DATA0,0) #define SPI_SCL_0 DrvGPIO_ClrBit(MIO_BASE + DATA0,9) #define SPI_SCL_1 DrvGPIO_SetBit(MIO_BASE + DATA0,9) void DrvGPIO_ClrBit(volatile unsigned int * p,int idx); void DrvGPIO_SetBit(volatile unsigned int * p,int idx); unsigned char DrvGPIO_GetBit(volatile unsigned int * p,int idx); void init_vs1003(void); void VS_Reset(void); //VS1003软复位及初始化 void VS_Write_Reg(unsigned char addr,unsigned char hdat,unsigned char ldat); //向VS1003的功能寄存器写入一个字 unsigned int VS_Read_Reg(unsigned char addr); //从VS1003的功能寄存器读取一个字 void VS_Send_Dat(unsigned char dat); //向VS1003发送音频数据 void VS_Flush_Buffer(void); //清空VS1003的数据缓冲区 void VS_sin_test(unsigned char x); //正弦测试 void LoadPatch(void); //为VS1003打补丁 void SPI_WriteByte(unsigned char x); #include "mp3.h" void print(char *str); int main() { init_platform(); print("Hello World\n\r"); unsigned int i; init_vs1003(); VS_Reset(); //VS1003复位初始化 VS_sin_test(200); //正弦测试,可以听到一声滴 VS_Flush_Buffer(); for(i = 0;i<sizeof(mp3_table);i++) { VS_Send_Dat(mp3_table[i]); } while(1) { DrvGPIO_ClrBit(MIO_BASE + DATA0,7); delay(40000); DrvGPIO_SetBit(MIO_BASE + DATA0,7); delay(40000); } return 0; } void DrvGPIO_ClrBit(volatile unsigned int * p,int idx) { (*p) &= ~(1<<idx); } void DrvGPIO_SetBit(volatile unsigned int * p,int idx) { (*p) |= (1<<idx); } unsigned char DrvGPIO_GetBit(volatile unsigned int * p,int idx) { return (((*p)&(1<<idx))>>idx); } void init_vs1003(void) { DrvGPIO_SetBit(MIO_BASE + OEN_0,7); DrvGPIO_SetBit(MIO_BASE + DIRM_0,7); DrvGPIO_SetBit(MIO_BASE + OEN_0,0); DrvGPIO_SetBit(MIO_BASE + DIRM_0,0); DrvGPIO_SetBit(MIO_BASE + OEN_0,9); DrvGPIO_SetBit(MIO_BASE + DIRM_0,9); DrvGPIO_SetBit(MIO_BASE + OEN_0,10); DrvGPIO_SetBit(MIO_BASE + DIRM_0,10); DrvGPIO_SetBit(MIO_BASE + OEN_0,12); DrvGPIO_SetBit(MIO_BASE + DIRM_0,12); DrvGPIO_SetBit(MIO_BASE + OEN_0,13); DrvGPIO_SetBit(MIO_BASE + DIRM_0,13); } void SPI_WriteByte(unsigned char x) { unsigned char i=0; for(i=0;i<8;i++) { if(x&0x80) { SPI_MOSI_1; } else { SPI_MOSI_0; } SPI_SCL_0; SPI_SCL_1; x<<=1; } } /****************************************************************** - 功能描述:向VS1003的功能寄存器中写入数据(一个字,即两个字节) - 隶属模块:VS1003B模块 - 函数属性:外部,用户可调用 - 参数说明:addr是功能寄存器的地址 hdat是要写入的高字节 ldat是要写入的低字节 - 返回说明:无返回 ******************************************************************/ void VS_Write_Reg(unsigned char addr,unsigned char hdat,unsigned char ldat) { 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软复位及初始化(设置时钟频率及音量) - 隶属模块:VS1003B模块 - 函数属性:外部,用户可调用 - 参数说明:无 - 返回说明:无 ******************************************************************/ void VS_Reset(void) { VS_XRESET_1; delay(100); VS_XRESET_0; delay(100); VS_XRESET_1; //硬件复位,XRESET低电平有效 delay(100); VS_Write_Reg(0x00,0x08,0x04);//软件复位,向0号寄存器写入0x0804 SM_SDINEW为1 SM_RESET为1 VS_Write_Reg(0x03,0x98,0x00);//时钟设置,向3号寄存器写入0x9800 SC_MULT 为4 SC_ADD 为3 SC_FREQ为0 VS_Write_Reg(0x0b,0x00,0x00);//音量设置,左右声道均最大音量 VS_XDCS_0; //打开数据片选,注意此时XCS(片选)为高电平,SDI有效 SPI_WriteByte(0); //写入数据,这里写入4个0,是无关数据,用来启动数据传输 SPI_WriteByte(0); SPI_WriteByte(0); SPI_WriteByte(0); VS_XDCS_1; //关闭数据片选,SDI无效 } /****************************************************************** - 功能描述:向VS1003写入一个字节的音频数据(即用于播放的数据) 注:调用前先将VS_XDCS置为0,打开数据片选 - 隶属模块:VS1003B模块 - 函数属性:外部,用户可调用 - 参数说明:dat是要写入的字节 - 返回说明:无 ******************************************************************/ void VS_Send_Dat(unsigned char dat) { VS_XDCS_0; //打开SDI,此时可以向VS1003写入音频数据 while(!VS_DREQ); //VS1003的DREQ为高才能写入数据 SPI_WriteByte(dat);//通过SPI向VS1003写入一个字节的音频数据 VS_XDCS_1; //关闭SDI } /****************************************************************** - 功能描述:向VS1003写入2048个0,用于清空VS1003的数据缓冲区 注:在播放完一个完整的音频(如一首完整的MP3)后,调用 此函数,清空VS1003数据缓冲区,为下面的音频数据(如下 一首MP3)作准备。 - 隶属模块:VS1003B模块 - 函数属性:外部,用户可调用 - 参数说明:无 - 返回说明:无 ******************************************************************/ void VS_Flush_Buffer(void) { unsigned int i; VS_XDCS_0; //打开数据片选,即开启SDI传输 for(i=0;i<2048;i++) { VS_Send_Dat(0); } VS_XDCS_1; //关闭数据片选 } /****************************************************************** - 功能描述:正弦测试,这是测试VS1003芯片是否正常的有效手段!! - 隶属模块:VS1003B模块 - 函数属性:外部,用户可调用 - 参数说明:x决定了正弦测试中产生的正弦波的频率,直接影响听到的 声音的频率 - 返回说明:无 ******************************************************************/ void VS_sin_test(unsigned char x) { VS_Write_Reg(0x00,0x08,0x20);//启动测试,向0号寄存器写入0x0820 SM_SDINEW为1 SM_TEST为1 while(!VS_DREQ); //等待DREQ变为高电平 VS_XDCS_0; //打开数据片选 SDI有效 SPI_WriteByte(0x53);//写入以下8个字节,进入正弦测试 SPI_WriteByte(0xef); SPI_WriteByte(0x6e); SPI_WriteByte(x); //参数x用来调整正弦测试中正弦波的频率 FsIdx (b7~b5):采样率表索引 S (b4~b0):正弦波的跃速 频率F=Fs X S / 128 SPI_WriteByte(0); //比如x=126 (0b 011 11110) FsIdx=011=3 Fs=22050Hz S=11110=30 F=22050Hz X 30 /128 =5168 Hz SPI_WriteByte(0); SPI_WriteByte(0); SPI_WriteByte(0); delay(6000); //这里延时一段时间,为了听到“正弦音” SPI_WriteByte(0x45);//写入以下8个字节,退出正弦测试 SPI_WriteByte(0x78); SPI_WriteByte(0x69); SPI_WriteByte(0x74); SPI_WriteByte(0); SPI_WriteByte(0); SPI_WriteByte(0); SPI_WriteByte(0); VS_XDCS_1; //关闭数据片选 ,SDI无效 }
音频文件需要转换为C头文件,可以用matlab实现:
clear; clc; close all; f = fopen('222.mp3','rb'); a = fread(f,'uint8'); fclose(f); fb = fopen('D:\Tutor_My\MP3Player\MP3Player.sdk\SDK\SDK_Export\mp3\src\mp3.h','w'); fprintf(fb,'const unsigned char mp3_table[] = {\r\n'); fprintf(fb,'0x%02x,\r\n',a(1:end)); fprintf(fb,'\r\n};'); fclose(fb);
下载比特流,运行。通过耳机可以听到你转换的mp3。
完成上述工程,只需要10min,操作完全由Vivado+SDK完成,操作十分简单集中。