WM8731 de reproductor de música con tarjeta SD basado en FPGA

WM8731 de reproductor de música con tarjeta SD basado en FPGA


prefacio

        Este tema es un diseño de curso de eda que hice a principios de julio. Después de más de un mes, todavía tengo algunos recuerdos. Quiero registrar mis ideas generales. Después de todo, puedo hacerlo con éxito con mi propia perseverancia. También es muy memorable para mí. Este artículo registra principalmente la configuración de uso del chip de voz WM8731, que utiliza la configuración I2C.


Consejo: El siguiente es el texto de este artículo, todos los cuales son originales del propio autor. No es fácil escribir un artículo. Espero que adjunte un enlace a este artículo cuando lo vuelva a publicar.

1. Módulo de controlador I2C

        Este módulo es responsable de completar la transmisión de datos de configuración desde FPGA al chip WM8731. El bus I2C consta de una línea de datos SDA y una línea de reloj SCL para formar una línea de comunicación, que se puede utilizar para enviar o recibir datos. La transmisión de datos bidireccional se puede realizar entre el control maestro y el IC controlado. La velocidad de transmisión de datos puede alcanzar 100 kbit/s en modo estándar, 400 kbit/s en modo rápido y 3,4 Mbit/s en modo de alta velocidad. Todo tipo de los dispositivos controlados están conectados en paralelo en el bus e identificados por la dirección del dispositivo (SLAVE ADDR, consulte el manual del dispositivo para obtener más detalles). La topología física del bus I2C de la placa de desarrollo que uso se muestra en la Figura 1. En esta figura, podemos ver que la dirección del chip de audio que quiero operar es 0x34.

Figura 1 Topología física del bus I2C

         I2C_SCL en la figura es una línea de reloj en serie e I2C_SDA es una línea de datos en serie. Dado que los dispositivos I2C generalmente usan una estructura de drenaje abierto para conectarse al bus, tanto I2C_SCL como I2C_SDA deben conectarse a una resistencia pull-up. Por lo tanto, , cuando el bus está inactivo, ambas líneas están en un estado de nivel alto. Cuando cualquier dispositivo conectado al bus emite un nivel bajo, el bus se descompondrá, es decir, el SDA y el SCL de cada dispositivo están en un " relación cableada y".

        El código de este módulo es realmente bastante largo y se utiliza la programación general de la máquina de estado. Puede aprender los principios en línea e intentar programar usted mismo. Si no sabe cómo hacerlo, puede buscarlo en Baidu.

        No adjunté el código del controlador IIC antes, pero si hay un socio pequeño que lo necesita, lo adjuntaré. Si no lo entiende, simplemente consulte los comentarios del programa directamente.

module clg_i2c_dri
    #(
      parameter   SLAVE_ADDR = 7'b0011010   ,  //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 ;
                        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

2. Módulo de configuración de registro WM8731

        Este módulo completa principalmente la configuración de los 11 registros del chip WM8731, establece el valor de los registros por adelantado y luego completa la transferencia de parámetros de registro a través del módulo I2C. La configuración específica del registro se muestra en la Figura 2. Configuré WM8731 como modo esclavo, y el reloj de bit y los relojes de canal izquierdo y derecho que necesita son generados por el módulo de generación de reloj WM8731. ¿Cómo se deben configurar los 11 registros? Puede consultar el manual del chip. También he subido los datos del manual del chip, que incluye una versión en inglés y una versión en chino traducida por Google. Para ser honesto, según mi experiencia, si Si desea tener éxito, este manual del chip debe leerse repetidamente. No recuerdo cuántas veces lo leí en ese momento. Como mi inglés no es muy bueno, lo leo en una combinación de chino e inglés.

Figura 2 Configuración de registro del chip WM8731

         La configuración del volumen en la figura se transmite a través de la señal de entrada, lo cual es conveniente para que el módulo de control de botones posterior complete el control de volumen del módulo del reproductor de música. El número de muestreo del séptimo registro R7 también se puede ajustar. Dado que el muestreo de la canción es de 16 bits, wl está configurado en 00 de forma predeterminada, es decir, el número de muestreo de WM8731 está configurado para ser de 16 bits. Por supuesto, se puede ajustado de acuerdo a las necesidades.

3. Módulo de generación de reloj WM8731

        Este módulo es responsable de generar el reloj de bit y el reloj de distinción de canal izquierdo y derecho requerido por WM8731. Cuando este módulo genera relojes que distinguen los canales izquierdo y derecho, se debe tener en cuenta que el bit más alto de los datos de audio de 16 bits en el modo alineado a la izquierda se recibe primero, y el bit más alto se puede usar cuando el primer flanco ascendente del llega el reloj de bits, y luego debe prestar atención a la recepción de 16 bits Después de los datos de audio, el reloj de bits ha reservado tres ciclos antes de recibir los siguientes datos de audio de 16 bits. El modo de alineación a la izquierda se muestra en la Figura 3. También hay formato I2S y modo alineado a la derecha que se pueden usar aquí, pero debe prestar atención a la diferencia en el diagrama de tiempo al usarlo y escribir el reloj correcto, de lo contrario, el efecto de la música no será bueno y habrá ruido.

Figura 3 Modo justificado a la izquierda

         Pongo el código de este módulo a continuación. Lo reescribí de acuerdo con una rutina. Debe recogerlo usted mismo. Primero debe leer el manual del chip WM8731, de lo contrario es posible que no lo entienda. Si no entiende, use un analizador lógico para observar cada señal, que es más intuitivo y vívido. Hay que tener especial cuidado en distinguir los relojes de los canales izquierdo y derecho, según el modo de alineación izquierda, cada vez que se reciban datos de audio de 16 bits habrá 3 ciclos inútiles, por lo que estos tres ciclos se generan en el comentario del código 36-1 , de modo que se ajuste a la alineación izquierda El formato del patrón.

        el código se muestra a continuación:

module Audio_Clk(
	clock_ref,               //参考时钟
	Rst_n,                   //复位信号
	xck,                     //主时钟
	Audio_LRCLK,             //左右声道区分时钟
	bclk,                    //位时钟
	mode,                    //播放模式,顺序、随机、单曲循环可选
	speed                    //播放速度
);
	input clock_ref          ; //wm8731振荡器时钟,选择18.432Mhz;
	input Rst_n              ;
	input [1:0] speed        ;
	input [2:0] mode         ;
	output  reg Audio_LRCLK  ;        
	output  xck              ;
	output  reg bclk         ;
   
	parameter CLOCK_REF=18432000 ;
	parameter CLOCK_SAMPLE0=48000;
	parameter CLOCK_SAMPLE1=96000;
    
	assign xck = clock_ref;
	//assign bclk = (speed==2'b01)? bclk0:bclk1;
	//assign Audio_LRCLK = (speed==2'b01)? Audio_LRCLK0:Audio_LRCLK1;
	
	always @(*)
		if((speed==2'b01)&&(mode<3'b101))
			begin Audio_LRCLK<=Audio_LRCLK0;bclk<=bclk0; end
		else if((speed==2'b10)&&(mode<3'b101))
			begin Audio_LRCLK<=Audio_LRCLK1;bclk<=bclk1; end
		else if(mode==3'b101)
			begin Audio_LRCLK<=1'b0;bclk<=1'b0; end
	
	reg Audio_LRCLK1;
	reg [8:0]Audio_LRCLK1_cnt;
	reg bclk1;
	reg [3:0]bclk1_cnt;
	//产生DAC和ADC的左右声道区分时钟,该时钟等于实际的芯片采样率
	always@(posedge clock_ref or negedge Rst_n)    
	if(!Rst_n) begin
		Audio_LRCLK1<=0;
		Audio_LRCLK1_cnt<=0;
	end
	else if(Audio_LRCLK1_cnt>=(CLOCK_REF/(CLOCK_SAMPLE1*2)+(CLOCK_REF/(CLOCK_SAMPLE1*2*16*2))*6-1)) 
	begin                                                     //36-1
		Audio_LRCLK1<=~Audio_LRCLK1;
		Audio_LRCLK1_cnt<=0;
	end
	else
		Audio_LRCLK1_cnt<=Audio_LRCLK1_cnt+1'b1; 

	//产生I2S位时钟,BCLK的频率= 2 * 采样频率 * 采样位数,其中的2代表了2个声道。
	always@(posedge clock_ref or negedge Rst_n)
	if(!Rst_n) begin
		bclk1<=0;
		bclk1_cnt<=0;
	end
	else if(bclk1_cnt>=(CLOCK_REF/(CLOCK_SAMPLE1*2*16*2)-1)) begin
		bclk1<=~bclk1;
		bclk1_cnt<=0;
	end
	else
		bclk1_cnt<=bclk1_cnt+1'b1;
	
	
	reg Audio_LRCLK0;
	reg [8:0]Audio_LRCLK0_cnt;
	reg bclk0;
	reg [3:0]bclk0_cnt;
	//产生DAC和ADC的左右声道区分时钟,该时钟等于实际的芯片采样率
	always@(posedge clock_ref or negedge Rst_n)    
	if(!Rst_n) begin
		Audio_LRCLK0<=0;
		Audio_LRCLK0_cnt<=0;
	end
	else if(Audio_LRCLK0_cnt>=(CLOCK_REF/(CLOCK_SAMPLE0*2)+(CLOCK_REF/(CLOCK_SAMPLE0*2*16*2))*6-1)) 
	begin                                                     //36-1
		Audio_LRCLK0<=~Audio_LRCLK0;
		Audio_LRCLK0_cnt<=0;
	end
	else
		Audio_LRCLK0_cnt<=Audio_LRCLK0_cnt+1'b1; 

	//产生I2S位时钟,BCLK的频率= 2 * 采样频率 * 采样位数,其中的2代表了2个声道。
	always@(posedge clock_ref or negedge Rst_n)
	if(!Rst_n) begin
		bclk0<=0;
		bclk0_cnt<=0;
	end
	else if(bclk0_cnt>=(CLOCK_REF/(CLOCK_SAMPLE0*2*16*2)-1)) begin
		bclk0<=~bclk0;
		bclk0_cnt<=0;
	end
	else
		bclk0_cnt<=bclk0_cnt+1'b1;

endmodule

4. Módulo de envío de audio

        Este módulo se encarga de enviar la señal digital leída desde la tarjeta SD al chip WM8731 para convertirla en una salida de señal analógica. Para garantizar que el chip WM8731 pueda leer datos confiables en el flanco ascendente del reloj de bits, el módulo envía datos en el flanco descendente del reloj de bits.Después de enviar datos de audio de 16 bits cada vez, permanecerá durante 3 bits ciclos de reloj antes de enviar datos de audio de 16 bits para ajustarse al modo justificado a la izquierda.

        Parte del código de este módulo se muestra en la Figura 4. Se puede ver en el código que los datos se envían en el flanco descendente del reloj de bits, y el bit alto se envía primero y luego permanece durante ciclos de reloj de 3 bits. antes de enviar los siguientes datos de audio de 16 bits. La representación de depuración del analizador lógico en línea del módulo de envío de audio se muestra en la Figura 5. También se puede ver en la Figura 5 que los datos se envían en el flanco descendente del reloj de bits, y el bit alto se envía primero, y luego los siguientes datos de audio de 16 bits se envían después de 3 ciclos de reloj de bits después del envío, que cumple con los requisitos de temporización del modo alineado a la izquierda.

Figura 4 Parte del código del módulo de envío de audio
Figura 5 Diagrama de efecto de depuración del analizador lógico en línea del módulo de envío de audio


Resumir

        Lo anterior es todo el contenido de este proyecto. Ha pasado mucho tiempo desde que hice esto, y la escritura no es muy buena. Este artículo solo presenta brevemente el funcionamiento de WM8731 en este proyecto.

Supongo que te gusta

Origin blog.csdn.net/m0_66360845/article/details/126462919
Recomendado
Clasificación