1)实验平台:正点原子开拓者
FPGA 开发板
2)摘自《开拓者FPGA开发指南》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-13912-1-1.html
第四十八章 基于以太网的板对板音频互传实验
在音频环回实验中,我们成功地在开发板上实现音频的采集与播放功能;在以太网
通信实
验中,我们通过网口调试助手成功地和开发板完成了以太网通信的功能。本章我们将使用以太
网接口实现两块FPGA开发板之间的音频互传与播放功能。
本章包括以下几个部分:
48.1 板对板音频互传简介
48.2 实验任务
48.3 硬件设计
48.4 程序设计
48.5 下载验证
板对板音频互传简介
音频板对板互传实验是基于以太网来传输音频数据的,我们在“以太网通信实验”章节中
对以太网的协议、MII时序等内容作了详细的介绍,如果大家对这部分内容不是很熟悉的话,
请参考“以太网通信实验”中的以太网简介部分。
从“音频环回实验”章节中可以知道,WM8978音频芯片的采样率最大为48Khz,在数据位
宽 为 32 位 数 据 格 式 下 , 每 秒 钟 传 输 的 数 据 量 为 48000*32*2 ( 左 右 两 个 声 道 ) =
3072000bit≈2.930Mbit。我们FPGA开发板上的PHY芯片类型为百兆以太网,理论上最大传输速
率为 100Mbit/s,即使加上帧头、CRC校验以及帧间隙带来的额外开销,对于实时传输音频数
据来说也是毫无压力的。我们知道,以太网通信是以数据包为单位进行数据传输,单包数据除
以太网帧头与CRC校验之外,以太网数据至少为46个有效字节,对于不足46个字节的数据要在
数据的后面补充任意值。如果我们使用以太网通信单包只传输一个32位的音频数据,那么会极
大的浪费以太网的高速传输能力,因此我们先将收到的音频数据使用fifo缓存下来,待数据量
达到预设值之后,再通过以太网发送出去。
实验任务
本节实验任务是使用以太网接口实现两块FPGA开发板之间的音频互传与播放功能。首先开
发板A对WM8978芯片进行音频数据采集,并将采集的数据通过以太网接口发送给开发板B,开发
板B将收到的数据通过WM8978芯片进行播放;开发板B同样对WM8978芯片进行音频数据采集,并
将采集的数据通过以太网接口发送给开发板A,开发板A将收到的数据通过WM8978芯片进行播放,
从而实现两块FPGA开发板之间的音频互传与播放功能。
硬件设计
WM8978音频芯片及音频接口原理图与“音频环回实验”完全相同,请参考“音频环回实验”
的硬件设计部分。以太网接口部分的硬件设计请参考“以太网通信实验”中的硬件设计部分。
由于以太网接口和WM8978音频引脚数目较多且在前面相应的章节中已经给出它们的管脚
列表,这里不再列出管脚分配。
程序设计
图 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 [3
:0
] eth_rx_data
, //MII输入数据
9
output eth_tx_en
, //MII输出数据有效信号
10
output [3
:0
] 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 [31
:0
] adc_data
; //接收到的音频数据
40
wire udp_tx_start_en
; //以太网开始发送信号
41
wire [15
:0
] udp_tx_byte_num
; //以太网发送的有效字节数
42
wire [31
:0
] udp_tx_data
; //以太网发送的数据
43
wire udp_rec_pkt_done
; //以太网单包数据接收完成信号
44
wire udp_rec_en
; //以太网接收使能信号
45
wire [31
:0
] udp_rec_data
; //以太网接收到的数据
46
wire udp_tx_req
; //以太网发送请求数据信号
47
wire udp_tx_done
; //以太网发送完成信号
48
wire aud_tx_done
; //音频发送完成信号
49
wire [31
:0
] 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 [31
:0
] 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 [15
:0
] udp_tx_byte_num
, //以太网发送的字节数
12
output [31
:0
] 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 [8
:0
] 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 [31
:0
] udp_rec_data
, //以太网接收到的数据
7
8
input aud_bclk
, //WM8978位时钟
9
input aud_dac_req
, //dac数据请求信号
10
output [31
:0
] 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为空
时进行读操作。
下载验证
首先我们打开基于以太网的板对板音频互传实验工程,在工程所在的路径下打开
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[USB-0]”。然后点击“Start”将工程编译完成后得到的sof文件下载到开
发板中,如图 48.5.所示:
图 48.5.2 程序下载完成界面
程序下载完成后并且硬件连接无误的话,我们可以看到两块开发板上以太网接口的灯会不
停地闪烁,说明此时两块开发板正在互传音频。如果音频连接线的另一端已经连接至电脑或手
机的音频输出端口,此时打开音乐,就可以听到另一块开发板上喇叭播放的音乐。戴上耳机,
也能听到耳机播放的音乐,说明音频互传实验验证成功。