正点原子 发表于 2019-7-27 11:21:04

【正点原子FPGA连载】第四十八章 基于以太网的板对板音频--摘自【正点原子】开拓者 FPGA 开发指南

本帖最后由 正点原子 于 2020-10-23 17:14 编辑

1)实验平台:正点原子开拓者FPGA开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=579749209820
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-281143-1-1.html
4)本实例源码下载:
5)对正点原子FPGA感兴趣的同学可以加群讨论:712557122点击加入:
6)关注正点原子公众号,获取最新资料更新

第四十八章 基于以太网的板对板音频互传实验

在音频环回实验中, 我们成功地在开发板上实现音频的采集与播放功能; 在以太网通信实验中, 我们通过网口调试助手成功地和开发板完成了以太网通信的功能。 本章我们将使用以太网接口实现两块FPGA开发板之间的音频互传与播放功能。
本章包括以下几个部分:
48.1 板对板音频互传简介
48.2 实验任务
48.3 硬件设计
48.4 程序设计
48.5 下载验证
48.1 板对板音频互传简介
音频板对板互传实验是基于以太网来传输音频数据的, 我们在“以太网通信实验” 章节中对以太网的协议、 MII时序等内容作了详细的介绍, 如果大家对这部分内容不是很熟悉的话,请参考“以太网通信实验” 中的以太网简介部分。从“音频环回实验” 章节中可以知道, WM8978音频芯片的采样率最大为48Khz, 在数据位宽 为 32 位 数 据 格 式 下 , 每 秒 钟 传 输 的 数 据 量 为 48000*32*2 (左 右 两 个 声 道 ) =3072000bit≈2.930Mbit。 我们FPGA开发板上的PHY芯片类型为百兆以太网, 理论上最大传输速率为 100Mbit/s, 即使加上帧头、 CRC校验以及帧间隙带来的额外开销, 对于实时传输音频数据来说也是毫无压力的。 我们知道, 以太网通信是以数据包为单位进行数据传输, 单包数据除以太网帧头与CRC校验之外, 以太网数据至少为46个有效字节, 对于不足46个字节的数据要在数据的后面补充任意值。 如果我们使用以太网通信单包只传输一个32位的音频数据, 那么会极大的浪费以太网的高速传输能力, 因此我们先将收到的音频数据使用fifo缓存下来, 待数据量达到预设值之后, 再通过以太网发送出去。
48.2 实验任务
本节实验任务是使用以太网接口实现两块FPGA开发板之间的音频互传与播放功能。首先开发板A对WM8978芯片进行音频数据采集, 并将采集的数据通过以太网接口发送给开发板B, 开发板B将收到的数据通过WM8978芯片进行播放; 开发板B同样对WM8978芯片进行音频数据采集, 并
将采集的数据通过以太网接口发送给开发板A, 开发板A将收到的数据通过WM8978芯片进行播放,从而实现两块FPGA开发板之间的音频互传与播放功能。
48.3 硬件设计
WM8978音频芯片及音频接口原理图与“音频环回实验”完全相同,请参考“音频环回实验”的硬件设计部分。 以太网接口部分的硬件设计请参考“以太网通信实验” 中的硬件设计部分。由于以太网接口和WM8978音频引脚数目较多且在前面相应的章节中已经给出它们的管脚
列表, 这里不再列出管脚分配。
48.4 程序设计
图 48.4.1是根据本章实验任务画出的系统框图。PLL时钟模块为WM8978音频芯片提供主时钟, 而UDP模块的驱动时钟是由开发板上的PHY芯片提供; WM8978配置模块用于初始化WM8978音频芯片,使其能够在预设的工作模式下工作;音频接收模块用于接收来自WM8978的音频数据,
将WM8978串行输入的1位数据转换成32位的并行数据; 音频缓存发送控制模块用于缓存32位的音频数据, 当缓存的数据量达到预设值之后, 控制以太网发送模块开始发送音频数据。 以太网接收模块负责接收另一块开发板传输的音频数据, 并将接收到的数据写入音频缓存接收控制模
块, 音频缓存接收控制模块负责缓存以太网接收到的音频数据, 将数据存入fifo模块等待被音频发送模块读取, 音频发送模块发送音频数据, 将并行输入的32位数据转成1位串行数据发送出去。
板对板音频互传实验系统框图如下图所示:


图 48.4.1 基于以太网的板对板音频互传系统框图

顶层模块的原理图如下图所示:





图 48.4.2 顶层模块原理图

由上图可知, FPGA顶层模块(eth_audio_transmit) 例化了以下五个模块: PLL时钟模块(pll_clk)、WM8978控制模块(wm8978_ctrl)、音频缓存发送控制模块(audio_cache_tx_ctrl)、UDP模块(udp) 和音频缓存接收控制模块(audio_cache_rx_ctrl) 。
PLL时钟模块(pll_clk) : PLL时钟模块通过调用锁相环(PLL) IP核来实现, 输出1个频率为12Mhz的时钟, 作为WM8978的主时钟MCLK。WM8978控制模块(wm8978_config) : WM8978控制模块完成了WM8978的初始化配置、 音频
数据采集和音频数据发送的功能, 该模块例化了WM8978配置模块(wm8978_config) 、 音频接收模块(audio_receive) 、 音频发送模块(audio_send) , 其中WM8978配置模块例化了IIC配置模块(i2c_reg_cfg) 和IIC驱动模块(i2c_dri) 。 有关该模块的详细介绍请大家参考“音
频环回实验” 章节。音频缓存发送控制(audio_cache_tx_ctrl) : 音频缓存发送控制模块用于缓存32位的音频数据, 当缓存的数据量达到预设值之后, 控制以太网发送模块开始发送音频数据。UDP模块(udp) : UDP模块实现以太网通信的收发功能, 该模块内部例化了以太网接收模
块(ip_receive) 、 以太网发送模块(ip_send) 和CRC32校验模块(crc32_d4) 。 有关该模块的详细介绍请大家参考“以太网通信实验” 章节。音频缓存接收控制(audio_cache_rx_ctrl) : 音频缓存接收控制模块负责缓存以太网接
收到的音频数据, 将数据存入fifo模块等待被WM8978控制模块读取。
顶层模块代码如下:
1 module eth_audio_transmit(
2 input sys_clk , //系统时钟
3 input sys_rst_n , //系统复位信号, 低电平有效
4 //以太网接口
5 input eth_rx_clk , //MII接收数据时钟
6 input eth_rxdv , //MII输入数据有效信号
7 input eth_tx_clk , //MII发送数据时钟
8 input eth_rx_data , //MII输入数据
9 output eth_tx_en , //MII输出数据有效信号
10 output eth_tx_data , //MII输出数据
11 output eth_rst_n , //以太网芯片复位信号, 低电平有效
12 //wm8978 interface
13 //audio interface(master mode)
14 input aud_bclk , // WM8978位时钟
15 input aud_lrc , // 对齐信号
16 input aud_adcdat , // 音频输入
17 output aud_mclk , // WM8978的主时钟(最大为12.288MHz)
18 output aud_dacdat , // 音频输出
19 //control interface
20 output aud_scl , // WM8978的SCL信号
21 inout aud_sda // WM8978的SDA信号
22 );
23
24 //parameter define
25 //这里DES_IP=BOARD_IP,这样两块开发板可以用同一个程序实现互传音频播放
26 //开发板MAC地址 00-11-22-33-44-55
27 parameter BOARD_MAC = 48'h00_11_22_33_44_55;
28 //开发板IP地址 192.168.1.123
29 parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd123};
30 //目的MAC地址 ff_ff_ff_ff_ff_ff
31 parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
32 //目的IP地址 192.168.1.102
33 parameter DES_IP = {8'd192,8'd168,8'd1,8'd123};
34 //wire define
35 wire rst_n ;
36 wire locked ;
37
38 wire aud_rx_done ; //音频数据接收完成信号
39 wire adc_data ; //接收到的音频数据
40 wire udp_tx_start_en ; //以太网开始发送信号
41 wire udp_tx_byte_num ; //以太网发送的有效字节数
42 wire udp_tx_data ; //以太网发送的数据
43 wire udp_rec_pkt_done; //以太网单包数据接收完成信号
44 wire udp_rec_en ; //以太网接收使能信号
45 wire udp_rec_data ; //以太网接收到的数据
46 wire udp_tx_req ; //以太网发送请求数据信号
47 wire udp_tx_done ; //以太网发送完成信号
48 wire aud_tx_done ; //音频发送完成信号
49 wire dac_data ; //音频dac数据
50
51 //*****************************************************
52 //** main code
53 //*****************************************************
54
55 assign rst_n = sys_rst_n & locked;
56
57 //锁相环
58 pll_clk u_pll_clk(
59 .inclk0 (sys_clk),
60 .areset (~sys_rst_n),
61 .c0 (aud_mclk),
62 .locked (locked)
63 );
64
65 //WM89878模块
66 wm8978_ctrl
67 #(
68 .WL (6'd32 ) //word length音频字长定义
69 )
70 u_wm8978_ctrl(
71 //system clock
72 .clk (sys_clk ),
73 .rst_n (rst_n ),
74 //wm8978 interface
75 //audio interface(master mode)
76 .aud_bclk (aud_bclk ),
77 .aud_lrc (aud_lrc ),
78 .aud_adcdat (aud_adcdat ),
79 .aud_dacdat (aud_dacdat ),
80 //control interface
81 .aud_scl (aud_scl ),
82 .aud_sda (aud_sda ),
83 //user interface
84 .dac_data (dac_data ),
85 .adc_data (adc_data ),
86 .rx_done (aud_rx_done),
87 .tx_done (aud_tx_done)
88 );
89
90 //音频缓存发送控制
91 audio_cache_tx_ctrl u_audio_cache_tx_ctrl(
92 .aud_bclk (aud_bclk),
93 .rst_n (rst_n),
94 .aud_rx_done (aud_rx_done),
95 .aud_adc_data (adc_data),
96 .eth_tx_clk (eth_tx_clk),
97 .udp_tx_req (udp_tx_req),
98 .udp_tx_done (udp_tx_done),
99 .udp_tx_start_en (udp_tx_start_en),
100 .udp_tx_byte_num (udp_tx_byte_num),
101 .udp_tx_data (udp_tx_data)
102 );
103
104 //UDP模块
105 udp
106 #(
107 .BOARD_MAC (BOARD_MAC), //参数例化
108 .BOARD_IP (BOARD_IP ),
109 .DES_MAC (DES_MAC ),
110 .DES_IP (DES_IP )
111 )
112 u_udp(
113 .eth_rx_clk (eth_rx_clk ),
114 .rst_n (rst_n ),
115 .eth_rxdv (eth_rxdv ),
116 .eth_rx_data (eth_rx_data),
117 .eth_tx_clk (eth_tx_clk ),
118 .tx_start_en (udp_tx_start_en),
119 .tx_data (udp_tx_data),
120 .tx_byte_num (udp_tx_byte_num),
121 .tx_done (udp_tx_done),
122 .tx_req (udp_tx_req ),
123 .rec_pkt_done (udp_rec_pkt_done),
124 .rec_en (udp_rec_en),
125 .rec_data (udp_rec_data),
126 .rec_byte_num (),
127 .eth_tx_en (eth_tx_en ),
128 .eth_tx_data (eth_tx_data),
129 .eth_rst_n (eth_rst_n )
130 );
131
132 //音频缓存接收控制
133 audio_cache_rx_ctrl u_audio_cache_rx_ctrl(
134 .eth_rx_clk (eth_rx_clk),
135 .rst_n (rst_n),
136 .udp_rec_pkt_done (udp_rec_pkt_done),
137 .udp_rec_en (udp_rec_en),
138 .udp_rec_data (udp_rec_data),
139 .aud_bclk (aud_bclk),
140 .aud_dac_req (aud_tx_done),
141 .dac_data (dac_data)
142 );
143
144 endmodule
在代码的第25至第33行定义了四个参量: 开发板MAC地址BOARD_MAC、 开发板IP地址BOARD_IP、 目的MAC地址DES_MAC和目的IP地址DES_IP。 需要注意的是, 如果目的IP地址和开发板IP地址不一致或者目的MAC(公共MAC地址除外) 地址和开发板MAC地址不一致的话, 以太网
接收模块会直接丢掉数据, 导致接收音频数据失败。 因此目的MAC地址这里写的是公共MAC地址(48'hff_ff_ff_ff_ff_ff) , 目的IP地址写的是和开发板IP地址相同的值, 目的是为了让同一程序可以下载在两个开发板中。
在代码的第85至第86行代码中, aud_rx_done(音频数据接收完成信号) 和adc_data(接收到的音频数据) 写入音频缓存发送控制模块, 该模块输出的udp_tx_start_en(以太网开始发送信号) 用于控制以太网发送模块开始传输音频数据。 UDP模块输出的udp_rec_en(以太网
接收数据有效信号) 和udp_rec_data(以太网接收到的数据) 写入音频缓存接收控制模块。WM8978控制模块输出的aud_tx_done(音频数据发送完成) 信号作为音频缓存接收控制模块的读请求信号, 并将读取后的数据dac_data通过引脚aud_dacdat引脚发送出去。
音频缓存发送控制模块用于缓存32位的音频数据, 当缓存的数据量达到预设值之后, 控制以太网发送模块开始发送音频数据。
该模块代码如下所示:
1 module audio_cache_tx_ctrl(
2 input aud_bclk , //WM8978位时钟
3 input rst_n , //复位信号, 低电平有效
4 input aud_rx_done , //音频数据接收完成信号
5 input aud_adc_data , //32位音频数据
6 7
input eth_tx_clk , //以太网发送时钟
8 input udp_tx_req , //以太网发送请求数据信号
9 input udp_tx_done , //以太网发送完成信号
10 output reg udp_tx_start_en, //以太网开始发送信号
11 output udp_tx_byte_num, //以太网发送的字节数
12 output udp_tx_data //以太网发送的数据
13 );
14
15 //parameter define
16 //fifo缓存的数量大于等于此值时控制udp开始发送数据
17 parameter AUDIO_TX_NUM = 9'd256;
18
19 //reg define
20 reg udp_tx_flag ; //udp正在发送数据的标志
21
22 //wire define
23 wire data_cnt; //fifo中缓存的个数
24
25 //*****************************************************
26 //** main code
27 //*****************************************************
28
29 //以太网发送的字节数(1个32位音频数据 = 4个字节),即udp_tx_byte_num = AUDIO_TX_NUM * 4
30 assign udp_tx_byte_num = {AUDIO_TX_NUM,2'd0};
31
32 //判断fifo中缓存的个数, 超过预设值控制udp开始发送数据
33 always @(posedge eth_tx_clk or negedge rst_n) begin
34 if(rst_n == 1'b0) begin
35 udp_tx_flag <= 1'b0;
36 udp_tx_start_en <= 1'b0;
37 end
38 else begin
39 udp_tx_start_en <= 1'b0;
40 //只有当udp没有发送数据时才判断fifo大小是否满足发送条件
41 if(udp_tx_flag == 1'b0) begin
42 if(data_cnt >= AUDIO_TX_NUM) begin
43 udp_tx_flag <= 1'b1;
44 udp_tx_start_en <= 1'b1; //udp开始发送信号
45 end
46 end
47 else if(udp_tx_done) //udp发送完成后,将udp发送标志清零
48 udp_tx_flag <= 1'b0;
49 end
50 end
51
52 //异步fifo
53 async_fifo_512x32b u_async_fifo(
54 .aclr (~rst_n),
55 .data (aud_adc_data),
56 .rdclk (eth_tx_clk),
57 .rdreq (udp_tx_req),
58 .wrclk (aud_bclk),
59 .wrreq (aud_rx_done),
60 .q (udp_tx_data),
61 .rdempty (),
62 //注意rdusedw为读时钟下的计数,如果需要在写时钟下读取数据时,在建立fifo时选择wrusedw
63 .rdusedw (data_cnt), //fifo缓存的个数
64 .wrfull ()
65 );
66
67 endmodule
在代码的第17行定义了参数AUDIO_TX_NUM(单包发送音频数据个数) , 当以太网没有在发送数据时, 判断data_cnt(fifo中缓存的个数) 的值是否大于等于AUDIO_TX_NUM值, 当大于等于此值时, 开始通知以太网发送数据, 发送的字节数为AUDIO_TX_NUM的4倍(32bit=4个字节) 。
AUDIO_TX_NUM的值在这里设置为256, 设置成其它值也是可以的。 需要注意的是不建议单包发送的音频数据个数太小或者太大, 单包发送音频数据量太小传输效率低, 太大会造成音频传输的延时。 需要注意的是, 如果单包发送的数据量增加, fifo的深度也要根据缓存量相应增
加, 否则fifo写满溢出, 导致音频数据丢失。图 48.4.3为音频缓存发送模块SignalTap抓取的波形图, 当rdusedw(fifo中缓存的个数,同data_cnt) 计数达到256之后, udp_tx_start_en(以太网开始发送信号) 开始输出一个脉冲信号, 发送的有效字节个数为1024(256*4) 个字节。


图 48.4.3 音频缓存发送模块SignalTap波形图

音频缓存接收控制模块负责缓存以太网接收到的音频数据, 将数据存入fifo模块等待被音频发送模块读取。
该模块代码如下所示:
1 module audio_cache_rx_ctrl(
2 input eth_rx_clk , //以太网接收时钟
3 input rst_n , //复位信号, 低电平有效
4 input udp_rec_pkt_done, //以太网单包数据接收完成信号
5 input udp_rec_en , //以太网接收数据使能信号
6 input udp_rec_data , //以太网接收到的数据
7 8
input aud_bclk , //WM8978位时钟
9 input aud_dac_req , //dac数据请求信号
10 output dac_data //dac值
11 );
12
13 //reg define
14 reg rec_done_flag ; //单包数据接收完成后给出标志
15 reg rec_done_flag_d0; //异步信号打拍处理
16 reg rec_done_flag_d1; //异步信号打拍处理
17
18 wire fifo_rd_req ; //fifo读请求信号
19
20 //*****************************************************
21 //** main code
22 //*****************************************************
23
24 //接收完单包数据后再开始读fifo,防止fifo为空时被读取
25 assign fifo_rd_req = aud_dac_req & rec_done_flag_d1;
26
27 //接收完单包数据后给出标志
28 always @(posedge eth_rx_clk or negedge rst_n) begin
29 if(rst_n == 1'b0)
30 rec_done_flag <= 1'b0;
31 else if(udp_rec_pkt_done)
32 rec_done_flag <= 1'b1;
33 end
34
35 //异步信号打拍处理
36 always @(posedge aud_bclk or negedge rst_n) begin
37 if(rst_n == 1'b0) begin
38 rec_done_flag_d0 <= 1'b0;
39 rec_done_flag_d1 <= 1'b0;
40 end
41 else begin
42 rec_done_flag_d0 <= rec_done_flag;
43 rec_done_flag_d1 <= rec_done_flag_d0;
44 end
45 end
46
47 //异步fifo
48 async_fifo_512x32b u_async_fifo(
49 .aclr (~rst_n),
50 .data (udp_rec_data),
51 .rdclk (aud_bclk),
52 .rdreq (fifo_rd_req),
53 .wrclk (eth_rx_clk),
54 .wrreq (udp_rec_en),
55 .q (dac_data),
56 .rdempty (),
57 .rdusedw (),
58 .wrfull ()
59 );
60
61 endmodule
在代码的第27行开始的always语句块中, 当接收完单包数据之后, rec_done_flag(单包数据接收完成后给出的标志) 信号拉高; 由于rec_done_flag信号对于aud_bclk时钟来说是异步信号, 因此该信号通过延时打拍的方式同步到aud_bclk时钟下。 在代码的第25行, fifo必须
在rec_done_flag_d1为高电平之后, 即接收完单包数据之后才开始读取数据, 防止在fifo为空时进行读操作。
48.5 下载验证
首先我们打开基于以太网的板对板音频互传实验工程, 在工程所在的路径下打开eth_audio_transmit/par文件夹, 在里面找到“eth_audio_transmit.qpf” 并双击打开。 注意工程所在的路径名只能由字母、 数字以及下划线组成, 不能出现中文、 空格以及特殊字符等。


工程打开后如图 48.5.1所示:

图 48.5.1 基于以太网的板对板音频互传实验工程然后将下载器一端连电脑, 另一端与开发板上对应端口连接; 将音频连接线的一端连接至电脑或手机的音频输出端口, 另一端连接至其中一块开发板的WM8978的LINE_IN接口, 并将耳机连接至另一开发板的PHONE接口; 网线的两端分别接在两个开发板上的以太网接口, 最后连接电源线并打开电源开关。接下来我们下载程序, 验证音频互传与播放的功能。 工程打开后通过点击工具栏中的“Programmer” 图 标 打 开 下 载 界 面 , 通 过 “Add File” 按 钮 选 择eth_audio_transmit/par/output_files目录下的“eth_audio_transmit.sof” 文件。 开发板电源打开后, 在程序下载界面点击“Hardware Setup” , 在弹出的对话框中选择当前的硬件连接为“USB-Blaster” 。然后点击“Start” 将工程编译完成后得到的sof文件下载到开发板中, 如图 48.5.2所示:


图 48.5.2 程序下载完成界面

程序下载完成后并且硬件连接无误的话, 我们可以看到两块开发板上以太网接口的灯会不停地闪烁, 说明此时两块开发板正在互传音频。 如果音频连接线的另一端已经连接至电脑或手机的音频输出端口, 此时打开音乐, 就可以听到另一块开发板上喇叭播放的音乐。 戴上耳机,也能听到耳机播放的音乐, 说明音频互传实验验证成功
页: [1]
查看完整版本: 【正点原子FPGA连载】第四十八章 基于以太网的板对板音频--摘自【正点原子】开拓者 FPGA 开发指南