ZED Board从入门到精通系列(七)——Vivado+SDK实现MP3播放

本文将给出通过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完成,操作十分简单集中。

时间: 2024-08-31 20:32:34

ZED Board从入门到精通系列(七)——Vivado+SDK实现MP3播放的相关文章

ZED Board从入门到精通系列(八)——Vivado HLS实现矩阵相乘

终于到了HLS部分.HLS是High Level Synthesis的缩写,是一种可以将高级程序设计语言C,C++,SystemC综合为RTL代码的工具.   生产力的发展推动了设计模式.在电子技术初级阶段,人们关注的是RLC电路,通过建立微分方程求解电路响应.门级电路是对RLC的初步封装,人们进而采用布尔代数.卡诺图进行电路设计与分析.之后随着集成电路进一步发展,门电路可以集成为寄存器.触发器.ROM等宏单元,设计工具也变得更为高度模块化.算法级别的电路设计,则一直没有特别好的工具,直到出现了

ZED Board从入门到精通(三):从传统ARM开发到PS开发的转变

ARM已经在国内流行得一塌糊涂,各类教程.开发板(S3C2440,6410)层出不穷,归结下来,传统ARM开发包括以下几个步骤: (1)硬件电路板设计(对于Zedboard,相当于设计逻辑电路,PL工程师负责): (2)基本模块裸机代码测试(UART,DDR2,其他外设): (3)移植操作系统(如Linux,uCLinux,uCOS等): (4)编写相应操作系统的驱动程序(可从(2)中移植过来): (5)编写应用程序(或移植已有的应用程序).界面设计(Qt): 一个有ARM开发经验的工程师,接触

ZED Board从入门到精通(一):ZYNQ结构简介

ZYNQ-7000是第一代可扩展处理平台(Extensible Processing Platform,EPP),同时具有软件可编程.硬件可编程.IO可编程的特性,为此Xilinx强调了"All Programmable的"概念.下面对其做一简要介绍,便于读者建立初步框架. ZYNQ芯片内包含一个丰富特性的基于双核ARM Cortex-A9的处理子系统(Processing System,PS)和Xilinx 28nm可编程逻辑(Programmable Logic,PL).PS除了核

ZED Board从入门到精通(二):AXI简介

距离上次发帖时间有点长了,其实这段时间一直在思考. 市面上已经有专门讲ZYNQ的书籍了,我看过的有这两本. 这两本书怎么说呢,我觉得第二本更像是官方文档的堆砌吧(不喜勿喷),洋洋洒洒近600页,真正我想看的内容却少之又少.第一本书更适合入门(其实相当于傻瓜教程,你拿到书,拿到板子之后马上就能开始做实验),但语法错误.名词错误.软件版本不同造成的错误有很多,附加的光盘第一个实验内容就有错!有时辛辛苦苦搭建起来的环境,因为书上有错造成实验失败是很痛苦的,浪费感情. 建议先读一读第一本,对ZYNQ建立

MySoft.Data从入门到精通系列(五)【数据更新】

前一章讲了如何利用MySoft.Data进行数据的插入,利用DbSession可以实现各种数据增.删.改.查等各种复杂的处理,本章着重讲解一下数据的更新: 数据更新在日常开发中占据中非常重要的地位,尽次于查询.下面就讲解一下如何利用DbSession来进行数据的更新. 继续引用前面的DbSession配置,如下: /// <summary> /// 数据库访问类 /// </summary> public static class DataAccess { /// <summ

MSDN Webcast:ASP.NET MVC2程序开发入门到精通系列课程

课程讲师:苏鹏 MSDN特邀讲师北京工业大学软件工程硕士,微软最有价值专家(ASP.NET MVP),微软MSDN特约讲师.曾于微软亚洲工程院MSN组工作,现任中国网通四分公司技术支持与项目部开发经理.具有多年电信系统与OA系统实施经验. 课程下载: ASP.NET MVC2程序开发入门到精通系列课程(1):MVC架构概述 ASP.NET MVC2程序开发入门到精通系列课程(2):MVC范例分享 ASP.NET MVC2程序开发入门到精通系列课程(3):MVC中的View实现技巧(上) ASP.

一起谈.NET技术,MSDN Webcast:ASP.NET MVC2程序开发入门到精通系列课程

课程讲师:苏鹏 MSDN特邀讲师北京工业大学软件工程硕士,微软最有价值专家(ASP.NET MVP),微软MSDN特约讲师.曾于微软亚洲工程院MSN组工作,现任中国网通四分公司技术支持与项目部开发经理.具有多年电信系统与OA系统实施经验. 课程下载: ASP.NET MVC2程序开发入门到精通系列课程(1):MVC架构概述 ASP.NET MVC2程序开发入门到精通系列课程(2):MVC范例分享 ASP.NET MVC2程序开发入门到精通系列课程(3):MVC中的View实现技巧(上) ASP.

Hibernate从入门到精通(七)多对一单向关联映射

上次的博文Hibernate从入门到精通(六)一对一双向关联映射中我们介绍了一下一对一双向关联映射,本 次博文我们讲解一下多对一关联映射 多对一单向关联映射 多对一关联映射与一对一关联映射类 似,只是在多对一的指向端可以存在多个对象,在指向端加载的时候,同时加载被指向端. 多对一和 一对一的异同 对比一对一单向关联映射和多对一单向关联映射,两者的相同之处在于在指向端被加载 的时候,指向端会被一起加载进来,这一点从如下类的结构图和代码中看出.(以下只给出多对一相关图示和 代码,一对一图示和代码参考

Docker从入门到精通系列(1)---第一个web应用

本文为minimicall原创文章,转载需注明出处:http://blog.csdn.net/minimicall 在继<阿里云部署Docker>之后,我决定系统的出一系列<Docker从入门到精通>的深度记录文章,这源于有一天图灵出版社的一个编辑联系我,问我有没有兴趣写Docker方面的书籍进行出版. 本文的目标是建立一个Docker web app.这样,你就可以直观的感受,docker是如何部署一个web应用. 首先,我们建立一个空目录来存放我们需要的文件. 我们建立的是一个