基于FPGA设计的音乐播放器

这篇具有很好参考价值的文章主要介绍了基于FPGA设计的音乐播放器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


基于FPGA设计的音乐播放器

本文将介绍如何使用FPGA和PCM5102音频解码模块来制作音乐播放器,从生成PCM格式的音频文件开始,到如何编写I2S总线协议代码,音频数据的储存等。

一、生成PCM音频格式的音乐文件

1.PCM音频文件格式

PCM(Pulse Code Modulation,脉冲编码调制)音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准数字音频数据。PCM音频的几个关键参数如下。

参数 描述
采样率(Sample Rate) 表示一帧音频数据的出现频率,在每一个周期期间内,传输完所有声道的音频信息。常用的有44.1kHz
量化位数(Sample Size) 表示对音频数据的量化位数,即单个通道的位宽。常用的有16bit,24bit,32bit
通道个数(Channels ) 表示音频数据的通道个数,双通道即可完成立体声效果,但双通道不一定是立体声,而立体声一定是多通道
数据符号(Sign) 表示音频数据是否带符号,有符号和无符号数的表示范围不同。例如当位宽为8bit时,有符号的话表示范围为-128 ~ 127,无符号是0 ~ 255

简单介绍如下:假设正弦波形为音频模拟信号,发出的声音为"滴",按照如图所示的采样、量化、编码为32bit有符号的数据,即为单通道PCM音频数据。
基于FPGA设计的音乐播放器
当然我们制作音乐播放器肯定不是只能播放出"滴",所以需要先生成满足需求的音频数据,就选取周杰伦的七里香钢琴曲作为音频源文件。下载WAV格式的音频文件,这里可以使用qq音乐下载MP3格式后,在进行音频转码为WAVE格式,如图所示。
基于FPGA设计的音乐播放器

2.Matlab读取WAV文件

当音频转码完成后,生成的WAV文件可直接使用matlab进行读取,使用函数audioread即可,下面附上完整读取、采样、量化、进制转换代码。

%% 读取wav音频文件,写入mif/coe
clear all ;
clc ;
wav  = audioread('周杰伦 - 七里香(钢琴版).wav') ;
% 低采样
sr = 3 ;
m = floor( max(wav) ) / 3 ;
wav_catch = zeros(m , 2) ;
wav_catch(: , 1) = wav(1:m , 1) ;
wav_catch(: , 2) = wav(1:m , 2) ;
save wav_catch wav_catch ;
% 截取部分时间,同时增大幅度值,位宽为32bit
l = 16384 ;
audio_l = wav_catch(1:l , 1) * 2^31 ;
audio_r = wav_catch(1:l , 2) * 2^31 ;
% 十进制 -> 十六进制
audio_l(find(audio_l<0)) = audio_l(find(audio_l<0)) + 2^32 ;
audio_r(find(audio_r<0)) = audio_r(find(audio_r<0)) + 2^32 ;
audio_l_hex = dec2hex(audio_l) ;
audio_r_hex = dec2hex(audio_r) ;

其中各个模块已经给出了中文注释,由于负数在FPGA中是以补码的形式进行储存,对于其中的十进制转换十六进制有不懂的地方可自行百度。到这里就已经得到了满足PCM格式的音频数据文件,由于数据量过大,如若使用ROM来存储,将会耗费大量的BRAM资源以及底层逻辑资源,甚至造成资源不够的情况。

当然若使用此种方式,需要生成COE或MIF文件可参考MATLAB生成COE或MIF文件代码
在本设计中需要完整的播放整个音乐文件,即使已经将采样率压缩到16KHz,FPGA上的ROM资源依然不够,所以采用SD卡的方式,或是通过串口写入到FLASH上面进行音频文件的储存,该部分内容会在后续完成后上传。

2022/03/22 毕业论文初稿完成,得闲,不愿荒废时光,却又找不到什么实际意义的事情,随便记录一下,留着以后怀念。

二、音频文件储存

1.ROM存储简单音频文件

本来之前想把整首曲子存储在sd卡或是ddr里面,然中间很多事情耽搁了,现在又投身工作了,难以抽身。无意间看到有私信求更,才想起来,那就简单的更完吧。

在FPGA中,只读存储器ROM(read only memory)常用于存放初始数据。ROM中的数据需要先进行初始化,即要先将数据写入到ROM内部的存储单元中,然后系统正常工作时,只能读出其中存储的数据,而不能写入信息,且其中储存的数据掉电不会丢失。由前面章节介绍可知,我们需要在ROM中存入音频数据,格式为PCM音频数据格式,如下图所示。

基于FPGA设计的音乐播放器

为了体现立体声的效果,我们设计传输两个声道的音频信息,选用双声道的数据格式。音频数据都选用正弦波形数据,其播放出来的声音为“滴”,但两个声道传输的正弦波形数据的幅度不同,在播放时两边声音的音量大小不同。设计时先使用Matlab生成幅度变化满足需求的音频数据,并创建内存初始化(mif)文件,将正弦波数据写入。然后使用EDA工具quartus直接调用已经封装好的ROM存储器IP核,如图所示。
我们设定ROM的输出端口位宽为32bit,指定存储器的深度为256,表示本IP核可以存储256个32bit位宽的音频数据。通过载入mif文件为存储器提供存储器初始化数据,并编写仿真文件对该ROM进行测试验证。

基于FPGA设计的音乐播放器

如下图所示为ROM的功能仿真波形图。从图中可知,左右声道中传输的数据不同,故在经过PCM5102解码模块解码后的音频信息中,可以明显的感受到立体声效果,这是由于双耳播放的音频不同导致的。
基于FPGA设计的音乐播放器

2. I2S数据传输协议

PCM5102解码模块是基于I2S传输协议,选择左对齐模式下的I2S传输协议进行代码编写和功能仿真。
首先,I2S总线共拥有三条数据信号线,一条系统时钟线,各信号简要介绍如下:
(1)BCK:串行时钟信号,每一个脉冲周期对应于数字音频文件中的每一位数据,所以也称为位同步时钟信号。在本设计中,声道数为2,音频数据位宽为32位,那么BCK的频率可通过如下公式计算:
基于FPGA设计的音乐播放器

其中f(lrck)为采样频率。
(2)LRCK:声道选择信号,用于切换左右声道的数据,也称为帧同步信号。命令选择线表明了正在被传输的声道,LRCK为低电平表示正在传输的是左声道的数据,LRCK为高电平表示正在传输的是右声道的数据。该信号的频率即是采样频率。
(3)SDIN:串行数据信号,即将音频数据按照串行的方式进行传输,先传输数据的最高位,最低位的位置则是依赖于数据的有效位数,本设计中有效位数是32位,那么不存在无效位,传输前音频数据都应转换为二进制补码的形式。
(4)SCK:系统时钟信号,当处于主模式时,可用于为外部设备提供系统时钟,工作为从模式时,不可用。

本文根据解码模块的系统时钟要求,如下图所示,图片来自pcm5102模块文档说明。我们选择采样时钟为16KHz,系统时钟为4.096MHz,进行时序设计。
基于FPGA设计的音乐播放器
其中基于i2s协议的数据发送代码如下:

module I2S_SEND (
        input           clk     ,   // 50M
        input           rstn    ,
        input   [31:0]  audio_l ,
        input   [31:0]  audio_r ,
        output          flag    ,
        output          bck     ,
        output          lrck    ,
        output          sck     ,
        output          dout
);

//----------------------
reg         [31:0]  r_audio_l ; 
reg         [31:0]  r_audio_r ; 


reg                 SDIN ;

reg                 BCLK ;
reg                 WCLK ;
reg                 SCLK ;
reg         [5:0]   SCLK_cnt ;
reg         [11:0]  BCLK_cnt ;
reg         [15:0]  WCLK_cnt ;


reg         [1:0]   pos_BCLK ;
reg         [1:0]   neg_BCLK ;
reg         [1:0]   pos_WCLK ;
reg         [1:0]   neg_WCLK ;

//----------------------
assign      bck = BCLK ;
assign      lrck = WCLK ;
assign      sck = SCLK ;
assign      dout = SDIN ;

//----------------------
assign      flag = (WCLK_cnt == 16'd3070) ? 1 : 0 ;

//- BCLK gen
always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            BCLK_cnt <= 12'd0 ;
        end
    else if(BCLK_cnt == 12'd47)
        begin
            BCLK_cnt <= 12'd0 ;
        end
    else
        begin
            BCLK_cnt <= BCLK_cnt + 1'b1 ;
        end
end

always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            BCLK <= 1'b0 ;
        end
    else if(BCLK_cnt < 12'd24)
        begin
            BCLK <= 1'b0 ;
        end
    else
        begin
            BCLK <= 1'b1 ;
        end
end
//- WCLK gen
always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            WCLK_cnt <= 16'd0 ;
        end
    else if(WCLK_cnt == 16'd3071)
        begin
            WCLK_cnt <= 16'd0 ;
        end
    else
        begin
            WCLK_cnt <= WCLK_cnt + 1'b1 ;
        end
end

always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            WCLK <= 1'b1 ;
        end
    else if(WCLK_cnt < 16'd1536)
        begin
            WCLK <= 1'b1 ;
        end
    else
        begin
            WCLK <= 1'b0 ;
        end
end
//- SCLK gen
always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            SCLK_cnt <= 6'd0 ;
        end
    else if(SCLK_cnt == 6'd11)
        begin
            SCLK_cnt <= 6'd0 ;
        end
    else
        begin
            SCLK_cnt <= SCLK_cnt + 1'b1 ;
        end
end

always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            SCLK <= 1'b0 ;
        end
    else if(SCLK_cnt < 6'd6)
        begin
            SCLK <= 1'b0 ;
        end
    else
        begin
            SCLK <= 1'b1 ;
        end
end
//-
always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            r_audio_l <= 32'd0 ;
            r_audio_r <= 32'd0 ;
        end
    else if(WCLK_cnt == 16'd3071)
        begin
            r_audio_l <= audio_l ;
            r_audio_r <= audio_r ;
        end
    else if(BCLK_cnt == 12'd0)
        begin
            r_audio_l <= {r_audio_l[30:0] , r_audio_l[31]} ;
            r_audio_r <= {r_audio_r[30:0] , r_audio_r[31]} ;
        end
end
//- DOUT gen
always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            SDIN <= 1'b0 ;
        end
    else if(WCLK_cnt < 16'd1536)
        begin
            if(BCLK_cnt == 12'd0)
                begin
                    SDIN <= r_audio_l[31] ;
                end
            else 
                begin
                    SDIN <= SDIN ;
                end
        end
    else if(WCLK_cnt > 16'd1535)
        begin
            if(BCLK_cnt == 12'd0)
                begin
                    SDIN <= r_audio_r[31] ;
                end
            else 
                begin
                    SDIN <= SDIN ;
                end
        end
end
endmodule

以下是从ROM中读取数据传给I2S的代码:

module MP3_TEST(
        input           clk     ,   // 50M
        input           rstn    ,
        input           enable  ,
        output  [31:0]  audio_l ,
        output  [31:0]  audio_r 
    );

// -
wire        [31:0]      dout1 ;
wire        [31:0]      dout2 ;
reg         [4:0]       switch_state ;
reg                     switch_flag ;
reg         [7:0]       addr ;
reg         [23:0]      cnt ;
//- binary twos-complement
// assign  audio_l = (dout1[31]) ? {dout1[31] , ~dout1[30:0] + 1} :  dout1 ;
assign  audio_l = dout1 ;
assign  audio_r = dout2 ;

//-
blk_mem_gen1 u1_blk_mem_gen1 (
        .clka               ( clk  ),          
        .rsta               ( ~rstn ),          
        .ena                ( 1'b1 ),           
        .addra              ( addr ),        
        .douta              ( dout1 ),          
        .rsta_busy          (  )  
);
//-
blk_mem_gen2 u2_blk_mem_gen2 (
        .clka               ( clk  ),          
        .rsta               ( ~rstn ),          
        .ena                ( 1'b1 ),           
        .addra              ( addr ),        
        .douta              ( dout2 ),          
        .rsta_busy          (  )  
);
//- switch frequence 
always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            cnt <= 24'd0 ;
            switch_flag <= 1'b0 ;
        end
    else if(cnt == 24'd6_144_000)
        begin
            cnt <= 24'd0 ;
            switch_flag <= 1'b1 ;
        end
    else 
        begin
            cnt <= cnt + 1'b1 ;
            switch_flag <= 1'b0 ;
        end
end
//- switch
always @(posedge clk or negedge rstn) 
begin
    if(rstn == 1'b0)
        begin
            addr <= 8'd0 ;
            switch_state <= 5'd0 ;
        end
    else begin
        case(switch_state)
            0:begin addr <= 8'd0 ; switch_state <= 5'd1; end
            1:begin
                if(switch_flag)
                    begin addr <= 8'd16 ; switch_state <= 5'd2 ; end
                else if(addr == 8'd15)
                    addr <= 8'd0 ; 
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            2:begin
                if(switch_flag)
                    begin addr <= 8'd32 ;switch_state <= 5'd3 ; end
                else if(addr == 8'd31)
                    addr <= 8'd16 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            3:begin
                if(switch_flag)
                    begin addr <= 8'd48 ;switch_state <= 5'd4 ; end
                else if(addr == 8'd47)
                    addr <= 8'd32 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            4:begin
                if(switch_flag)
                    begin addr <= 8'd64 ; switch_state <= 5'd5 ; end
                else if(addr == 8'd63)
                    addr <= 8'd48 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            5:begin
                if(switch_flag)
                    begin addr <= 8'd80 ; switch_state <= 5'd6 ; end
                else if(addr == 8'd79)
                    addr <= 8'd64 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            6:begin
                if(switch_flag)
                    begin addr <= 8'd96 ; switch_state <= 5'd7 ; end
                else if(addr == 8'd95)
                    addr <= 8'd80 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            7:begin
                if(switch_flag)
                    begin addr <= 8'd112 ; switch_state <= 5'd8 ; end
                else if(addr == 8'd111)
                    addr <= 8'd96 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            8:begin
                if(switch_flag)
                    begin addr <= 8'd128 ; switch_state <= 5'd9 ; end
                else if(addr == 8'd127)
                    addr <= 8'd112 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end

            9:begin
                if(switch_flag)
                    begin addr <= 8'd144 ; switch_state <= 5'd10 ; end
                else if(addr == 8'd143)
                    addr <= 8'd128 ; 
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            10:begin
                if(switch_flag)
                    begin addr <= 8'd160 ;switch_state <= 5'd11 ; end
                else if(addr == 8'd159)
                    addr <= 8'd144 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            11:begin
                if(switch_flag)
                    begin addr <= 8'd176 ;switch_state <= 5'd12 ; end
                else if(addr == 8'd175)
                    addr <= 8'd160 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            12:begin
                if(switch_flag)
                    begin addr <= 8'd192 ; switch_state <= 5'd13 ; end
                else if(addr == 8'd191)
                    addr <= 8'd176 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            13:begin
                if(switch_flag)
                    begin addr <= 8'd208 ; switch_state <= 5'd14 ; end
                else if(addr == 8'd207)
                    addr <= 8'd192 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            14:begin
                if(switch_flag)
                    begin addr <= 8'd224 ; switch_state <= 5'd15 ; end
                else if(addr == 8'd223)
                    addr <= 8'd208 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            15:begin
                if(switch_flag)
                    begin addr <= 8'd240 ; switch_state <= 5'd16 ; end
                else if(addr == 8'd239)
                    addr <= 8'd224 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            16:begin
                if(switch_flag)
                    begin addr <= 8'd0 ; switch_state <= 5'd1 ; end
                else if(addr == 8'd255)
                    addr <= 8'd240 ;
                else if(enable)
                    addr <= addr + 1'b1 ;
            end
            default:begin addr <= 8'd0 ; switch_state <= 5'd0; end
        endcase
    end
end
endmodule

三、PCM5102解码模块介绍

pcm5102的芯片手册和原理图,网上应该很容易搜到。在这里我也创建了一份网盘分享文件,里面有相关的文档和完整的工程文件,工程是vivado2018.3版本下开发的,可供有相关兴趣的爱好者参考。

仓促结尾,实属无奈。

链接:https://pan.baidu.com/s/1cu_7JYVaHr0OJrM5-iiuqg
提取码:k87f文章来源地址https://www.toymoban.com/news/detail-422835.html

到了这里,关于基于FPGA设计的音乐播放器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于微信小程序的音乐播放器设计

    目 录 1绪论 1 1.1选题背景及意义 1 1.2发展现状 1 1.2.1什么是微信小程序 1 1.2.2小程序市场的现状 4 1.3研究主要内容 4 2系统技术 5 2.1 Java语言 5 2.2 SSM框架 6 2.3 Vue.js框架 7 2.4 Eclipse开发工具 8 2.5数据库 9 2.6系统开发环境概述 10 3系统分析 12 3.1 功能需求(用例图分析) 12 3.1.1 网络音

    2024年02月11日
    浏览(28)
  • FPGA开发:音乐播放器

    相关阅读  FPGA开发专栏 https://blog.csdn.net/weixin_45791458/category_12388695.html?spm=1001.2014.3001.5482         FPGA开发板上的蜂鸣器可以用来播放音乐,只需要控制蜂鸣器信号的方波频率、占空比和持续时间即可。         简谱上的4/4表示该简谱以4分音符为一拍,每小节4拍,简谱上应该

    2024年02月14日
    浏览(29)
  • 硬件课程设计:基于STM32的多功能播放器之MP3音乐播放

    3.1.1 设计思路         音乐播放是手机等电子产品最常见的功能,也是最具娱乐性的功能,在该模块中,我先从SD卡中读取已存放的MP3音乐文件,然后将读取的字节流依次送人VS1053中进行解码和播放,同时从VS1053相关的寄存器中读取音乐播放时间,在LCD上实时显示歌曲信息。

    2023年04月08日
    浏览(27)
  • 基于微信小程序音乐播放器的设计与实现毕业设计源码271156

    Springboot音乐播放小程序的设计与实现 摘 要 本文设计了一种基于微信小程序的音乐播放器,系统为人们提供了方便快捷、即用即搜的音乐搜索播放服务,包括音乐资讯、音乐库推荐、交流论坛、注册登录、最近播放列表功能等,用户不仅能够方便快捷地查看资讯、还能搜索

    2023年04月24日
    浏览(38)
  • 案例115:基于微信小程序的音乐播放器的设计与实现

    文末获取源码 开发语言:Java 框架:SSM JDK版本:JDK1.8 数据库:mysql 5.7 开发软件:eclipse/myeclipse/idea Maven包:Maven3.5.4 小程序框架:uniapp 小程序开发软件:HBuilder X 小程序运行软件:微信开发者 目录 目录 前言 系统展示 系统首页界面的设计实现 用户注册功能的设计实现 用户登

    2024年02月02日
    浏览(31)
  • 基于Vue的音乐播放器的设计与实现(论文+源码)_kaic

    摘  要 随着前端技术的发展和迭代,前端开发工程师在java的MVC框架基础上,推出了前端的MVC,MVP,MVVM等架构模式,利用目前主流的前端开发技术,如React、AngularJS等,我们可以很轻松的构建起一个不需要服务器端渲染就可以展示的WebApp,同时这类框架也都提供了前端路由功

    2024年02月07日
    浏览(42)
  • 基于微信小程序的音乐播放器设计与实现(源码+lw+部署文档+讲解等)

    💗 博主介绍 :✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 👇🏻 精彩专栏 推荐订阅 👇🏻 2023-2024年最值得选的微信小程序毕业设

    2024年02月03日
    浏览(25)
  • 基于微信小程序的音乐播放器的设计与开发(源码+lw+部署文档+讲解等)

    💗 博主介绍 :✌新人博主,工作经验两年+、专注于Java、小程序技术领域和毕业项目实战✌💗 🌟文末获取源码+数据库🌟 感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多的人 随着移动互联网的快速发展,音

    2024年02月04日
    浏览(26)
  • FPGA中Verilog的单首音乐播放器代码,简洁易懂

    利用数控分频器设计硬件乐曲演奏电路,利用蜂鸣器播放《我和我的祖国》 分频器模块: module div(iclk_50,rst,addr,clk_4); input iclk_50; input rst; output [8:0]addr; output clk_4; reg clk_4; reg [8:0]addr; reg [31:0]count_4; always@(posedge iclk_50 or negedge rst) begin     if(!rst)         begin          clk_4=1\\\'b

    2024年02月08日
    浏览(28)
  • 在Winform(C++/CLR)平台设计的(本地&在线)音乐播放器(基于WMP(Windows Media Player)控件实现)

    首先,祝贺阿根廷获得2022世界杯冠军! Winform作为一个比较老的平台,应用其实越来越少了,而即使设计Winform程序,多数人也会选择C#,而不是C++。但是题主在学校学习一门课程被迫使用了Winform/C++,并完成了课程作业,在此分享以下自己的作业,也当作学习纪录。在完成这

    2024年02月09日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包