OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 7762|回复: 0

[XILINX] 【正点原子FPGA连载】第三十五章音乐播放器实验--摘自【正点原子】超越者之FPGA开发指南

[复制链接]

1118

主题

1129

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4671
金钱
4671
注册时间
2019-5-8
在线时间
1224 小时
发表于 2021-1-21 16:40:58 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2021-1-21 16:40 编辑

1)实验平台:正点原子超越者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=631660290421
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-chaoyuezhe.html
4)正点原子FPGA交流群:905624739点击加入:
5)关注正点原子公众号,获取最新资料更新
100846rel79a9p4uelap24.jpg
100846f1ce1fg14zbg0va4.png
第三十五章音乐播放器实验


在 “音频环回实验”中,我们成功地用WM8960实现了音频环回,将WM8960输入的音频数据通过WM8960输出。本章我们将使用FPGA实现从SD卡读取音乐并使用WM8960播放音乐的音乐播放器功能。本章包括以下几个部分:
3535.1简介
35.2实验任务
35.3硬件设计
35.4程序设计
35.5下载验证

35.1简介
WM8960作为带扬声器驱动的立体声多媒体数字信号编译码器,可以结合FPGA实现音乐播放器的功能。本次实验,我们结合SD卡,把音乐文件存入SD卡中,FPGA从SD卡中读取音乐文件的数据传送给WM8960进行播放,就可实现音乐播放器的功能。
我们在“音频环回实验”中对WM8960这块芯片作了详细的介绍,所以在本次实验就不再对WM8960作重复介绍了。如果大家对这部分内容不是很熟悉的话,请参考“音频环回实验”中的WM8960简介部分。这里我们简单介绍一下音频方面的知识。
人的说话频率基本上为300Hz-3400Hz,但是人耳朵听觉频率基本上为20Hz-20000Hz。由于人发出的声音信号为连续的模拟信号,电子设备中处理的为数字信号,所以需要对声音进行采样、量化、编码的数字化处理。处理的第一步为采样,即模数转换,将连续信号转换为离散信号。根据奈奎斯特(Nyquist)采样定理,用不低于两倍信号的频率进行采样就能还原该信号。所以,对于声音信号而言,要想对离散信号进行还原,必须将采样频率定为40KHz以上。在实际应用中,一般定为44.1KHz。可以理解为在1秒钟之内对声音波形采样44100次。原则上采样率越高,声音的质量越好,采样频率一般分为22.05KHz、44.1KHz、48KHz三个等级。22.05KHz只能达到FM广播的声音品质,44.1KHz则是理论上的CD音质界限,48KHz则已达到DVD音质了,WM8960的采样率就是48kHz。
信号经采样后,进行量化、编码。量化位数代表用多少bit位表示采样点的值,常用的有8位(低品质)、12位、16位(高品质)等,16bit是最常见的采样精度。WM8960支持的位数有16位、20位、24位、32位,我们采用的是16位。编码在这里指信源编码,即数据压缩,对于音乐而言,有无压缩的音乐格式如WAV和有压缩的音乐格式如MP3。
无压缩的音乐格式WAV文件是波形文件,是微软公司推出的一种音频储存格式,主要用于保存Windows平台下的音频源。WAV文件储存的是声音波形的二进制数据,由于没有经过压缩,使得WAV波形声音文件的体积很大。WAV文件占用的空间大小计算公式是[(采样频率×量化位数×声道数)÷8]×时间(秒),单位是字节(Byte)。理论上,采样频率和量化位数越高越好,但是所需的磁盘空间就更大。通用的WAV格式(即CD音质的WAV)是44100Hz的采样频率,16Bit的量化位数,双声道,这样的WAV声音文件储存一分钟的音乐需要10.34MB,占用空间大,但无需解码就可直接播放。
作为数字音乐文件格式的标准,WAV格式容量过大,因而使用起来不方便。因此,一般情况下我们把它压缩为MP3或WMA格式。压缩方法有无损压缩与有损压缩。MP3,OGG就属于有损压缩,如果把压缩的音频还原回去,音频其实是不一样的。当然,人耳很难分辨出这种细微的差别。因此,如果把MP3,OGG格式从压缩的状态还原回去的话,就会产生损失,而像APE和FLAC这类音频格式即使还原,也能毫无损失地保留原有音质。所以APE和FLAC可以无损失高音质地压缩和还原。这些压缩的音频格式由于都采用了各自的压缩算法,若要播放音乐需要先用相应的算法进行解压缩。
本次实验我们只是把存放在SD卡里的音乐通过FPGA读出来并输出给WM8960进行播放。如果采用压缩的音乐格式而不通过相应的解压缩算法处理的话听到的就是噪声了,所以本次实验我们使用无压缩的WAV格式。由于一般WAV格式的音乐采样率为44.1kHz,而WM8960支持的采用率中没有44.1kHz,所以我们需要把存放在SD卡里的音乐采样率转换成WM8960支持的48kHz,如果不转换成48kHz,直接使用原始的44.1kHz进行播放,播放的速度会快一些,相当于1.088倍的速度播放,转换可以用音乐格式转换类的软件。本次实验使用的WAV格式音乐的采样率为48kHz,量化位数为16位。
35.2实验任务
本节实验任务是使用FPGA开发板实现从SD卡中读取存放的音乐,并输出给WM8960实现音乐播放器的功能。
35.3硬件设计
音乐播放器的硬件设计其实和音频环回实验是一摸一样的,其中还用到了SD卡,关于SD卡的硬件设计在SD卡读写实验中也已经介绍过了,所以在本次音乐播放器的实验中就不再给出硬件电路设计了。
本次实验所用到的引脚分配如下表所示:
表 35.3.1 音乐播放器实验引脚分配
3531.png

对应的UCF文件如下所示:
  1. Net sys_clk LOC = N8 | TNM_NET = sys_clk_pin;
  2. TIMESPEC "TS_sys_clk" = PERIOD "sys_clk"  20  ns HIGH 50 %;

  3. NET sys_rst_n                    LOC = G16 | IOSTANDARD = "LVCMOS33";
  4.    
  5. NET aud_scl                      LOC = T4 | IOSTANDARD = "LVCMOS33";      
  6. NET aud_sda                      LOC = R5 | IOSTANDARD = "LVCMOS33";  

  7. NET aud_mclk                     LOC = L10 | IOSTANDARD = "LVCMOS33";      
  8. NET aud_bclk                     LOC = M10 | IOSTANDARD = "LVCMOS33";     
  9. NET aud_adcdat                   LOC = N9  | IOSTANDARD = "LVCMOS33";      
  10. NET aud_dacdat                   LOC = P9  | IOSTANDARD = "LVCMOS33";      
  11. NET aud_lrc                      LOC = M9  | IOSTANDARD = "LVCMOS33";

  12. NET sd_cs                        LOC = E13 | IOSTANDARD = "LVCMOS33";    ##  SD MODE DATA3
  13. NET sd_clk                       LOC = F12 | IOSTANDARD = "LVCMOS33";    ##  SD MODE CLK/SCK
  14. NET sd_mosi                      LOC = E12 | IOSTANDARD = "LVCMOS33";    ## SD MODE CMD
  15. NET sd_miso                      LOC = E11 | IOSTANDARD = "LVCMOS33";    ## SD MODE DATA0


  16. NET "aud_bclk" CLOCK_DEDICATED_ROUTE = FALSE;
  17. PIN "aud_bclk_BUFGP/BUFG.O" CLOCK_DEDICATED_ROUTE = FALSE;
复制代码

35.4程序设计
根据试验任务我们给出如下程序设计框图:
353787.png

图 35.4.1音乐播放器实验程序框图

本次音乐播放器实验的程序设计思路是先在SD卡中存入我们要播放的音乐(音乐格式是WAV格式的,可以直接从网络上下载,或者直接打开音乐播放器实验例程,其中doc文件夹里面存放了示例音乐),然后通过FPGA将SD中的内容读取出来存入fifo,再从fifo读出数据发送给WM8960芯片,最后WM8960芯片播放音乐。从上面的程序框图中可以看到本次实验软件工程共包含了6个模块,分别是顶层模块(top_audio_sd)、锁相环模块(pll)、WM8960控制模块(wm8960_ctrl)、SD卡控制模块(sd_ctrl_top)、SD卡/WM8960协调模块(audio_sd_ctrl)和FIFO模块(fifo)。其中每个模块的功能如下所示:
顶层模块(top_audio_sd):主要是例化各个子模块。
锁相环模块(pll):主要是生成SD卡和WM8960的工作时钟。
WM8960控制模块(wm8960_ctrl):WM8960控制模块又包含了三个子模块,分别是寄存器配置模块(wm8960_config)、音频接收模块(audio_receive)和音频发送模块(audio_send)。其中寄存器配置模块的作用就是配置WM8960音频芯片,让芯片能够正常工作;音频接收模块就是将WM8960音频芯片输出的串行ADC数据转换为并行音乐数据,但是需要注意的是在本次实验是用不到音频接收模块的因为我们的数据是从SD卡中读取的,只不过为了移植方便我们保留了音频接收模块,其实去掉这个模块对功能也是不影响的;音频发送模块的功能就是将SD卡中读出的并行音乐数据转化为串行数据输出给板载WM8960音频芯片。
SD卡控制模块(sd_ctrl_top):SD卡控制模块又包含了SD卡初始化模块、SD卡写模块和SD卡读模块,他们的作用分别是初始化SD卡、写数据进SD卡,从SD卡读取数据。需要注意的是本次实验没有用到SD卡写模块,因为数据是事先通过电脑直接拷贝到SD卡中去的,不需要我们在代码中写入数据了,但是为了移植方便,我们同样保留了SD卡写模块。
SD卡/WM8960协调模块(audio_sd_ctrl):此模块主要负责协调SD卡模块、FIFO模块和WM8960控制模块三者之间的数据交互工作。首先协调模块会先检测FIFO模块的写入数据量(wrusedw_cnt),当FIFO中储存的数据量小于等于255的时候,协调模块就会给SD卡模块发送读取数据请求,将数据读出来存入FIFO,当FIFO中的数据大于255个的时候,协调模块就停止向SD卡发送读数据请求。同时协调模块还会检测WM8960数据发送模块的数据发送完成信号(tx_done),每当WM8960数据发送模块完成一次数据发送,协调模块就会给FIFO模块发送一次读使能,从FIFO中读一个数据出来传给WM8960数据发送模块,让WM8960数据发送模块把数据输出给板载WM8960音频芯片。这样SD卡、FIFO和WM8960控制模块三者之间就可以有序的相互配合工作了。
FIFO模块:数据缓冲和切换时钟域的作用。
接下来我们来看看各个模块的代码,首先是顶层模块:
  1. 1   module top_audio_sd(   
  2. 2       input           sys_clk      ,   //系统时钟
  3. 3       input           sys_rst_n    ,   //系统复位,低电平有效  
  4. 4   
  5. 5       //wm8960 audio interface (master mode)
  6. 6       input           aud_bclk     ,   // 位时钟
  7. 7       input           aud_lrc      ,   // 左右通道时钟
  8. 8       input           aud_adcdat   ,   // 音频输入
  9. 9       output          aud_mclk     ,   // 主时钟
  10. 10      output          aud_dacdat   ,   // 音频输出
  11. 11                                      
  12. 12      //wm8960 control interface      
  13. 13      output          aud_scl      ,   // I2C时钟信号
  14. 14      inout           aud_sda      ,   // I2C数据信号
  15. 15                                      
  16. 16      //sd interface                  
  17. 17      output          sd_clk       ,   //SD卡SPI时钟
  18. 18      input           sd_miso      ,   //SD卡SPI主机输入数据脚
  19. 19      output          sd_mosi      ,   //SD卡SPI主机输出数据脚
  20. 20      output          sd_cs            //SD卡SPI片选
  21. 21      
  22. 22      );   
  23. 23      
  24. 24  //parameter define
  25. 25  parameter       START_ADDR = 17'd8200  ;  // 音乐存放的起始地址
  26. 26  parameter       AUDIO_SEC  = 17'd123026;  // 音乐占用的扇区数   
  27. 27                                                                  
  28. 28  //wire define                          
  29. 29  wire         locked                    ;  //时钟锁定信号              
  30. 30  wire         clk_50m                   ;  //50m时钟
  31. 31  wire         clk_50m_180deg            ;  //50m时钟180度相位
  32. 32  wire         clk_12_5m                 ;  //12.5m时钟
  33. 33  wire  [31:0] adc_data                  ;  // 输入的音频数据
  34. 34  wire         rx_done                   ;  // 一次采集完成
  35. 35  wire         tx_done                   ;  // 一次发送完成
  36. 36  wire  [31:0] rd_sec_addr               ;  // 读SD卡扇区地址
  37. 37  wire         rd_start_en               ;  // 开始读出使能
  38. 38  wire         rd_busy                   ;  // 读忙信号
  39. 39  wire         rd_val_en                 ;  // 数据读取有效使能信号
  40. 40  wire  [15:0] music_data                ;  // 音乐数据
  41. 41  wire  [31:0] dac_data                  ;  // 音频数据
  42. 42  wire  [ 9:0] wr_data_count             ;  // fifo内剩余写入的字数
  43. 43  wire  [15:0] rd_val_data               ;  // 读数据
  44. 44  wire         sd_init_done              ;  // SD卡初始化完成
  45. 45  
  46. 46  //*****************************************************
  47. 47  //**                    main code
  48. 48  //*****************************************************
  49. 49  
  50. 50  //待时钟锁定后产生复位结束信号
  51. 51  assign  rst_n = sys_rst_n & locked;
  52. 52  
  53. 53  pll u_pll
  54. 54  (// Clock in ports
  55. 55  .CLK_IN1    (sys_clk       ), // IN
  56. 56  // Clock out ports
  57. 57  .CLK_OUT1   (clk_50m       ), // OUT
  58. 58  .CLK_OUT2   (clk_50m_180deg), // OUT
  59. 59  .CLK_OUT3   (clk_12_5m     ), // OUT   
  60. 60  // Status and control signals
  61. 61  .RESET      (~sys_rst_n    ), // IN
  62. 62  .LOCKED     (locked        )  // OUT
  63. 63  );                     
  64. 64  
  65. 65  ODDR2 #(
  66. 66      .DDR_ALIGNMENT("NONE"), // Sets output alignment to "NONE", "C0" or "C1"
  67. 67      .INIT(1'b0),            // Sets initial state of the Q output to 1'b0 or 1'b1
  68. 68      .SRTYPE("SYNC")         // Specifies "SYNC" or "ASYNC" set/reset
  69. 69  ) ODDR2_inst_1 (
  70. 70      .Q(aud_mclk),           // 1-bit DDR output data
  71. 71      .C0(clk_12_5m),         // 1-bit clock input
  72. 72      .C1(~clk_12_5m),        // 1-bit clock input
  73. 73      .CE(1'b1),              // 1-bit clock enable input
  74. 74      .D0(1'b1),              // 1-bit data input (associated with C0)
  75. 75      .D1(1'b0),              // 1-bit data input (associated with C1)
  76. 76      .R(1'b0),               // 1-bit reset input
  77. 77      .S(1'b0)                // 1-bit set input
  78. 78  );   
  79. 79  
  80. 80  //例化WM8960控制模块
  81. 81  wm8960_ctrl u_wm8960_ctrl(
  82. 82      .clk                (clk_50m    ),    // 时钟信号
  83. 83      .rst_n              (rst_n      ),    // 复位信号
  84. 84  
  85. 85      .aud_bclk           (aud_bclk   ),    // WM8960位时钟
  86. 86      .aud_lrc            (aud_lrc    ),    // 左右通道时钟
  87. 87      .aud_adcdat         (),               // 音频输入
  88. 88      .aud_dacdat         (aud_dacdat ),    // 音频输出
  89. 89      
  90. 90      .aud_scl            (aud_scl    ),    // WM8960的SCL信号
  91. 91      .aud_sda            (aud_sda    ),    // WM8960的SDA信号
  92. 92      
  93. 93      .adc_data           (),               // 输入的音频数据
  94. 94      .dac_data           (dac_data   ),    // 输出的音频数据
  95. 95      .rx_done            (),
  96. 96      .tx_done            (tx_done    )     // 发送完成信号
  97. 97  );
  98. 98  
  99. 99  sd_ctrl_top u_sd_ctrl_top(
  100. 100     .clk_ref          (clk_50m       ),    // 时钟信号(25Mhz)
  101. 101     .clk_ref_180deg   (clk_50m_180deg),    // 时钟信号,与sd_clk相位相差180度
  102. 102     .rst_n            (rst_n         ),    // 复位信号,低电平有效
  103. 103     //SD卡接口
  104. 104     .sd_clk           (sd_clk        ),    // SD卡输出时钟
  105. 105     .sd_miso          (sd_miso       ),    // SD卡SPI 主机输入数据脚
  106. 106     .sd_mosi          (sd_mosi       ),    // SD卡SPI 主机输出数据脚
  107. 107     .sd_cs            (sd_cs         ),    // SD卡SPI 片选
  108. 108     //用户写SD卡接口
  109. 109     .wr_start_en      (0),                 // 开始写入数据信号
  110. 110     .wr_sec_addr      (0),                 // 写数据扇区地址
  111. 111     .wr_data          (0),                 // 写数据
  112. 112     .wr_req           (),                  // 写请求数据信号
  113. 113     .wr_busy          (),                  // 写忙信号
  114. 114     //用户读SD卡接口
  115. 115     .rd_sec_addr      (rd_sec_addr  ),     // 读数据扇区地址
  116. 116     .rd_start_en      (rd_start_en  ),     // 开始读取数据信号
  117. 117     .rd_busy          (rd_busy      ),     // 读忙信号
  118. 118     .rd_val_en        (rd_val_en    ),     // 读数据有效信号使能
  119. 119     .rd_val_data      (rd_val_data  ),     // 读数据
  120. 120     .sd_init_done     (sd_init_done )      // SD卡初始化完成
  121. 121 );
  122. 122
  123. 123 audio_sd_ctrl #(
  124. 124     .START_ADDR  (START_ADDR),             // 音乐存放的起始地址
  125. 125     .AUDIO_SEC   (AUDIO_SEC )              // 音乐占用的扇区数
  126. 126 ) u_audio_sd_ctrl(
  127. 127     //system clock
  128. 128     .sd_clk       (clk_50m       ),        // SD卡时钟信号
  129. 129     .aud_bclk     (aud_bclk      ),        // WM8960位时钟信号
  130. 130     .rst_n        (rst_n         ),        // 复位信号
  131. 131     //user interface
  132. 132     .sd_init_done (sd_init_done  ),        // SD卡初始化完成
  133. 133     .rd_busy      (rd_busy       ),        // 读忙信号
  134. 134     .tx_done      (tx_done       ),
  135. 135     .music_data   (music_data    ),        // 音乐数据
  136. 136     .wrusedw_cnt  (wr_data_count ),        // fifo内剩余写入的字数
  137. 137     .rd_start_en  (rd_start_en   ),        // 开始读出使能
  138. 138     .rd_sec_addr  (rd_sec_addr   ),        // 读SD卡扇区地址
  139. 139     .dac_data     (dac_data      )         // 音频数据
  140. 140 );
  141. 141
  142. 142 fifo u_fifo (
  143. 143 .wr_clk       (clk_50m      ),            // input wr_clk
  144. 144 .rd_clk       (aud_bclk     ),            // input rd_clk
  145. 145 .din          (rd_val_data  ),            // input [15 : 0] din
  146. 146 .wr_en        (rd_val_en    ),            // input wr_en
  147. 147 .rd_en        (tx_done      ),            // input rd_en
  148. 148 .dout         (music_data   ),            // output [15 : 0] dout
  149. 149 .full         (),                         // output full
  150. 150 .empty        (),                         // output empty
  151. 151 .rd_data_count(),                         // output [9 : 0] rd_data_count
  152. 152 .wr_data_count(wr_data_count)             // output [9 : 0] wr_data_count
  153. 153 );
  154. 154
  155. 155 endmodule
复制代码

顶层模块的代码其实没什么好分析的,就是例化各个子模块,但是这里需要注意两个参数,它们在代码的第25~26行,其中START_ADDR代表的是SD卡中储存的音乐文件起始位置,AUDIO_SEC代表的是整个音乐文件占用的总扇区数,那么这两个参数是怎么得来的呢?START_ADDR比较容易得到,当你把音乐文件(在例程的doc文件夹中有提供音乐文件)拷贝到SD卡中去后就可以直接使用WinHex软件打开SD卡查看存放文件的起始扇区地址了。WinHex软件在资料盘A盘有提供,它的路径是超越者FPGA开发板资料盘(A盘)\6_软件资料\1_软件。WinHex软件的具体操作步骤如下图所示:
3512408.png

图 35.4.2 WinHex软件打开磁盘

如上图所示第一步先打开WinHex软件然后找到“工具”选项,点击打开磁盘,会弹出磁盘选择界面,如下图所示:
3512529.png

图 35.4.3选择要打开的磁盘

如上图所示,第二步我们在逻辑驱动器选项下找到本次实验所使用的SD卡“可移动磁盘(G)”,选中它然后点击确定就可以打开SD卡了,打开后的界面如下图所示:
3512667.png

图 35.4.4 WinHex打开SD卡界面

从上图中可以看到我们存放的音乐是“风起天澜”,它的起始扇区位置是8200,所以对应的START_ADDR的值就是8200。那么AUDIO_SEC的值是怎么得到的呢?它要稍微麻烦点需要通过计算得到,我们先找到风起天澜这个文件(可以直接在例程的doc文件夹下找到,例程路径为超越者FPGA开发板资料盘(A盘)\4_SourceCode\1_Verilog),然后右击它,打开这个文件的属性,如下图所示:
3512935.png

图 35.4.5风起天澜文件属性

从上图中可以看到这个文件的大小是62988846个字节,而我们SD卡一个扇区可以存储512个字节,因此这个文件占用的总扇区数就是62988846/512≈123026(这里切记不能四舍五入,当出现小数的时候应该把扇区数加一),这样就可以得到AUDIO_SEC的值了。
顶层模块代码看完后我们直接来讲解一下SD卡/WM8960协调模块(audio_sd_ctrl)的代码,至于锁相环模块(pll)、WM8960控制模块(wm8960_ctrl)、SD卡控制模块(sd_ctrl_top)和FIFO模块(fifo)的代码在本次实验中就不在讲解了,因为在前面的例程学习过程中已经详细的讲解过了,但是在这里有一点需要大家注意,WM8960控制模块(wm8960_ctrl)在配置WM8960芯片寄存器的时候需要将音频数据位宽改为16位,如下图所示:
3513368.png

图 35.4.6修改音频数据位宽

这里只需要把wm8960_ctrl模块的WL改为16位就行了,它会把这个参数传递给它例化的子模块,之所以要修改音频数据位宽主要是为了匹配WAV格式的音乐文件(在上文中已经介绍过WAV格式的音乐文件数据位宽是16位位宽)。整个WM8960控制模块(wm8960_ctrl)除了音频数据位宽需要修改之外其他地方就和“音频环回”实验一模一样了。
接下来我们重点讲解一下SD卡/WM8960协调模块,其代码如下所示:
  1. 1   module audio_sd_ctrl(
  2. 2       //system clock
  3. 3       input                  sd_clk         ,      // SD卡时钟信号
  4. 4       input                  aud_bclk       ,      // WM8960位时钟信号
  5. 5       input                  rst_n          ,      // 复位信号
  6. 6       //user interface
  7. 7       input                  sd_init_done   ,      // SD卡初始化完成
  8. 8       input                  rd_busy        ,      // 读忙信号
  9. 9       input                  tx_done        ,
  10. 10      input          [15:0]  music_data     ,      // 音乐数据
  11. 11      input          [ 9:0]  wrusedw_cnt    ,      // fifo内剩余写入的字数
  12. 12      output   reg           rd_start_en    ,      // 开始读出使能
  13. 13      output   reg   [31:0]  rd_sec_addr    ,      // 读SD卡扇区地址
  14. 14      output   reg   [15:0]  dac_data              // 音频数据
  15. 15  );
  16. 16  
  17. 17  //parameter define
  18. 18  parameter       START_ADDR = 17'd8200 ;          // 音乐存放的起始地址
  19. 19  parameter       AUDIO_SEC  = 17'd123026;         // 音乐占用的扇区数
  20. 20  
  21. 21  //reg define
  22. 22  reg    [ 1:0]   flow_cnt       ;                 // 状态流计数
  23. 23  reg    [16:0]   rd_sec_cnt     ;                 // 读扇区次数计数器
  24. 24  reg             rd_busy_d0     ;                 // 读忙信号打拍d0
  25. 25  reg             rd_busy_d1     ;                 // 读忙信号打拍d1
  26. 26  
  27. 27  //wire define
  28. 28  wire            neg_rd_busy    ;                 // 读忙信号的下降沿
  29. 29  
  30. 30  //*****************************************************
  31. 31  //**                    main code
  32. 32  //*****************************************************
  33. 33  
  34. 34  assign  neg_rd_busy = rd_busy_d1 & (~rd_busy_d0);// 采读忙信号下降沿
  35. 35  
  36. 36  //音频处理
  37. 37  always @(posedge aud_bclk or negedge rst_n) begin
  38. 38      if(!rst_n) begin
  39. 39          dac_data <= 16'd0;
  40. 40      end
  41. 41      else if(tx_done)
  42. 42          dac_data <= {music_data[7:0],music_data[15:8]};
  43. 43  end
  44. 44  
  45. 45  //打拍采上升沿
  46. 46  always @(posedge sd_clk or negedge rst_n) begin
  47. 47      if(!rst_n) begin
  48. 48          rd_busy_d0 <= 1'b0;
  49. 49          rd_busy_d1 <= 1'b0;
  50. 50      end
  51. 51      else begin
  52. 52          rd_busy_d0 <= rd_busy;
  53. 53          rd_busy_d1 <= rd_busy_d0;
  54. 54      end
  55. 55  end
  56. 56  
  57. 57  //SD扇区地址变更
  58. 58  always @(posedge sd_clk or negedge rst_n) begin
  59. 59      if(!rst_n) begin
  60. 60          rd_sec_addr <= 32'd0;
  61. 61      end
  62. 62      else if(rd_sec_addr <= START_ADDR + AUDIO_SEC)
  63. 63          rd_sec_addr <= rd_sec_cnt + START_ADDR;
  64. 64  end
  65. 65  
  66. 66  //读取音频数据
  67. 67  always @(posedge sd_clk or negedge rst_n) begin
  68. 68      if(!rst_n) begin
  69. 69          flow_cnt   <=  2'b0;
  70. 70          rd_sec_cnt <= 17'd0;
  71. 71      end
  72. 72      else begin
  73. 73          rd_start_en <= 1'b0;
  74. 74          case(flow_cnt)
  75. 75          2'd0: begin
  76. 76              if(sd_init_done == 1'b1) begin
  77. 77              flow_cnt     <= flow_cnt + 1'd1;
  78. 78              rd_start_en  <= 1'b1;
  79. 79              end
  80. 80          end
  81. 81          2'd1: begin
  82. 82          //读忙信号下降沿说明单次读出结束,开始读取下一扇区地址数据
  83. 83              if(rd_sec_cnt < AUDIO_SEC) begin
  84. 84                  if(neg_rd_busy) begin
  85. 85                      rd_sec_cnt <= rd_sec_cnt + 17'd1;   
  86. 86                      flow_cnt   <= flow_cnt + 1'd1;
  87. 87                  end
  88. 88              end
  89. 89              else begin
  90. 90                  rd_sec_cnt <= 17'd0;
  91. 91                  flow_cnt   <=  2'd0;
  92. 92              end
  93. 93          end
  94. 94          2'd2: begin
  95. 95              if(wrusedw_cnt <= 10'd255) begin
  96. 96                  rd_start_en <= 1'b1;
  97. 97                  flow_cnt <= 2'd1;
  98. 98              end
  99. 99          end
  100. 100         default: flow_cnt <= 2'd0;
  101. 101         endcase
  102. 102     end
  103. 103 end
  104. 104
  105. 105 endmodule
复制代码

SD卡/WM8960协调模块代码的第37~43行是将从SD卡中读出的数据高低字节对调位置,这个是与WAV文件格式有关,WAV格式文件的储存数据是低位字节在前高位字节在后,所以我们读出数据后需要把高低位字节互换一下才能使用。接下来代码46~55行是对读忙信号(rd_busy)进行打拍,目的是为了抓取读忙信号下降沿(neg_rd_busy)。因为当SD卡在读一个扇区数据的时候会拉高读忙信号(rd_busy),当读完一个扇区的数据之后就会拉低读忙信号(rd_busy),因此当抓到读忙信号(rd_busy)的下降沿就表明SD卡已经读完一个扇区的数据了,可以开始读取下一个扇区的数据。在代码67~103行,读音频数据状态机中就用到了读忙信号下降沿(neg_rd_busy),这个状态机会先检测SD卡是否初始化成功(sd_init_done),如果初始化成功就开始读取数据,但是读取数据的最大扇区数不能超过AUDIO_SEC(代码第83行),每读完一个扇区扇区地址加一(根据neg_rd_busy判断),读数据使能由FIFO中的存储数据量决定(代码95~98),每当FIFO中的存储数据量少于255个的时候就需要拉高开始读数据使能(rd_start_en),从SD卡中读出数据。
到这里代码就讲解完了,如果大家对工程中的其他模块代码不理解可以翻看前面对应的实验例程,接下来我们编译工程生成bit流文件,然后下载到板子中去开始播放音乐吧。
35.5下载验证
先将SD卡插入开发板的卡槽,然后将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,连接电源线,并打开开发板的电源开关。硬件连接完成后,将程序下载到超越者开发板中,待程序下载完成后,就可以听到开发板喇叭开始播放音乐了(如果插上耳机耳机也会播放音乐),硬件连接如下图所示:
3517891.png

图 35.5.1 音乐播放器实验硬件连接图


正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-24 01:29

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表