音频环回实验

news/2024/12/2 12:53:55/

音频环回实验

一、WM8978简介

WM8978是一个低功耗、高质量的立体声多媒体数字信号编译码器,它结合了一个高质量的立体声音DAC和ADC,带有灵活的音频线输入、麦克风输入和音频输出处理

image-20230504144149219

WM8978内部有58个寄存器。每一个寄存器的地址位为7位,数据位为9位。可通过控制接口配置相应的寄存器以打开相应的通道或者使能相应的功能。

控制接口是一个可选的2线或3线结构。通过MODE引脚选择(MODE引脚接高电平时为3线接口模式、低电平时为2线接口模式)。

当控制接口为2线接口模式时,其时序图如下图所示:

image-20230504144725298

WM8979可以通过I2S或者PCM音频接口与FPGA进行音频数据传输。I2S(Inter-IC Sound)总线,又称为集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而定制的一种总线标准。

image-20230504145328331

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、开发板上的原理图

image-20230504150820660

端口的具体解释如下图:

image-20230504153043541

三、程序设计

1、实验任务:

将电脑或者手机上的音乐通过开拓者开发板上的WM8978器件输出到FPGA,然后通过WM8978器件输出给耳机和喇叭。

2、实验的系统框图:

image-20230504151239059

3、顶层模块:

image-20230504151451631

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/1nsmodule 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) beginif(!rst_n)wl <= 2'b00;else begincase(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;endcaseend
end//上电或复位后延时一段时间
always @(posedge clk or negedge rst_n) beginif(!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) beginif(!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;elsei2c_exec <= 1'b0;
end//配置寄存器计数
always @(posedge clk or negedge rst_n) beginif(!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) beginif(!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) beginif(!rst_n)i2c_data <= 16'b0;else begincase(init_reg_cnt)// R0,软复位5'd0 : i2c_data <= {7'd0 ,9'b1};// R1,设置VMIDSEL,BUFIOEN,BIASEN,PLLEN,BUFDCOPEN5'd1 : i2c_data <= {7'd1 ,9'b1_0010_1111};// R2,使能BOOSTENR,BOOSTENL和ADCENR/L;使能ROUT1,LOUT15'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 : ;endcaseend
endendmodule 

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) beginif(!rst_n) begindri_clk <=  1'b0;clk_cnt <= 10'd0;endelse if(clk_cnt == clk_divide[8:1] - 1'd1) beginclk_cnt <= 10'd0;dri_clk <= ~dri_clk;endelseclk_cnt <= clk_cnt + 1'b1;
end//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) beginif(!rst_n)cur_state <= st_idle;elsecur_state <= next_state;
end//组合逻辑判断状态转移条件
always @(*) beginnext_state = st_idle;case(cur_state)st_idle: begin                          //空闲状态if(i2c_exec) beginnext_state = st_sladdr;endelsenext_state = st_idle;endst_sladdr: beginif(st_done) beginif(bit_ctrl)                    //判断是16位还是8位字地址next_state = st_addr16;elsenext_state = st_addr8 ;endelsenext_state = st_sladdr;endst_addr16: begin                        //写16位字地址if(st_done) beginnext_state = st_addr8;endelse beginnext_state = st_addr16;endendst_addr8: begin                         //8位字地址if(st_done) beginif(wr_flag==1'b0)               //读写判断next_state = st_data_wr;elsenext_state = st_addr_rd;endelse beginnext_state = st_addr8;endendst_data_wr: begin                       //写数据(8 bit)if(st_done)next_state = st_stop;elsenext_state = st_data_wr;endst_addr_rd: begin                       //写地址以进行读数据if(st_done) beginnext_state = st_data_rd;endelse beginnext_state = st_addr_rd;endendst_data_rd: begin                       //读取数据(8 bit)if(st_done)next_state = st_stop;elsenext_state = st_data_rd;endst_stop: begin                          //结束I2C操作if(st_done)next_state = st_idle;elsenext_state = st_stop ;enddefault: next_state= st_idle;endcase
end//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin//复位初始化if(!rst_n) beginscl       <= 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;          //开始I2C7'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;                    end7'd37: scl     <= 1'b1;7'd38: begin                     //从机应答st_done <= 1'b1;     if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位    end   7'd39: beginscl <= 1'b0;cnt <= 1'b0;enddefault : ;endcaseendst_data_rd: begin                        //读取数据(8 bit)case(cnt)7'd0: sda_dir <= 1'b0;7'd1: begindata_r[7] <= sda_in;scl       <= 1'b1;end7'd3: scl  <= 1'b0;7'd5: begindata_r[6] <= sda_in ;dscl       <= 1'b1   ;end7'd7: scl  <= 1'b0;7'd9: begindata_r[5] <= sda_in;scl       <= 1'b1  ;end7'd11: scl  <= 1'b0;7'd13: begindata_r[4] <= sda_in;scl       <= 1'b1  ;end7'd15: scl  <= 1'b0;7'd17: begindata_r[3] <= sda_in;scl       <= 1'b1  ;end7'd19: scl  <= 1'b0;7'd21: begindata_r[2] <= sda_in;scl       <= 1'b1  ;end7'd23: scl  <= 1'b0;7'd25: begindata_r[1] <= sda_in;scl       <= 1'b1  ;end7'd27: scl  <= 1'b0;7'd29: begindata_r[0] <= sda_in;scl       <= 1'b1  ;end7'd31: scl  <= 1'b0;7'd32: beginsda_dir <= 1'b1;             sda_out <= 1'b1;end7'd33: scl     <= 1'b1;7'd34: st_done <= 1'b1;          //非应答7'd35: beginscl <= 1'b0;cnt <= 1'b0;i2c_data_r <= data_r;enddefault  :  ;endcaseendst_stop: begin                           //结束I2C操作case(cnt)7'd0: beginsda_dir <= 1'b1;             //结束I2Csda_out <= 1'b0;end7'd1 : scl     <= 1'b1;7'd3 : sda_out <= 1'b1;7'd15: st_done <= 1'b1;7'd16: begincnt      <= 1'b0;i2c_done <= 1'b1;            //向上层模块传递I2C结束信号enddefault  : ;endcaseendendcaseend
endendmodule

audio_receive模块:

module audio_receive #(parameter WL = 6'd32) (      // WL(word length音频字长定义)//system clock 50MHzinput                 rst_n     ,               // 复位信号//wm8978 interfaceinput                 aud_bclk  ,               // WM8978位时钟input                 aud_lrc   ,               // 对齐信号input                 aud_adcdat,               // 音频输入//user interfaceoutput   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) beginif(!rst_n)aud_lrc_d0 <= 1'b0;elseaud_lrc_d0 <= aud_lrc;
end//采集32位音频数据的计数
always @(posedge aud_bclk or negedge rst_n) beginif(!rst_n) beginrx_cnt <= 6'd0;endelse 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) beginif(!rst_n) beginadc_data_t <= 32'b0;endelse 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) beginif(!rst_n) beginrx_done   <=  1'b0;adc_data  <= 32'b0;endelse if(rx_cnt == 6'd32) beginrx_done <= 1'b1;adc_data<= adc_data_t;endelserx_done <= 1'b0;
endendmodule 

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 interfaceoutput          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 
	--晓凡 202355日于桂林书

http://www.ppmy.cn/news/59213.html

相关文章

22.碳交易机制下考虑需求响应的综合能源系统优化运行

说明书 MATLAB代码&#xff1a;碳交易机制下考虑需求响应的综合能源系统优化运行 注意&#xff1a;另外还有含义柔性负荷、蓄冷式空调、共享储能以及碳捕集的综合能源系统优化运行代码&#xff0c;欢迎咨询 关键词&#xff1a;需求响应 碳交易机制 日前优化调度 综合能源系统…

关于低代码开发,你是真的了解了吗?

在低代码开发已是大势所趋的今天&#xff0c;不少企业都切身感受到了低代码开发带来的便利。低代码开发平台的优势在当下数字化浪潮中&#xff0c;为企业提供了定制专属的数字化解决方案。 低代码本身没有太强的行业属性&#xff0c;这也让低代码开发平台能够更加灵活地适应不同…

pikachu靶场-Unsafe Filedownload

不安全的文件下载 文件下载功能在很多web系统上都会出现&#xff0c;一般我们当点击下载链接&#xff0c;便会向后台发送一个下载请求&#xff0c;一般这个请求会包含一个需要下载的文件名称&#xff0c;后台在收到请求后 会开始执行下载代码&#xff0c;将该文件名对应的文件…

2 行代码开启 SAST,将代码漏洞定位到具体行数

&#x1f4a1; 如何在流水线中集成与应用 SAST&#xff0c;实现自动化代码安全扫描 &#xff1f; 近日&#xff0c;在「DevSecOps软件安全开发实践」课程上&#xff0c;极狐(GitLab) 高级专业服务交付工程师欧阳希、极狐(GitLab) 后端工程师黄松&#xff0c;分享了静态安全扫描…

Android - 动画

一、概念 属性动画PeopertyAnimation&#xff1a;view的属性根据执行的动画发生真实的改变。通过不断设置 View 的属性实现。补间动画 ViewAnimation&#xff08;Tween&#xff09;&#xff1a;不改变view的位置和属性。基于 Framework的绘制转变。帧动画 DrawableAnimation&a…

【网络原理】TCP/IP协议

目录 1.应用层 2.传输层&#xff08;核心问题&#xff09; 2.1 UDP协议 2.1.2 UDP的特点 2.1.3 基于UDP的应用层协议 2.2 TCP协议&#xff08;重点内容&#xff09; 2.2.1 TCP/IP 协议含义 2.2.2 TCP协议端格式&#xff1a; 2.2.3 TCP的特点 2.3 TCP原理 2.4 确认应…

Vue3中双向数据绑定与Pinia实践+JS数据引用的循环修改问题

Vue3 Pinia VUE3虽然出了很久了&#xff0c;但是很少深入研究&#xff0c;目前项目上遇到了一些问题&#xff0c;所以做个Note解决一下疑问&#xff1a; v-bind/v-model怎么与Pinia进行结合Object/Array数据大量处理时&#xff0c;为何有的修改不生效组合API与选项API选择 (…

在Android应用中集成使用traceroute工具

背景知识 traceroute是一个常用于Linux系统的网络工具&#xff0c;它可显示数据包在IP网络中所经过路由的IP地址&#xff0c;理想状态下可探测本机和目标地址之间的所有路由节点。 其他操作系统中也有类似的替代品&#xff0c;实现都大同小异。一般用法如下&#xff1a; 终端…