正点原子开拓者FPGA开发板资料连载第三十二章 音频环回实验
1)实验平台:正点原子开拓者FPGA 开发板
2)摘自《开拓者FPGA开发指南》关注官方微信号公众号,获取更多资料:正点原子
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-13912-1-1.html
第三十二章 音频环回实验
WM8978是一个低功耗、高质量的立体声多媒体数字信号编译码器,它结合了一个高质量的
立体声音频DAC和ADC,带有灵活的音频线输入、麦克风输入和音频输出处理。其主要应用于便
携式应用,可以应用到可携式数码摄像机或数码相机等设备。本章我们将使用FPGA开发板上的
WM8978器件实现音频环回的功能。
本章包括以下几个部分:
32.1 WM8978简介
32.2 实验任务
32.3 硬件设计
32.4 程序设计
32.5 下载验证
WM8978简介
WM8978是欧胜(Wolfson)推出的一款全功能音频处理器。它带有一个HI-FI级数字信号处
理内核,支持增强3D硬件环绕音效,以及5频段的硬件均衡器,可以有效改善音质。
WM8978具有高级的片上数字信号处理功能,包含一个5路均衡功能,一个用于ADC和麦克风
或者线路输入之间的混合信号的电平自动控制功能,一个纯粹的录音或者重放的数字限幅功能。
另外在ADC的线路上提供了一个数字滤波的功能,可以更好的应用滤波,比如“减少风噪”。
WM8978集成了立体声差分麦克风的前置放大与扬声器、耳机和差分、立体声线输出的驱动,
减少了应用时必需的外部组件,比如不需要单独的麦克风或者耳机的放大器。WM8978提供了一
个强悍的扬声器功放,可提供高达900mW的高质量音响效果扬声器功率,一个数字回放限制器
可防止扬声器声音过载。WM8978进一步提升了耳机放大器输出功率,在推动16欧姆耳机的时候,
每个声道最大输出功率高达40毫瓦!可以连接市面上绝大多数适合随身听的高端HI-FI耳机。
WM8978整体功能模块的框图如图 32.1.1所示:
图 32.1.1 WM8978整体功能框图
WM8978可通过I2S或PCM音频接口(I2S/PCM AUDIO INTERFACE)与FPGA进行音频数据传输。
具体应用哪种方式可通过控制接口(Control Interface)配置相应的寄存器。控制接口是一
个可选的2线或3线结构。通过MODE引脚选择(MODE引脚接高电平时为3线接口模式、低电平时
为2线接口模式),当控制接口为2线接口模式时,其时序图如下图所示。
图 32.1.2 两线接口时序图
由上图可见,其时序图与I2C时序相同。此时SCLK为串行时钟线、SDIN为串行数据线,WM8978
芯片的器件地址固定为0011010b。本次实验我们使用的是两线的控制接口。
音频接口的ADCDAT为ADC数据的输出接口,本实验中WM8978通过此接口输出音频给FPGA,
DACDAT为DAC数据的输入接口,WM8978通过此接口接收FPGA输出的音频。LRC为音频左右声道的
数据对齐时钟信号,BCLK即Bit Clock(位时钟),用于同步数据输入和输出。MCLK为主时钟
输入接口,MCLK的频率为256fs,fs为音频的采样率,一般为48kHz,所以MCLK为256 × 48 =
12288kHz = 12.288MHz。我们一般使用FPGA内部的PLL分频得到12MHz的时钟信号,然后通过
配置WM8978内部的寄存器使其PLL输出12.288MHz的时钟信号。
WM8978支持主从两种工作模式。主从工作模式的区别在于BCLK和LRC由谁控制。在主模式
下,WM8978作为主控设备,产生BCLK和LRC信号并输出。在从模式下,BCLK和LRC信号由外部设
备(本实验指FPGA)提供,WM8978作为从设备接收BCLK和LRC信号。可见如果使WM8978工作在
从模式下,我们需要通过FPGA产生BCLK和LRC信号,既浪费FPGA内部的资源也浪费(空闲)了
WM8978本身的资源,所以一般使WM8978工作在主模式下。主从工作模式通过配置R6寄存器的
bit0位来设置,bit0位为0时WM8978工作在从模式下,为1时WM8978工作在主模式下,本次实验
我们使WM8978工作在主模式下。
对于音频接口,本次实验我们采用I2S音频总线接口传输音频数据。I2S(Inter-IC Sound)
总线,又称集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定
的一种总线标准,该总线专门负责音频设备之间的数据传输,广泛应用于各种多媒体系统。I2S
的优点是接收端与发送端的音频数据有效位数可以不同。如果接收端能处理的有效位数少于发
送端,可以放弃数据帧中多余的低位数据;如果接收端能处理的有效位数多于发送端,可以自
行补足剩余的位。这种同步机制使得数字音频设备的互连更加方便,而且不会造成数据错位。
I2S总线的音频传输格式如下图:
图 32.1.3 I2S总线音频传输格式
fs为音频的采样率,LRC为左右声道的对齐时钟。由上图可知,当LRC为低电平时传输左声
道的音频数据,高电平时传输右声道的音频数据。位时钟BCLK的频率=2×采样频率×采样位数,
由于使用的是主模式,LRC和位时钟BCLK由WM8978提供,所以我们无需关心其频率的大小。我
们需要注意的是I2S格式的音频信号DACDAT和ADCDAT无论有多少位有效数据,数据的最高位总
是出现在LRC变化后的第2个BCLK脉冲处,即传输数据时高位在前,且该位在LRC变化后BCLK的
第2个上升沿采样到。
图 32.1.1中的LIP(LIN)、RIP(RIN)分别为左麦克风前置放大同相(反相)输入和右
麦克风前置放大同相(反相)输入,L2/GPIO2和R2/GPIO3分别为左通道线输入/GPIO引脚和右
通道线输入/GPIO引脚,AUXL和AUXR为左右辅助输入。LOUT1和ROUT1为耳机的左右输出,LOUT2
为第二左输出或者BTL扬声器反相输出,ROUT2第二右输出或者BTL扬声器同相输出。
由图 32.1.1可见输入到输出的通道通过一个个开关控制,每个开关由相应的寄存器控制。
配置相应的寄存器就可打开相应的通道、使能相应的功能。
WM8978内部有58个寄存器。每个寄存器的地址位为7位,数据位为9位。可通过控制接口配
置相应的寄存器以打开相应的通道或使能相应的功能。这里我们简单介绍一下要正常使用
WM8978来播放音乐时,需要配置的寄存器。
1.寄存器R0(00h),该寄存器用于控制WM8978的软复位,写任意值到该寄存器地址,即
可实现WM8978的软复位。
2.寄存器R1(01h),该寄存器需要设置VMIDSEL(bit[1:0])为2’b11,开启最快启动;
BUFIOEN(bit2)为1,避免输入输出直接在WM8978内部环回;BIASEN(bit3)为1,模拟部分
的放大器才会工作,才可以听到声音,PLLEN(bit5)为1使能WM8978内部PLL功能,使WM8978
内部的主时钟为12.288MHz。
3.寄存器R2(02h),该寄存器需要设置ROUT1EN(bit8)、LOUT1EN(bit7)为1,使能耳机输
出;BOOSTENR(bit5)、BOOSTENL(bit4)为1,使能左右声道进入BOOST;ADCENR(bit1)、
ADCENL(bit0)为1,使能左右声道的ADC功能。
4.寄存器R3(03h),该寄存器要设置LOUT2EN(bit6),ROUT2EN(bit5),RMIXER(bit3),
LMIXER(bit2),DACENR(bit1)和DACENL(bit0)等6个位为1。LOUT2EN和ROUT2EN,设置为1,使
能喇叭输出;LMIXER和RMIXER设置为1,使能左右声道混合器;DACENL和DACENR则是使能左右
声道的DAC,使数字音频信号转换为模拟音频信号。
5.寄存器R4(04h),该寄存器要设置WL(bit[6:5])和FMT(bit[4:3]) 4个位。WL(bit[6:5])
用于设置字长(即设置音频数据有效位数),00表示16位音频,10表示24位音频;FMT(bit[4:3])
用于设置音频接口数据传输格式,我们设置为10,使用I2S音频数据格式传输音频数据。
6.寄存器R6(06h),该寄存器的MS(bit0)设置为1,使WM8978工作在主模式下,输出BCLK
和LRC给FPGA。
7.寄存器R7(07h),该寄存器我们要设置采样率SR(bit[3:1])为000,使用48kHz的采
样率;设置SLOWCLKEN(bit0)为1,使能零交叉功能。
8.寄存器R10(0Ah),该寄存器我们要设置DACOSR128(bit3)为1,DAC得到最好的SNR。
9.寄存器R14(0Eh),该寄存器我们要设置ADCOSR128(bit3)为1,ADC得到最好的SNR。
10.寄存器R43(2Bh),该寄存器我们只需要设置INVROUT2(bit4)为1即可,反转ROUT2
输出,更好的驱动喇叭。
11.寄存器R47(2Fh)和寄存器R48(30h),这两个寄存器设置类似,一个用于设置左声
道(R47)输入增益(bit[6:4]),另外一个用于设置右声道(R48)输入增益(bit[6:4])。
12.寄存器R49(31h),该寄存器我们要设置SPKBOOST(bit2)和TSDEN(bit1)这两个位。
SPKBOOST用于设置喇叭的增益,我们设置为1(gain=+1.5)以获得更大的声音;TSDEN用于设
置过热保护,设置为1(开启)即可。
13.寄存器R50(32h)和 R51(33h),这两个寄存器一个用于设置左声道(R50),另外
一个用于设置右声道(R51)。我们只需要设置这两个寄存器的最低位为1即可,将左右声道的
DAC输出接入左右声道混合器里面,才能在耳机/喇叭听到音乐。
14.寄存器R52(34h)和R53(35h),这两个寄存器用于设置耳机音量,同样一个用于设
置左声道(R52),另外一个用于设置右声道(R53)。这两个寄存器的最高位(HPVU)用于设
置是否更新左右声道的音量,最低6位用于设置左右声道的音量,我们可以先设置好两个寄存器的音量值,最后设置其中一个寄存器最高位为1,即可更新音量设置。
15.寄存器R54(36h)和R55(37h),这两个寄存器用于设置喇叭音量,同R52,R53设置
一模一样,这里就不细说了。
以上,就是我们用WM8978播放音乐时的设置,按照以上所述,对各个寄存器进行相应的配
置,即可使用WM8978播放音乐了。还有其他一些3D设置,EQ设置等,我们这里就不再介绍了,
大家参考WM8978的数据手册自行研究下即可。
实验任务
本节实验任务是将电脑或手机的音乐通过开拓者开发板上的WM8978器件输出到FPGA,然后
FPGA通过WM8978器件输出给耳机和喇叭。
硬件设计
开拓者开发板上音频模块WM8978接口部分的原理图如图 32.3.1所示。
图 32.3.1 WM8978接口原理图
WM8978的MODE引脚接地,选择的是两线接口模式,等同于I2C接口。L2/GPIO2和R2/GPIO3
作为音频输入接口(LINE_IN),外部音频从此接口输入;LOUT1和ROUT1作为音频输出接口
(PHONE),输出给外接耳机。LOUT2和ROUT2为喇叭接口。
本实验中,各端口信号的管脚分配如下表所示:
表 32.3.1 音频环回实验管脚分配
程序设计
根据实验任务,我们可以大致规划出系统的控制流程:FPGA首先通过控制接口配置WM8978
相关的寄存器,然后接收WM8978传输过来的音频数据,并将接收到的音频数据传递给WM8978发
送出去。由此画出系统的功能框图如下所示:
图 32.4.1 WM8978音频环回实验系统框图
由系统框图可知,FPGA部分包括八个模块,顶层模块(audio_speak)、WM8978控制模块(wm8978_ctrl)、时钟分频模块(pll_clk)、音频接收模块(audio_receive)、音频发送模
块(audio_send)、WM8978配置模块(wm8978_config)、I2C配置模块(i2c_reg_cfg)和I2C
驱动模块(i2c_dri)。各模块的功能如下:
顶层模块(audio_speak):顶层模块完成了对WM8978控制模块和时钟分频模块的例化。
并将音频接收模块接收到的音频数据adc_data连接至音频发送模块的数据端口dac_data,实现
信号的交互连接。顶层模块的原理图如下图所示:
图 32.4.2 顶层模块原理图
时钟分频模块(pll_clk):时钟分频模块即锁相环(PLL)模块,调用锁相环(PLL)IP
核以产生12Mhz时钟作为WM8978的主时钟MCLK。
WM8978控制模块(wm8978_ctrl):WM8978控制模块例化了WM8978配置模块,音频接收模
块和音频发送模块。WM8978控制模块是对WM8978器件底层操作的封装,使用WM8978时例化此模
块即可。WM8978控制模块的原理图如下图所示:
图 32.4.3 WM8978控制模块原理图
音频接收模块(audio_receive):音频接收模块是接收来自WM8978的音频数据,并将WM8978
串行输入的音频数据进行并行处理。
音频发送模块(audio_send):音频发送模块是将FPGA输出的并行音频数据串行输出至
WM8978。
WM8978配置模块(wm8978_config):WM8978配置模块例化了I2C驱动模块(i2c_dri)和
I2C配置模块(i2c_reg_cfg),其内部端口及信号连接如下图所示:
图 32.4.4 WM8978配置模块原理图
I2C驱动模块(i2c_dri):因为WM8978的两线控制接口的时序与I2C时序相同,所以我们
这里调用了“EEPROM实验”的I2C驱动模块,实现FPGA对WM8978控制接口的操作。
I2C配置模块(i2c_reg_cfg):主要完成对WM8978相关寄存器的配置。
顶层模块的代码如下:
1 module audio_speak(
2 //system clock 50MHz
3 input sys_clk , // 系统时钟(50MHz)
4 input sys_rst_n , // 系统复位
5
6 //wm8978 interface
7 //audio interface(master mode)
8 input aud_bclk , // WM8978位时钟
9 input aud_lrc , // 对齐信号
10 input aud_adcdat, // 音频输入
11 output aud_mclk , // WM8978的主时钟(最大为12.288MHz)
12 output aud_dacdat, // 音频输出
13 //control interface
14 output aud_scl , // WM8978的SCL信号
15 inout aud_sda // WM8978的SDA信号
16 );
17
18 //wire define
19 wire [ 31: 0] adc_data; // FPGA采集的音频数据
20
21 //*****************************************************
22 //** main code
23 //*****************************************************
24
25 //例化pll_clk
26 pll_clk u_pll_clk(
27 . areset (~ sys_rst_n), // pll_clk异步复位信号
28 . inclk0 ( sys_clk ), // 输入sys_clk = 50 MHZ
29 . c0 ( aud_mclk ) // WM8978的MCLK信号(12MHz)
30 );
31
32 //例化WM89878控制模块
33 wm8978_ctrl u_wm8978_ctrl(
34 //system clock
35 . clk ( sys_clk ), // 时钟信号
36 . rst_n ( rst_n ), // 复位信号
37 //wm8978 interface
38 //audio interface(master mode)
39 . aud_bclk ( aud_bclk ), // WM8978位时钟
40 . aud_lrc ( aud_lrc ), // 对齐信号
41 . aud_adcdat ( aud_adcdat ), // 音频输入
42 . aud_dacdat ( aud_dacdat ), // 音频输出
43 //control interface
44 . aud_scl ( aud_scl ), // WM8978的SCL信号
45 . aud_sda ( aud_sda ), // WM8978的SDA信号
46 //user interface
47 . dac_data ( adc_data ), // 输出的音频数据
48 . adc_data ( adc_data ), // 输入的音频数据
49 . rx_done (), // 一次接收完成
50 . tx_done () // 一次发送完成
51 );
52
53 endmodule
顶层模块中主要完成对其余模块的例化。程序中第29行的c0是经锁相环(PLL)分频输出
的WM8978的主时钟。第48行是FPGA串转并处理后的音频数据,这里我们直接把该端口的音频数
据信号连到第47行的音频数据端口dac_data,实现音频数据的环回。当然了也可以直接将音频
输入信号aud_adcdat直接连接至第42行的音频输出端口aud_dacdat,来实现音频环回,但这样
处理就不能测试音频接收模块和音频发送模块的功能是否正确。
WM8978控制模块的代码如下所示:
1 module wm8978_ctrl(
2 //system clock
3 input clk , // 时钟信号
4 input rst_n , // 复位信号
5
6 //wm8978 interface
7 //audio interface(master mode)
8 input aud_bclk , // WM8978位时钟
9 input aud_lrc , // 对齐信号
10 input aud_adcdat , // 音频输入
11 output aud_dacdat , // 音频输出
12 //control interface
13 output aud_scl , // WM8978的SCL信号
14 inout aud_sda , // WM8978的SDA信号
15
16 //user interface
17 input [ 31: 0] dac_data , // 输出的音频数据
18 output [ 31: 0] adc_data , // 录音的数据
19 output rx_done , // 一次接收完成
20 output tx_done // 一次发送完成
21 );
22
23 //parameter define
24 parameter WL = 6'd32; // word length音频字长定义
25
26 //*****************************************************
27 //** main code
28 //*****************************************************
29
30 //例化wm8978_config,配置WM8978的寄存器
31 wm8978_config #(. WL( WL)) u_wm8978_config(
32 //system clock
33 . clk ( clk ), // 时钟信号
34 . rst_n ( rst_n ), // 复位信号
35 //wm8978 interface
36 . aud_scl ( aud_scl ), // WM8978的SCL时钟
37 . aud_sda ( aud_sda ) // WM8978的SDA信号
38 //user interface
39 );
40
41 //例化audio_receive,FPGA接收WM8978的音频数据
42 audio_receive #(. WL( WL)) u_audio_receive(
43 //system reset
44 . rst_n ( rst_n ), // 复位信号
45 //wm8978 interface
46 . aud_bclk ( aud_bclk), // WM8978位时钟
47 . aud_lrc ( aud_lrc ), // 对齐信号
48 . aud_adcdat( aud_adcdat), // 音频输入
49 //user interface
50 . rx_done ( rx_done ), // FPGA接收数据完成
51 . adc_data ( adc_data) // FPGA接收的数据
52 );
53
54 //例化audio_send,FPGA向WM8978传送音频数据
55 audio_send #(. WL( WL)) u_audio_send(
56 //system reset
57 . rst_n ( rst_n ), // 复位信号
58 //wm8978 interface
59 . aud_bclk ( aud_bclk ), // WM8978位时钟
60 . aud_lrc ( aud_lrc ), // 对齐信号
61 . aud_dacdat( aud_dacdat), // 音频数据输出
62 //user interface
63 . dac_data ( dac_data ), // 预输出的音频数据
64 . tx_done ( tx_done ) // 发送完成信号
65 );
66
67 endmodule
WM8978控制模块主要完成对WM8978器件层代码的封装,这样当操作WM8978时,只需例化
此模块即可实现WM8978的寄存器配置、音频接收、音频发送的功能。代码第24行的参数WL即音
频字长(word length),当用于接收、发送不同字长的音频数据时,可修改此参数。
WM8978音频配置模块的代码如下所示:
1 module wm8978_config(
2 //system clock
3 input clk , // 时钟信号
4 input rst_n , // 复位信号
5
6 //wm8978 interface
7 output aud_scl , // WM8978的SCL时钟
8 inout aud_sda // WM8978的SDA信号
9
10 //user interface
11 );
12
13 //parameter define
14 parameter SLAVE_ADDR = 7'h1a ; // 器件地址
15 parameter WL = 6'd32 ; // word length音频字长参数设置
16 parameter BIT_CTRL = 1'b0 ; // 字地址位控制参数(16b/8b)
17 parameter CLK_FREQ = 26'd50_000_000; // i2c_dri模块的驱动时钟频率(CLK_FREQ)
18 parameter I2C_FREQ = 18'd250_000 ; // I2C的SCL时钟频率
19
20 //wire define
21 wire clk_i2c ; // i2c的操作时钟
22 wire i2c_exec ; // i2c触发控制
23 wire i2c_rh_wl ; // I2C读写控制信号
24 wire i2c_done ; // i2c操作结束标志
25 wire cfg_done ; // WM8978配置完成标志
26 wire [ 15: 0] reg_data ; // WM8978需要配置的寄存器(地址及数据)
27
28 //*****************************************************
29 //** main code
30 //*****************************************************
31
32 //例化i2c_dri,调用IIC协议
33 i2c_dri #(
34 . SLAVE_ADDR ( SLAVE_ADDR), // slave address从机地址,放此处方便参数传递
35 . CLK_FREQ ( CLK_FREQ ), // i2c_dri模块的驱动时钟频率(CLK_FREQ)
36 . I2C_FREQ ( I2C_FREQ ) // I2C的SCL时钟频率
37 ) u_i2c_dri(
38 //global clock
39 . clk ( clk ), // i2c_dri模块的驱动时钟(CLK_FREQ)
40 . rst_n ( rst_n ), // 复位信号
41 //i2c interface
42 . i2c_exec ( i2c_exec ), // I2C触发执行信号
43 . bit_ctrl ( BIT_CTRL ), // 器件地址位控制(16b/8b)
44 . i2c_rh_wl ( i2c_rh_wl ), // I2C读写控制信号
45 . i2c_addr ( reg_data[ 15: 8]), // I2C器件字地址
46 . i2c_data_w ( reg_data[ 7: 0]), // I2C要写的数据
47 . i2c_data_r (), // I2C读出的数据
48 . i2c_done ( i2c_done ), // I 2C一次操作完成
49 . scl ( aud_scl ), // I2C的SCL时钟信号
50 . sda ( aud_sda ), // I2C的SDA信号
51 //user interface
52 . dri_clk ( clk_i2c ) // I2C操作时钟
53 );
54
55 //例化i2c_reg_cfg模块,配置WM8978的寄存器
56 i2c_reg_cfg #(. WL( WL) // word length音频字长参数设置
57 ) u_i2c_reg_cfg(
58 //clock & reset
59 . clk ( clk_i2c ), // i2c_reg_cfg驱动时钟(一般取1MHz)
60 . rst_n ( rst_n ), // 复位信号
61 //i2c interface
62 . i2c_done ( i2c_done ), // I2C一次操作完成的反馈信号
63 . i2c_exec ( i2c_exec ), // I2C触发执行信号
64 . i2c_rh_wl ( i2c_rh_wl), // i2c读写控制
65 . cfg_done ( cfg_done ), // WM8978配置完成
66 . i2c_data ( reg_data ) // 寄存器数据(7位地址+9位数据)
67 );
68
69 endmodule
WM8978音频配置模块主要完成对I2C驱动模块和寄存器配置模块的例化。程序中第15行的
WL参数用于音频字长设置,也即音频的采样后的量化位数。音频字长可选择为16bit、20bit、
24bit或32bit,量化位数越多,声音的质量越高,这里我们采用32bit。
其中I2C驱动模块(i2c_dri)程序与“EEPROM读写实验”章节中的IIC驱动模块(i2c_dri)
程序完全相同。有关IIC驱动模块的详细介绍请大家参考“EEPROM读写实验”。
I2C配置模块的代码如下:
1 module i2c_reg_cfg (
2 input clk , // i2c_reg_cfg驱动时钟(一般取1MHz)
3 input rst_n , // 复位信号
4 input i2c_done , // I2C一次操作完成反馈信号
5 output reg i2c_exec , // I2C触发执行信号
6 output reg cfg_done , // WM8978配置完成
7 output reg [ 15: 0] i2c_data // 寄存器数据(7位地址+9位数据)
8 );
9
10 //parameter define
11 parameter WL = 6'd32; // word length音频字长参数设置
12
13 //parameter define
14 localparam REG_NUM = 5'd19; // 总共需要配置的寄存器个数
15 localparam PHONE_VOLUME = 6'd30; // 耳机输出音量大小参数(0~63)
16 localparam SPEAK_VOLUME = 6'd45; // 喇叭输出音量大小参数(0~63)
17
18 //reg define
19 reg [ 1: 0] wl ; // word length音频字长参数定义
20 reg [ 7: 0] start_init_cnt; // 初始化延时计数器
21 reg [ 4: 0] init_reg_cnt ; // 寄存器配置个数计数器
22
23 //*****************************************************
24 //** main code
25 //*****************************************************
26
27 //音频字长(位数)参数设置
28 always @(posedge clk or negedge rst_n) begin
29 if(! rst_n)
30 wl <= 2'b00;
31 else begin
32 case( WL)
33 6'd16: wl <= 2'b00;
34 6'd20: wl <= 2'b01;
35 6'd24: wl <= 2'b10;
36 6'd32: wl <= 2'b11;
37 default:
38 wl <= 2'd00;
39 endcase
40 end
41 end
42
43 //上电或复位后延时一段时间
44 always @(posedge clk or negedge rst_n) begin
45 if(! rst_n)
46 start_init_cnt <= 8'd0;
47 else if( start_init_cnt < 8'hff)
48 start_init_cnt <= start_init_cnt + 1'b1;
49 end
50
51 //触发I2C操作
52 always @(posedge clk or negedge rst_n) begin
53 if(! rst_n)
54 i2c_exec <= 1'b0;
55 else if( init_reg_cnt == 5'd0 & start_init_cnt == 8'hfe)
56 i2c_exec <= 1'b1;
57 else if( i2c_done && init_reg_cnt < REG_NUM)
58 i2c_exec <= 1'b1;
59 else
60 i2c_exec <= 1'b0;
61 end
62
63 //配置寄存器计数
64 always @(posedge clk or negedge rst_n) begin
65 if(! rst_n)
66 init_reg_cnt <= 5'd0;
67 else if( i2c_exec)
68 init_reg_cnt <= init_reg_cnt + 1'b1;
69 end
70
71 //寄存器配置完成信号
72 always @(posedge clk or negedge rst_n) begin
73 if(! rst_n)
74 cfg_done <= 1'b0;
75 else if( i2c_done & ( init_reg_cnt == REG_NUM) )
76 cfg_done <= 1'b1;
77 end
78
79 //配置I2C器件内寄存器地址及其数据
80 always @(posedge clk or negedge rst_n) begin
81 if(! rst_n)
82 i2c_data <= 16'b0;
83 else begin
84 case( init_reg_cnt)
85 // R0,软复位
86 5'd0 : i2c_data <= { 7'd0 , 9'b1};
87 // R1,设置VMIDSEL,BUFIOEN,BIASEN,PLLEN,BUFDCOPEN
88 5'd1 : i2c_data <= { 7'd1 , 9'b1_0010_1111};
89 // R2,使能BOOSTENR,BOOSTENL和ADCENR/L;使能ROUT1,LOUT1
90 5'd2 : i2c_data <= { 7'd2 , 9'b1_1011_0011};
91 // R3,LOUT2,ROUT2输出使能(喇叭工作),RMIX,LMIX,DACENR、DACENL使能
92 5'd3: i2c_data <= { 7'd3 , 9'b0_0110_1111};
93 // R4,配置wm8978音频接口数据为I2S格式(bit4:3),字长度(wl)
94 5'd4 : i2c_data <= { 7'd4 ,{ 2'd0, wl, 5'b10000}};
95 // R6,设置为MASTER MODE(BCLK和LRC输出)
96 5'd5 : i2c_data <= { 7'd6 , 9'b0_0000_0001};
97 // R7,使能slow clock,采样率为48KHz(bit3:1)
98 5'd6 : i2c_data <= { 7'd7 , 9'b0_0000_0001};
99 // R10,设置DAC过采样率为128x(bit3),以实现最佳信噪比
100 5'd7 : i2c_data <= { 7'd10, 9'b0_0000_1000};
101 // R14,设置ADC过采样率为128x(bit3),以达到最佳信噪比
102 5'd8 : i2c_data <= { 7'd14, 9'b1_0000_1000};
103 // R43,INVROUT2(bit4)反向,驱动喇叭
104 5'd9 : i2c_data <= { 7'd43, 9'b0_0001_0000};
105 // R47,左通道输入增益控制,L2_2BOOSTVOL(bit6:4)
106 5'd10: i2c_data <= { 7'd47, 9'b0_0111_0000};
107 // R48,右通道输入增益控制
108 5'd11: i2c_data <= { 7'd48, 9'b0_0111_0000};
109 // R49,TSDEN(bit0),开启过热保护;SPKBOOST(bit2)1.5倍增益
110 5'd12: i2c_data <= { 7'd49, 9'b0_0000_0110};
111 // R50,选择左DAC输出至左输出混合器(bit0)
112 5'd13: i2c_data <= { 7'd50, 9'b1};
113 // R51,选择右DAC输出至右输出混合器(bit0)
114 5'd14: i2c_data <= { 7'd51, 9'b1};
115 // R52,耳机左声道音量设置(bit5:0),使能零交叉(bit7)
116 5'd15: i2c_data <= { 7'd52,{ 3'b010, PHONE_VOLUME}};
117 // R53,耳机右声道音量设置(bit5:0),使能零交叉(bit7),同步更新(HPVU=1)
118 5'd16: i2c_data <= { 7'd53,{ 3'b110, PHONE_VOLUME}};
119 // R54,喇叭左声道音量设置(bit5:0),使能零交叉(bit7)
120 5'd17: i2c_data <= { 7'd54,{ 3'b010, SPEAK_VOLUME}};
121 // R55,喇叭右声道音量设置(bit5:0),使能零交叉(bit7),同步更新(SPKVU=1)
122 5'd18: i2c_data <= { 7'd55,{ 3'b110, SPEAK_VOLUME}};
123 default : ;
124 endcase
125 end
126 end
127
128 endmodule
I2C配置模块主要完成对WM8978的寄存器配置。WM8978开始上电时电压有可能不够稳定,
所以程序中定义了一个延时计数器(start_init_cnt)等待WM8978工作在稳定的状态。当计数
器计数到预设值之后,开始第一次配置即软复位,目的是让所有的寄存器复位到默认的状态。
发送完软件复位命令后,紧接着配置剩下的寄存器。在代码的第14行定义了总共需要配置的寄
存器的个数,如果增加或者删减了寄存器的配置,需要修改此参数。第15行和第16行分别为耳
机和喇叭的音量参数,调整耳机和喇叭的音量可修改此参数。
音频接收模块代码如下:
1 module audio_receive #(parameter WL = 6'd32) ( // WL(word length音频字长定义)
2 //system clock 50MHz
3 input rst_n , // 复位信号
4
5 //wm8978 interface
6 input aud_bclk , // WM8978位时钟
7 input aud_lrc , // 对齐信号
8 input aud_adcdat, // 音频输入
9
10 //user interface
11 output reg rx_done , // FPGA接收数据完成
12 output reg [ 31: 0] adc_data // FPGA接收的数据
13 );
14
15 //reg define
16 reg aud_lrc_d0; // aud_lrc延迟一个时钟周期
17 reg [ 5: 0] rx_cnt; // 发送数据计数
18 reg [ 31: 0] adc_data_t; // 预输出的音频数据的暂存值
19
20 //wire define
21 wire lrc_edge ; // 边沿信号
22
23 //*****************************************************
24 //** main code
25 //*****************************************************
26
27 assign lrc_edge = aud_lrc ^ aud_lrc_d0; // LRC信号的边沿检测
28
29 //为了在aud_lrc变化的第二个AUD_BCLK上升沿采集aud_adcdat,延迟打拍采集
30 always @(posedge aud_bclk or negedge rst_n) begin
31 if(! rst_n) begin
32 aud_lrc_d0 <= 1'b0;
33 end
34 else
35 aud_lrc_d0 <= aud_lrc;
36 end
37
38 //采集32位音频数据的计数
39 always @(posedge aud_bclk or negedge rst_n) begin
40 if(! rst_n) begin
41 rx_cnt <= 6'd0;
42 end
43 else if( lrc_edge == 1'b1)
44 rx_cnt <= 6'd0;
45 else if( rx_cnt < 6'd35)
46 rx_cnt <= rx_cnt + 1'b1;
47 end
48
49 //把采集到的音频数据临时存放在一个寄存器内
50 always @(posedge aud_bclk or negedge rst_n) begin
51 if(! rst_n) begin
52 adc_data_t <= 32'b0;
53 end
54 else if( rx_cnt < WL)
55 adc_data_t[ WL - 1'd1 - rx_cnt] <= aud_adcdat;
56 end
57
58 //把临时数据传递给adc_data,并使能rx_done,表明一次采集完成
59 always @(posedge aud_bclk or negedge rst_n) begin
60 if(! rst_n) begin
61 rx_done <= 1'b0;
62 adc_data <= 32'b0;
63 end
64 else if( rx_cnt == 6'd32) begin
65 rx_done <= 1'b1;
66 adc_data<= adc_data_t;
67 end
68 else
69 rx_done <= 1'b0;
70 end
71
72 endmodule
音频接收模块的时钟信号是WM8978的位时钟信号aud_bclk,所以在程序的第3行我们只定
义了rst_n信号。
第27行的assign语句和第30行的always语句实现了aud_lrc信号的双边沿检测。第39行和
第50行的always语句实现了音频信号 aud_adcdat在aud_lrc信号变化后的第二个 BCLK
(aud_bclk)的上升沿采样,以及把音频信号的最高有效位放在adc_data_t的最高位。
音频发送模块的代码如下:
1 module audio_send #(parameter WL = 6'd32) ( // WL(word length音频字长定义)
2 //system reset
3 input rst_n , // 复位信号
4
5 //wm8978 interface
6 input aud_bclk , // WM8978位时钟
7 input aud_lrc , // 对齐信号
8 output reg aud_dacdat, // 音频数据输出
9 //user interface
10 input [ 31: 0] dac_data , // 预输出的音频数据
11 output reg tx_done // 发送一次音频位数完成
12 );
13
14 //reg define
15 reg aud_lrc_d0; // aud_lrc延迟一个时钟周期
16 reg [ 5: 0] tx_cnt; // 发送数据计数
17 reg [ 31: 0] dac_data_t; // 预输出的音频数据的暂存值
18
19 //wire define
20 wire lrc_edge; // 边沿信号
21
22 //*****************************************************
23 //** main code
24 //*****************************************************
25
26 assign lrc_edge = aud_lrc ^ aud_lrc_d0; // LRC信号的边沿检测
27
28 //为了在aud_lrc变化的第二个AUD_BCLK上升沿采集aud_adcdat,延迟打拍采集
29 always @(posedge aud_bclk or negedge rst_n) begin
30 if(! rst_n) begin
31 aud_lrc_d0 <= 1'b0;
32 end
33 else
34 aud_lrc_d0 <= aud_lrc;
35 end
36
37 //发送32位音频数据的计数
38 always @(posedge aud_bclk or negedge rst_n) begin
39 if(! rst_n) begin
40 tx_cnt <= 6'd0;
41 dac_data_t <= 32'd0;
42 end
43 else if( lrc_edge == 1'b1) begin
44 tx_cnt <= 6'd0;
45 dac_data_t <= dac_data;
46 end
47 else if( tx_cnt < 6'd35)
48 tx_cnt <= tx_cnt + 1'b1;
49 end
50
51 //发送完成信号
52 always @(posedge aud_bclk or negedge rst_n) begin
53 if(! rst_n) begin
54 tx_done <= 1'b0;
55 end
56 else if( tx_cnt == 6'd32)
57 tx_done <= 1'b1;
58 else
59 tx_done <= 1'b0;
60 end
61
62 //把预发送的音频数据串行发送出去
63 always @(negedge aud_bclk or negedge rst_n) begin
64 if(! rst_n) begin
65 aud_dacdat <= 1'b0;
66 end
67 else if( tx_cnt < WL)
68 aud_dacdat <= dac_data_t[ WL - 1'd1 - tx_cnt];
69 else
70 aud_dacdat <= 1'b0;
71 end
72
73 endmodule
音频发送模块与音频接收模块基本相同,有一点区别是程序第63行我们使用的是aud_bclk
的下降沿。这是为了能够让WM8978正确采集到I2S总线上的音频数据,即aud_lrc信号变化后的
第二个BCLK(aud_bclk)的上升沿采样到FPGA发送的音频信号aud_dacdat。
图 32.4.5是为音频环回过程中SignalTap抓取到的波形图。
从图中可以看到音频环回模块的aud_lrc信号的边沿检测信号lrc_edge,在aud_lrc变化后的第一个aud_bclk的低电平时拉高。
当FPGA接收音频时,在接下来的aud_bclk的第一个上升沿rx_cnt变为00h,在第二个上升沿时
采集WM8978发送过来的音频数据。当FPGA发送音频时,在接下来的aud_bclk的第一个下降沿传
输音频数据给WM8978,在第二个上升沿时WM8978采集FPGA发送的音频数据。这样就满足了I2S
信号传输格式的要求。
图 32.4.5 音频环回过程中 SignalTap抓取的波形图
下载验证
首先我们打开音频环回实验工程,在工程所在的路径下打开audio_speak/par文件夹,在
里面找到“audio_speak.qpf”并双击打开。注意工程所在的路径名只能由字母、数字以及下
划线组成,不能出现中文、空格以及特殊字符等。工程打开后如图 32.5.1所示。
图 32.5.1 音频环回实验工程
然后将下载器一端连电脑,另一端与开发板上对应端口连接,将音频连接线的一端连接至
电脑或手机的音频输出端口,另一端连接至WM8978的LINE_IN接口,然后将耳机连接至WM8978
的PHONE接口,最后连接电源线并打开电源开关。
开拓者开发板实物图如下所示:
图 32.5.2 开拓者开发板硬件图
接下来我们下载程序,验证WM8978的音频环回功能。
工程打开后通过点击工具栏中的“Programmer”图标打开下载界面,通过“Add File”按
钮选择audio_speak/par/output_files目录下的“audio_speak.sof”文件。开发板电源打开
后,在程序下载界面点击“Hardware Setup”,在弹出的对话框中选择当前的硬件连接为“USB-
Blaster[USB-0]”。然后点击“Start”将工程编译完成后得到的sof文件下载到开发板中,如
图 32.5.3所示。
图 32.5.3 程序下载界面
下载完成后打开音频连接线一端连接的电脑或手机,播放一首音乐,这时听到喇叭播放音
乐,戴上耳机,也能听到播放的音乐,说明音频环回实验程序下载验证成功。
音响产品空间辐射(RE)整改案例分享
某音响产品的空间辐射(RE)测试不通过整改案例分析,下图-1是产品的空间辐射(RE)测试数据,产品在空间辐射测试时150MHZ~300MHz频段有较多的谐波干扰数据超标,导致产品EMC空间辐射测试不通过,如下图1,这是一个典型的时钟电路干扰波型:
干扰机制及原因分析
结合以上测试数据分析,导致辐射测试不通过的主要原因可能是产品电路设计中晶振或时钟信号之间的通信形成的干扰,在电路设计中晶振和时钟信号之间都要有匹配电路等措施;在电路中晶振及其相应时钟信号由于其周期特性,会因时钟边沿速率过快引起信号完整性和电磁兼容等问题,成为PCB的主要骚扰源,并产生较多谐波干扰,导致空间辐射测试超标。通常情况下,当PCB电路设计中有品振及时钟信号电路时,晶振线路布线设计时是否增加匹配电阻和去耦电容,或只加了去耦电容未加匹配电阻,以及时钟信号线上是否有增加匹配电阳及去耦电容对产生EMC河题,造成EMI干扰会很大,对EMI测试结果也有很大的影响。匹配电阻在电路设计中作用有阻抗匹配和减缓信号上升沿的作用。阻抗匹配因PCB上的布线都有一定阻抗,会因布线问题产生阻抗失配,引发信号的完整性产生电磁干扰即一般会采用源端串联一个电阻的方式进行匹配来保证信号的质量。减缓信号上升沿匹配电阻与电容的低通滤波电路,为反映信号响应速度,从而影响高频干扰,匹配电阻的取值最好在20~100Q之间,也可在确保信号质量的前提下其值越大越好。去耦电容主要应用于信号电路设计中,完成去耦、振荡/同步及时间常数的作用,振荡/同步:包括RC、LC振荡器及晶体的负载电容都属于振荡/同步。在PCB布局设计时,若要保证时钟电路有足够的去耦电容,就必须保证去耦电容的选取要满足预期的应用。自谐振频率需要考虑抑制时钟的谐波,通常我们都要考虑原始时钟频率的五次谐波。同时在高频电路中会因回路电感的影响,需要在PCB上放置有效容性的去耦电容来达到良好的去耦效果。在实际应用中通常采用大电容滤低频,小电容滤高频的原则来选取去耦电容值。
解决思路分析
结合以上原因根据测试数据和产品设计分析,可能是功放MCLK时钟信号干扰,我们整改时先将功放的MCLK时钟信号断开,测试时干扰仍然存在,测试数据变化不大,可以判断跟MCLK时钟信号关系不大;再次查看原理图,发现产品的是12.288MHz有源晶振,如上所述,有源晶振在没有匹配电路的情况下很有可能产生干扰,成为干扰源,于是怀疑是该晶振干扰,将晶振电源断开,150Mhz~300Mhz时钟干扰频点现象消失,基本可以确定是该有源晶振造成谐波干扰。在对该晶振部分电路进行近一步检查分析,发现该晶振下方没有铺地,附近的地面积也较少,这样该有源晶振本体产生谐波频率干扰,在没有相应的匹配电路和地回路的情况下,无法通过匹配是电路和大面积的地回路进行有效的去耦和滤波,导致造成EMC测试严重的谐波频率干扰,空间辐射测试不通过。
整改方法及对策
经以上分析,于是我们增加了如下对策后测试通过,整改对策如下图2,整改后测试数据如下图3所示:
1.在X1有源晶振外壳与周边增加接地连接;
2.C31位置滤波电容增加接地连接,15pF改为33pF电容;:
3.将功放I2C MCLK时钟信号R3位置电阻33Ω电阻改为100Q磁珠,靠近X1晶振位置12C MCLK信号引脚串600Q磁珠。
总结
对于这种典型的有源晶振、时钟信号整改,我们需要先了解其干扰机制和原理,定位其干扰路径、分析其受干扰电路或模块,找到问题原因所在,才是解决问题的关键。一般的处理方法如屏蔽、接地、旁路、去耦、阻抗调整、电容滤波等都是解决有源晶振、时钟信号谐波干扰的有效手段。往往我们在整改过程中,只有找到干扰途径和干扰源,才能采用最简单的方法、最低廉的成本来达到最有效的测试结果,通常情况下使用的整改方案越简单,实现起来就越容易,也越容易达成解决问题、降低成本的目的。
相关问答
HDMI里面5组线怎么接?
谢谢邀答!hdmi线在弱电中应用非常广泛,现在安防监控工程中拼接屏显示部分,除了用VGA,好多开始用高清HDMI线了,hdmi的在不断的升级,目前最新的hdmi2.0,hdmi...14...
ssd1306引脚功能定义?
SSD1306是一种OLED显示屏驱动芯片,它的引脚定义如下:1.VDD:电源正极,通常接3.3V或5V电源。2.VSS:电源负极,通常接地。3.SCL:串行时钟输入,用于I2C...S...
oled屏幕四个引脚介绍?
OLED屏幕一般有四个引脚,分别是VCC、GND、SCL、SDA。它们的作用如下:VCC:是电源引脚,连接正极电源。一般的OLED屏幕工作电压为3.3V或5V,具体电压值需要根据...
gm7130引脚功能?
GM7130是一款视频监控摄像头芯片,具有以下引脚功能:1.AVIN+/Y2:模拟视频输入引脚,可用于连接外部视频源。在PAL制式下,将CVBS的Y信号输入到该引脚。2.AV...
nodemcu引脚功能说明?
nodemcu是一款基于ESP8266芯片的开发板,它共有个引脚,其中包括了数字输入/输出引脚、模拟输入引脚、SPI、I2C、UART通信引脚等。以下是nodem引脚的功能说明:...
oled屏只接vcc和gnd会亮吗?
不会亮。仅仅连接VCC(电源)和GND(地)并不足以使OLED屏幕亮起。OLED(有机发光二极管)屏幕需要一个驱动电路来提供适当的信号和电压以激活和控制每个像...
mcv14a引脚功能及数据?
1.VDD:芯片电源正极。2.VSS:芯片电源负极。3.ISET:电流设置引脚,通过调整电流大小来控制LED亮度。4.DIN:串行数据输入引脚,用于输入要显示的数据。...
物理电源符号?
.VBB:B可以认为是三极管的基极B,一般是指电源正极。2.VCC:C可以认为是三极管的集电极Collector或者电路Circuit,一般是指电源正极。3.VDD:D可以认为是MOS...
谁能通俗易懂地讲解一下上拉电阻和下拉电阻的原理是什么?
很高兴能够看到和回答这个问题!上拉或下拉电阻(两者统称为"拉阻")的主要价值在于,具有不确定状态的信号线沿着固定为高(上)的电阻延伸。在本节中,我们...总的...
21844芯片各引脚参数?
21844芯片的引脚参数如下:1.VCC:供电电源,通常是5V或3.3V。2.GND:地线引脚,用于接地。3.XTAL1:外部晶振连接脚,通常连接一个4-20MHz的晶振。4.XTAL2...