本文將給出通過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 DATA00x40#define DATA0_RO 0x60#define DIRM_00x204#define OEN_00x208void 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_0DrvGPIO_ClrBit(MIO_BASE + DATA0,12)#define VS_XRESET_1DrvGPIO_SetBit(MIO_BASE + DATA0,12)#define VS_DREQ DrvGPIO_GetBit(MIO_BASE + DATA0_RO,11)#define VS_XDCS_0DrvGPIO_ClrBit(MIO_BASE + DATA0,10)#define VS_XDCS_1DrvGPIO_SetBit(MIO_BASE + DATA0,10)#define VS_XCS_0DrvGPIO_ClrBit(MIO_BASE + DATA0,13)#define VS_XCS_1DrvGPIO_SetBit(MIO_BASE + DATA0,13)#define SPI_MOSI_0DrvGPIO_ClrBit(MIO_BASE + DATA0,0)#define SPI_MOSI_1DrvGPIO_SetBit(MIO_BASE + DATA0,0)#define SPI_SCL_0DrvGPIO_ClrBit(MIO_BASE + DATA0,9)#define SPI_SCL_1DrvGPIO_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為1VS_Write_Reg(0x03,0x98,0x00);//時鐘設定,向3號寄存器寫入0x9800 SC_MULT 為4 SC_ADD 為3 SC_FREQ為0VS_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為1while(!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 / 128SPI_WriteByte(0); //比如x=126 (0b 011 11110) FsIdx=011=3 Fs=22050Hz S=11110=30 F=22050Hz X 30 /128 =5168 HzSPI_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完成,操作十分簡單集中。