音频环回实验

news2025/1/13 15:36:57

音频环回实验

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/491741.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud基础知识

1、什么是SpringCloud SpringCloud分布式微服务架构的一站式解决方案&#xff0c;是多种微服务架构落地技术的集合体&#xff08;微服务全家桶&#xff09;。 查看官网&#xff1a;https://spring.io/ Spring Cloud本身不是新的框架&#xff0c;是一个全家桶式的技术栈&…

销售管理系统哪种好?

一、如何选择销售管理系统 销售管理软件其实就是我们常说的CRM软件&#xff0c;在激烈的市场竞争下&#xff0c;传统的销售管理模式不能满足有效跟进和及时维护客户的需求&#xff0c;在挖掘新客户和增强客户忠诚度方面也出现了一定弊端&#xff0c;因此销售管理软件应运而生&…

作者等级与权益说明

「创收计划」3.0上线&#xff0c;全面助力资源优质创作者 创&#xff1a;代表创作者收&#xff1a;代表收获 截止目前&#xff0c;文库资源频道已开放20细分领域&#xff0c;每个领域又进行详细的细分&#xff0c;在每个细分的品类上&#xff0c;我们已经收获了来自广大创作者…

基于AT89C51单片机的简易计算器的设计与仿真

点击链接获取Keil源码与Project Backups仿真图&#xff1a; https://download.csdn.net/download/qq_64505944/87755299?spm1001.2014.3001.5503 源码获取 本设计是以单片机AT89C51为核心的简易计算器设计&#xff0c;要通过芯片AT89C51实现计算器程序运行来完成加、减、乘…

python--读取TRMM-3B43月平均降水绘制气候态空间分布图(陆地区域做掩膜)

python–读取TRMM-3B43月平均降水绘制气候态空间分布图&#xff08;陆地区域做掩膜&#xff09; 成果展示 TRMM降水数据介绍 热带降雨测量任务(The Tropical Rainfall Measuring Mission&#xff0c;TRMM)是美国国家航空航天局(NASA)和日本国家太空发展署(National Space Dev…

刚转岗做项目经理,无从下手,怎么办?

01 背景 最近在知乎平台看到一个问题是这么说的&#xff1a; 或许很多人都不是从工作开始就是项目专员再到项目经理这里一步一步过来&#xff0c;而是从其他岗位比如售前、销售、产品经理、程序员等转到项目经理岗位的。 那么对于这些人来说&#xff0c;做项目经理会有什么问…

第一次找实习, 什么项目可以给自己加分(笔记)

什么样的项目能简历加分、对找工作有帮助 基本特征&#xff1a; 一个特征是“硬核基础软件”&#xff0c;另一个为很实用的APP。 硬核基础软件 独立实现一个操作系统的kerne内核&#xff08;操作系统的内部引擎&#xff09; 北美计算机名校会让学生用一个学期的时间实现一个…

冒险岛私人服务器详细架设教程

冒险岛Online   《冒险岛Online》是由韩国WIZET和NEXON制作开发的一款2D横版卷轴网络游戏&#xff0c;于2004年7月24日在中国大陆正式上线&#xff0c;由盛大游戏负责运营。   故事以被“黑暗力量”不断入侵&#xff0c;因而进入了“浑沌期”的世界为背景&#xff0c;勇士们…

微服务知识3

Gateway核心概念 路由(route) 网关中最基础的部分&#xff0c;路由信息包括一个ID&#xff0c;一个目的URI&#xff0c;一组谓词工厂&#xff0c;一组Filter组成。如果谓词为真&#xff0c;则说明请求的URL和配置的路由匹配 谓词(preducates) 即java.util.function.Predica…

atbf中imu数据的读取与处理方式

一、说明 本文为作者在阅读atbf源码的过程中&#xff0c;对atbf中imu数据的读取和处理方式的个人理解&#xff0c;可能存在不对之处&#xff0c;意在抛砖引玉&#xff0c;请各位老师多多指正&#xff1b; 二、数据读取流程图 1、target NEUTRONRCF435SE 不同的target所定义…

C++中的queue与priority_queue

文章目录 queuequeue的介绍queue的使用 priority_queuepriority_queue介绍priority_queue使用 queue queue的介绍 队列是一种容器适配器&#xff0c;专门用于上下文先进先出的操作中。队列的特性是先进先出&#xff0c;从容器的一端插入&#xff0c;另一端提取元素。   队列…

Java17的性能优势是否足以让它取代Java8?

随着时间的推移&#xff0c;Java不断地进行更新和发展&#xff0c;以满足不断变化的业务需求。目前&#xff0c;Java8已经成为了一个非常成熟的版本&#xff0c;并且在各个领域广泛应用。但是&#xff0c;Java17也早已发布&#xff0c;并且是Java 11以来又一个LTS(长期支持)版本…

vmware虚拟机安装k8s(之前已经安装过docker)

1、安装开始 先执行&#xff1a;curl https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | sudo apt-key add 再执行更改源&#xff1a;echo "deb https://mirrors.aliyun.com/kubernetes/apt kubernetes-xenial main" >> /etc/apt/sources.list …

单向链表——C语言实现

哈喽&#xff0c;大家好&#xff0c;今天我们学习的是数据结构里的链表&#xff0c;这里主要讲的是不带哨兵卫头节点的单向链表&#xff0c;下篇将会继续带大家学习双向链表。 目录 1.链表的概念 2.单向链表接口的实现 2.1动态申请一个节点 2.2单链表打印 2.3单链表尾插 …

强大的editplus 5.7

EditPlus是一款由韩国 Sangil Kim &#xff08;ES-Computing&#xff09;出品的小巧但是功能强大的可处理文本、HTML和程序语言的Windows编辑器&#xff0c;你甚至可以通过设置用户工具将其作为C,Java,Php等等语言的一个简单的IDE。 EditPlus&#xff08;文字编辑器&#xff0…

Java学习-MySQL-数据库的设计

Java学习-MySQL-数据库的设计 为什么需要设计数据库 当数据库比较复杂的时候&#xff0c;需要设计数据库。 糟糕的数据库设计&#xff1a; 数据冗余&#xff0c;浪费空间&#xff1b;数据库插入和删除很麻烦&#xff0c;可能导致异常&#xff08;屏蔽使用物理外键&#xff0…

机器学习实战教程(十三):集成学习

简介 集成学习是一种机器学习方法&#xff0c;它旨在通过将多个单独的学习器&#xff08;称为基分类器或基学习器&#xff09;的预测结果进行组合&#xff0c;来提高整体的预测准确率。 集成学习可以看作是一种“多个人一起合作做事”的方法。每个基分类器都是独立的学习器&a…

【恭喜宿主:你的神装Xpath到手】——07全栈开发——如桃花来

目录索引 什么是XML&#xff1a;文档演示&#xff1a; XML的节点关系&#xff1a;1.父节点&#xff1a;2. 子节点&#xff1a;3. 同胞节点&#xff1a;4. 先辈节点&#xff1a;5. 后代节点&#xff1a; Xpath&#xff1a;1. 相关语法&#xff1a;*最常用的路径表达式&#xff1…

Android Studio实现文艺阅读App

项目目录 一、系统概述二、系统特点三、开发环境四、运行演示五、源码获取 一、系统概述 本次带来的文艺阅读App可以提供高质量的原创文学作品。用户可以App中找到各种类型的文学作品&#xff0c;包括小说、散文、诗歌等&#xff0c;由来自不同领域的作家所创作。此外&#xf…

对不起,我们不招还在用Excel的人,和金山系新秀比起差太远了

相信点进来的朋友曾经也深受Excel荼毒。 的确&#xff0c;现如今在网上随便一搜&#xff0c;关于Excel的学习资料和答疑解惑的帖子不胜枚举&#xff0c;盖因为Excel有时太过热心&#xff0c;当然&#xff0c;是帮倒忙的那种热心。 自动把天数转换为日期&#xff0c;还替你把身…