音频环回实验
一、WM8978简介
WM8978是一个低功耗、高质量的立体声多媒体数字信号编译码器,它结合了一个高质量的立体声音DAC和ADC,带有灵活的音频线输入、麦克风输入和音频输出处理
WM8978内部有58个寄存器。每一个寄存器的地址位为7位,数据位为9位。可通过控制接口配置相应的寄存器以打开相应的通道或者使能相应的功能。
控制接口是一个可选的2线或3线结构。通过MODE引脚选择(MODE引脚接高电平时为3线接口模式、低电平时为2线接口模式)。
当控制接口为2线接口模式时,其时序图如下图所示:
WM8979可以通过I2S或者PCM音频接口与FPGA进行音频数据传输。I2S(Inter-IC Sound)总线,又称为集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而定制的一种总线标准。
WM8978支持主从两种工作模式,主从工作模式的区别在于BLCK和LRC由谁来控制。
在主模式下,WM8978作为主控设备,生产BCLK和LRC信号并输出;
在从模式下,BCLK和LRC信号由外部设备提供;
MCLK为主时钟输入接口,MCLK的频率为256*fs。fs为音频的采样率,一般为48KHz,所以MCLK为256×48=12288KHz=12.288MHz。我们一般使用FPGA内部的PLL分频得到12MHz的时钟信号,,然后通过配置WM8978内部的寄存器使其PLL输出12.288MHz的时钟信号。
二、硬件设计
1、开发板上的原理图
端口的具体解释如下图:
三、程序设计
1、实验任务:
将电脑或者手机上的音乐通过开拓者开发板上的WM8978器件输出到FPGA,然后通过WM8978器件输出给耳机和喇叭。
2、实验的系统框图:
3、顶层模块:
aud_Irc:左右数据的同步信号
aud_bclk:数据的同步时钟
aud_adcdat:数据adc
顶层代码:
module wm8978_ctrl(
input clk , // 时钟信号
input rst_n , // 复位信号
//audio interface(mast
input aud_bclk , // WM8978位时钟
input aud_lrc , // 对齐信号
input aud_adcdat , // 音频输入
output aud_dacdat , // 音频输出
//control interfac
output aud_scl , // WM8978的SCL信号
inout aud_sda , // WM8978的SDA信号
//user i
output [31:0] adc_data , // 输入的音频数据
input [31:0] dac_data , // 输出的音频数据
output rx_done , // 一次采集完成
output tx_done // 一次发送完成
);
//parameter define
parameter WL = 6'd32; // word length音频字长定义
//*****************************************************
//** main code
//*****************************************************
//例化WM8978寄存器配置模块
wm8978_config #(
.WL (WL)
) u_wm8978_config(
.clk (clk), // 时钟信号
.rst_n (rst_n), // 复位信号
.aud_scl (aud_scl), // WM8978的SCL时钟
.aud_sda (aud_sda) // WM8978的SDA信号
);
//例化WM8978音频接收模块
audio_receive #(
.WL (WL)
) u_audio_receive(
.rst_n (rst_n), // 复位信号
.aud_bclk (aud_bclk), // WM8978位时钟
.aud_lrc (aud_lrc), // 对齐信号
.aud_adcdat (aud_adcdat), // 音频输入
.adc_data (adc_data), // FPGA接收的数据
.rx_done (rx_done) // FPGA接收数据完成
);
//例化WM8978音频发送模块
audio_send #(
.WL (WL)
) u_audio_send(
.rst_n (rst_n), // 复位信号
.aud_bclk (aud_bclk), // WM8978位时钟
.aud_lrc (aud_lrc), // 对齐信号
.aud_dacdat (aud_dacdat), // 音频数据输出
.dac_data (dac_data), // 预输出的音频数据
.tx_done (tx_done) // 发送完成信号
);
endmodule
wm8978_config模块代码:
`timescale 1ns/1ns
module wm8978_config(
input clk , // 时钟信号
input rst_n , // 复位信号
output i2c_ack , // I2C应答标志 0:应答 1:未应答
output aud_scl , // WM8978的SCL时钟
inout aud_sda // WM8978的SDA信号
);
//parameter define
parameter SLAVE_ADDR = 7'h1a ; // 器件地址
parameter WL = 6'd32 ; // word length音频字长参数设置
parameter BIT_CTRL = 1'b0 ; // 字地址位控制参数(16b/8b)
parameter CLK_FREQ = 26'd50_000_000; // i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 ; // I2C的SCL时钟频率
//wire define
wire clk_i2c ; // i2c的操作时钟
wire i2c_exec ; // i2c触发控制
wire i2c_done ; // i2c操作结束标志
wire cfg_done ; // WM8978配置完成标志
wire [15:0] reg_data ; // WM8978需要配置的寄存器(地址及数据)
//*****************************************************
//** main code
//*****************************************************
//配置WM8978的寄存器
i2c_reg_cfg #(
.WL (WL ) // word length音频字长参数设置
) u_i2c_reg_cfg(
.clk (clk_i2c ), // i2c_reg_cfg驱动时钟
.rst_n (rst_n ), // 复位信号
.i2c_exec (i2c_exec ), // I2C触发执行信号
.i2c_data (reg_data ), // 寄存器数据(7位地址+9位数据)
.i2c_done (i2c_done ), // I2C一次操作完成的标志信号
.cfg_done (cfg_done ) // WM8978配置完成
);
//调用IIC协议
i2c_dri #(
.SLAVE_ADDR (SLAVE_ADDR), // slave address从机地址,放此处方便参数传递
.CLK_FREQ (CLK_FREQ ), // i2c_dri模块的驱动时钟频率(CLK_FREQ)
.I2C_FREQ (I2C_FREQ ) // I2C的SCL时钟频率
) u_i2c_dri(
.clk (clk ), // i2c_dri模块的驱动时钟(CLK_FREQ)
.rst_n (rst_n ), // 复位信号
.i2c_exec (i2c_exec ), // I2C触发执行信号
.bit_ctrl (BIT_CTRL ), // 器件地址位控制(16b/8b)
.i2c_rh_wl (1'b0 ), // I2C读写控制信号
.i2c_addr (reg_data[15:8]), // I2C器件字地址
.i2c_data_w (reg_data[ 7:0]), // I2C要写的数据
.i2c_data_r (), // I2C读出的数据
.i2c_done (i2c_done ), // I 2C一次操作完成
.i2c_ack (i2c_ack ), // I2C应答标志 0:应答 1:未应答
.scl (aud_scl ), // I2C的SCL时钟信号
.sda (aud_sda ), // I2C的SDA信号
.dri_clk (clk_i2c ) // I2C操作时钟
);
endmodule
i2c_reg_cfg模块:
module i2c_reg_cfg (
input clk , // i2c_reg_cfg驱动时钟(一般取1MHz)
input rst_n , // 复位信号
input i2c_done , // I2C一次操作完成反馈信号
output reg i2c_exec , // I2C触发执行信号
output reg cfg_done , // WM8978配置完成
output reg [15:0] i2c_data // 寄存器数据(7位地址+9位数据)
);
//parameter define
parameter WL = 6'd32; // word length音频字长参数设置
//parameter define
localparam REG_NUM = 5'd19; // 总共需要配置的寄存器个数
localparam PHONE_VOLUME = 6'd30; // 耳机输出音量大小参数(0~63)
localparam SPEAK_VOLUME = 6'd45; // 喇叭输出音量大小参数(0~63)
//reg define
reg [1:0] wl ; // word length音频字长参数定义
reg [7:0] start_init_cnt; // 初始化延时计数器
reg [4:0] init_reg_cnt ; // 寄存器配置个数计数器
//*****************************************************
//** main code
//*****************************************************
//音频字长(位数)参数设置
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
wl <= 2'b00;
else begin
case(WL)
6'd16: wl <= 2'b00;
6'd20: wl <= 2'b01;
6'd24: wl <= 2'b10;
6'd32: wl <= 2'b11;
default:
wl <= 2'd00;
endcase
end
end
//上电或复位后延时一段时间
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
start_init_cnt <= 8'd0;
else if(start_init_cnt < 8'hff)
start_init_cnt <= start_init_cnt + 1'b1;
end
//触发I2C操作
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
i2c_exec <= 1'b0;
else if(init_reg_cnt == 5'd0 & start_init_cnt == 8'hfe)
i2c_exec <= 1'b1;
else if(i2c_done && init_reg_cnt < REG_NUM)
i2c_exec <= 1'b1;
else
i2c_exec <= 1'b0;
end
//配置寄存器计数
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
init_reg_cnt <= 5'd0;
else if(i2c_exec)
init_reg_cnt <= init_reg_cnt + 1'b1;
end
//寄存器配置完成信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cfg_done <= 1'b0;
else if(i2c_done & (init_reg_cnt == REG_NUM) )
cfg_done <= 1'b1;
end
//配置I2C器件内寄存器地址及其数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
i2c_data <= 16'b0;
else begin
case(init_reg_cnt)
// R0,软复位
5'd0 : i2c_data <= {7'd0 ,9'b1};
// R1,设置VMIDSEL,BUFIOEN,BIASEN,PLLEN,BUFDCOPEN
5'd1 : i2c_data <= {7'd1 ,9'b1_0010_1111};
// R2,使能BOOSTENR,BOOSTENL和ADCENR/L;使能ROUT1,LOUT1
5'd2 : i2c_data <= {7'd2 ,9'b1_1011_0011};
// R3,LOUT2,ROUT2输出使能(喇叭工作),RMIX,LMIX,DACENR、DACENL使能
5'd3: i2c_data <= {7'd3 ,9'b0_0110_1111};
// R4,配置wm8978音频接口数据为I2S格式(bit4:3),字长度(wl)
5'd4 : i2c_data <= {7'd4 ,{2'd0,wl,5'b10000}};
// R6,设置为MASTER MODE(BCLK和LRC输出)
5'd5 : i2c_data <= {7'd6 ,9'b0_0000_0001};
// R7,使能slow clock,采样率为48KHz(bit3:1)
5'd6 : i2c_data <= {7'd7 ,9'b0_0000_0001};
// R10,设置DAC过采样率为128x(bit3),以实现最佳信噪比
5'd7 : i2c_data <= {7'd10,9'b0_0000_1000};
// R14,设置ADC过采样率为128x(bit3),以达到最佳信噪比
5'd8 : i2c_data <= {7'd14,9'b1_0000_1000};
// R43,INVROUT2(bit4)反向,驱动喇叭
5'd9 : i2c_data <= {7'd43,9'b0_0001_0000};
// R47,左通道输入增益控制,L2_2BOOSTVOL(bit6:4)
5'd10: i2c_data <= {7'd47,9'b0_0111_0000};
// R48,右通道输入增益控制
5'd11: i2c_data <= {7'd48,9'b0_0111_0000};
// R49,TSDEN(bit0),开启过热保护;SPKBOOST(bit2)1.5倍增益
5'd12: i2c_data <= {7'd49,9'b0_0000_0110};
// R50,选择左DAC输出至左输出混合器(bit0)
5'd13: i2c_data <= {7'd50,9'b1 };
// R51,选择右DAC输出至右输出混合器(bit0)
5'd14: i2c_data <= {7'd51,9'b1 };
// R52,耳机左声道音量设置(bit5:0),使能零交叉(bit7)
5'd15: i2c_data <= {7'd52,{3'b010,PHONE_VOLUME}};
// R53,耳机右声道音量设置(bit5:0),使能零交叉(bit7),同步更新(HPVU=1)
5'd16: i2c_data <= {7'd53,{3'b110,PHONE_VOLUME}};
// R54,喇叭左声道音量设置(bit5:0),使能零交叉(bit7)
5'd17: i2c_data <= {7'd54,{3'b010,SPEAK_VOLUME}};
// R55,喇叭右声道音量设置(bit5:0),使能零交叉(bit7),同步更新(SPKVU=1)
5'd18: i2c_data <= {7'd55,{3'b110,SPEAK_VOLUME}};
default : ;
endcase
end
end
endmodule
i2c_dri模块:
module i2c_dri
#(
parameter SLAVE_ADDR = 7'b1010000 , //EEPROM从机地址
parameter CLK_FREQ = 26'd50_000_000, //模块输入的时钟频率
parameter I2C_FREQ = 18'd250_000 //IIC_SCL的时钟频率
)
(
input clk ,
input rst_n ,
//i2c interface
input i2c_exec , //I2C触发执行信号
input bit_ctrl , //字地址位控制(16b/8b)
input i2c_rh_wl , //I2C读写控制信号
input [15:0] i2c_addr , //I2C器件内地址
input [ 7:0] i2c_data_w , //I2C要写的数据
output reg [ 7:0] i2c_data_r , //I2C读出的数据
output reg i2c_done , //I2C一次操作完成
output reg i2c_ack , //I2C应答标志 0:应答 1:未应答
output reg scl , //I2C的SCL时钟信号
inout sda , //I2C的SDA信号
//user interface
output reg dri_clk //驱动I2C操作的驱动时钟
);
//localparam define
localparam st_idle = 8'b0000_0001; //空闲状态
localparam st_sladdr = 8'b0000_0010; //发送器件地址(slave address)
localparam st_addr16 = 8'b0000_0100; //发送16位字地址
localparam st_addr8 = 8'b0000_1000; //发送8位字地址
localparam st_data_wr = 8'b0001_0000; //写数据(8 bit)
localparam st_addr_rd = 8'b0010_0000; //发送器件地址读
localparam st_data_rd = 8'b0100_0000; //读数据(8 bit)
localparam st_stop = 8'b1000_0000; //结束I2C操作
//reg define
reg sda_dir ; //I2C数据(SDA)方向控制
reg sda_out ; //SDA输出信号
reg st_done ; //状态结束
reg wr_flag ; //写标志
reg [ 6:0] cnt ; //计数
reg [ 7:0] cur_state ; //状态机当前状态
reg [ 7:0] next_state; //状态机下一状态
reg [15:0] addr_t ; //地址
reg [ 7:0] data_r ; //读取的数据
reg [ 7:0] data_wr_t ; //I2C需写的数据的临时寄存
reg [ 9:0] clk_cnt ; //分频时钟计数
//wire define
wire sda_in ; //SDA输入信号
wire [8:0] clk_divide ; //模块驱动时钟的分频系数
//*****************************************************
//** main code
//*****************************************************
//SDA控制
assign sda = sda_dir ? sda_out : 1'bz; //SDA数据输出或高阻
assign sda_in = sda ; //SDA数据输入
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//模块驱动时钟的分频系数
//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
//组合逻辑判断状态转移条件
always @(*) begin
next_state = st_idle;
case(cur_state)
st_idle: begin //空闲状态
if(i2c_exec) begin
next_state = st_sladdr;
end
else
next_state = st_idle;
end
st_sladdr: begin
if(st_done) begin
if(bit_ctrl) //判断是16位还是8位字地址
next_state = st_addr16;
else
next_state = st_addr8 ;
end
else
next_state = st_sladdr;
end
st_addr16: begin //写16位字地址
if(st_done) begin
next_state = st_addr8;
end
else begin
next_state = st_addr16;
end
end
st_addr8: begin //8位字地址
if(st_done) begin
if(wr_flag==1'b0) //读写判断
next_state = st_data_wr;
else
next_state = st_addr_rd;
end
else begin
next_state = st_addr8;
end
end
st_data_wr: begin //写数据(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_wr;
end
st_addr_rd: begin //写地址以进行读数据
if(st_done) begin
next_state = st_data_rd;
end
else begin
next_state = st_addr_rd;
end
end
st_data_rd: begin //读取数据(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_rd;
end
st_stop: begin //结束I2C操作
if(st_done)
next_state = st_idle;
else
next_state = st_stop ;
end
default: next_state= st_idle;
endcase
end
//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
//复位初始化
if(!rst_n) begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done <= 1'b0;
i2c_ack <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
i2c_data_r<= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
data_wr_t <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle: begin //空闲状态
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done<= 1'b0;
cnt <= 7'b0;
if(i2c_exec) begin
wr_flag <= i2c_rh_wl ;
addr_t <= i2c_addr ;
data_wr_t <= i2c_data_w;
i2c_ack <= 1'b0;
end
end
st_sladdr: begin //写地址(器件地址和字地址)
case(cnt)
7'd1 : sda_out <= 1'b0; //开始I2C
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0; //0:写
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[15]; //传送字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[7]; //字地址
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_wr: begin //写数据(8 bit)
case(cnt)
7'd0: begin
sda_out <= data_wr_t[7]; //I2C写8位数据
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_wr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_wr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_wr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_wr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_wr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_wr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_wr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr_rd: begin //写地址以进行读数据
case(cnt)
7'd0 : begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0; //重新开始
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1; //1:读
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin //从机应答
st_done <= 1'b1;
if(sda_in == 1'b1) //高电平表示未应答
i2c_ack <= 1'b1; //拉高应答标志位
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_rd: begin //读取数据(8 bit)
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;d
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1; //非应答
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
i2c_data_r <= data_r;
end
default : ;
endcase
end
st_stop: begin //结束I2C操作
case(cnt)
7'd0: begin
sda_dir <= 1'b1; //结束I2C
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1; //向上层模块传递I2C结束信号
end
default : ;
endcase
end
endcase
end
end
endmodule
audio_receive模块:
module audio_receive #(parameter WL = 6'd32) ( // WL(word length音频字长定义)
//system clock 50MHz
input rst_n , // 复位信号
//wm8978 interface
input aud_bclk , // WM8978位时钟
input aud_lrc , // 对齐信号
input aud_adcdat, // 音频输入
//user interface
output reg rx_done , // FPGA接收数据完成
output reg [31:0] adc_data // FPGA接收的数据
);
//reg define
reg aud_lrc_d0; // aud_lrc延迟一个时钟周期
reg [ 5:0] rx_cnt; // 发送数据计数
reg [31:0] adc_data_t; // 预输出的音频数据的暂存值
//wire define
wire lrc_edge ; // 边沿信号
//*****************************************************
//** main code
//*****************************************************
assign lrc_edge = aud_lrc ^ aud_lrc_d0; // LRC信号的边沿检测
//为了在aud_lrc变化的第二个AUD_BCLK上升沿采集aud_adcdat,延迟打拍采集
always @(posedge aud_bclk or negedge rst_n) begin
if(!rst_n)
aud_lrc_d0 <= 1'b0;
else
aud_lrc_d0 <= aud_lrc;
end
//采集32位音频数据的计数
always @(posedge aud_bclk or negedge rst_n) begin
if(!rst_n) begin
rx_cnt <= 6'd0;
end
else if(lrc_edge == 1'b1)
rx_cnt <= 6'd0;
else if(rx_cnt < 6'd35)
rx_cnt <= rx_cnt + 1'b1;
end
//把采集到的音频数据临时存放在一个寄存器内
always @(posedge aud_bclk or negedge rst_n) begin
if(!rst_n) begin
adc_data_t <= 32'b0;
end
else if(rx_cnt < WL)
adc_data_t[WL - 1'd1 - rx_cnt] <= aud_adcdat;
end
//把临时数据传递给adc_data,并使能rx_done,表明一次采集完成
always @(posedge aud_bclk or negedge rst_n) begin
if(!rst_n) begin
rx_done <= 1'b0;
adc_data <= 32'b0;
end
else if(rx_cnt == 6'd32) begin
rx_done <= 1'b1;
adc_data<= adc_data_t;
end
else
rx_done <= 1'b0;
end
endmodule
audio_speak模块
module audio_speak(
input sys_clk , // 系统时钟(50MHz)
input sys_rst_n , // 系统复位
//wm8978 audio interface (master mode)
input aud_bclk , // WM8978位时钟
input aud_lrc , // 对齐信号
input aud_adcdat, // 音频输入
output aud_mclk , // WM8978的主时钟
output aud_dacdat, // 音频输出
//wm8978 control interface
output aud_scl , // WM8978的SCL信号
inout aud_sda // WM8978的SDA信号
);
//wire define
wire [31:0] adc_data; // FPGA采集的音频数据
//*****************************************************
//** main code
//*****************************************************
//例化PLL,生成WM8978主时钟
pll_clk u_pll_clk(
.areset (~sys_rst_n), // pll_clk异步复位信号
.inclk0 (sys_clk ), // 输入sys_clk = 50 MHZ
.c0 (aud_mclk ) // WM8978的MCLK信号(12MHz)
);
//例化WM89878控制模块
wm8978_ctrl u_wm8978_ctrl(
.clk (sys_clk ), // 时钟信号
.rst_n (sys_rst_n ), // 复位信号
.aud_bclk (aud_bclk ), // WM8978位时钟
.aud_lrc (aud_lrc ), // 对齐信号
.aud_adcdat (aud_adcdat ), // 音频输入
.aud_dacdat (aud_dacdat ), // 音频输出
.aud_scl (aud_scl ), // WM8978的SCL信号
.aud_sda (aud_sda ), // WM8978的SDA信号
.adc_data (adc_data ), // 输入的音频数据
.dac_data (adc_data ), // 输出的音频数据
.rx_done (), // 一次接收完成
.tx_done () // 一次发送完成
);
endmodule
--晓凡 2023年5月5日于桂林书