FPGA 기반 SD 카드 뮤직 플레이어의 WM8731

FPGA 기반 SD 카드 뮤직 플레이어의 WM8731


머리말

        이번 주제는 7월 초에 했던 에다 코스 디자인입니다. 한 달이 넘었지만 아직도 기억이 조금 남아요. 일반적인 아이디어를 기록하고 싶습니다. 결국 내 자신의 인내로 성공적으로 할 수 있습니다. 또한 매우 저에게는 기억에 남습니다. 이 기사는 주로 I2C 구성을 사용하는 음성 칩 WM8731의 사용 구성을 기록합니다.


팁: 다음은 이 글의 내용으로, 모두 저자가 직접 작성한 원본입니다.

1. I2C 드라이버 모듈

        이 모듈은 FPGA에서 WM8731 칩으로 구성 데이터 전송을 완료하는 역할을 합니다. I2C 버스는 데이터 라인 SDA와 클록 라인 SCL로 구성되어 데이터를 보내거나 데이터를 받는 데 사용할 수 있는 통신 라인을 형성합니다. 마스터 제어와 제어 IC 간에 양방향 데이터 전송이 가능하며 데이터 전송 속도는 표준 모드에서 100kbit/s, 고속 모드에서 400kbit/s, 고속 모드에서 3.4Mbit/s에 달할 수 있습니다. 제어되는 장치의 수는 버스에 병렬로 연결되고 장치 주소로 식별됩니다(SLAVE ADDR, 자세한 내용은 장치 설명서 참조). 내가 사용하는 개발 보드의 I2C 버스의 물리적 토폴로지는 그림 1에 나와 있습니다. 이 그림에서 내가 작동하려는 오디오 칩의 주소가 0x34임을 알 수 있습니다.

그림 1 I2C 버스 물리적 토폴로지

         그림에서 I2C_SCL은 시리얼 클럭 라인, I2C_SDA는 시리얼 데이터 라인 I2C 장치는 일반적으로 오픈 드레인 구조를 사용하여 버스에 연결하므로 I2C_SCL과 I2C_SDA 모두 풀업 저항에 연결해야 합니다. , 버스가 유휴 상태일 때 이 두 라인은 모두 하이 레벨 상태입니다. 버스에 연결된 어떤 장치가 로우 레벨을 출력하면 버스가 풀다운됩니다. 즉, 각 장치의 SDA와 SCL은 " 유선 및 "관계.

        이 모듈의 코드는 정말 꽤 길고 전체 상태 머신 프로그래밍이 사용됩니다.온라인에서 원리를 배우고 직접 프로그래밍을 시도할 수 있습니다.모르는 경우 Baidu에서 검색할 수 있습니다.

        이전에는 IIC 드라이버 코드를 첨부하지 않았지만 필요한 작은 파트너가 있으면 첨부하겠습니다. 이해가 안되면 프로그램 설명을 직접 참조하십시오.

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. WM8731 레지스터 구성 모듈

        이 모듈은 주로 WM8731 칩의 11개 레지스터 구성을 완료하고 레지스터 값을 미리 설정한 다음 I2C 모듈을 통해 레지스터 매개변수 전송을 완료합니다. 레지스터의 특정 구성은 그림 2에 나와 있습니다. WM8731을 슬레이브 모드로 구성했고 필요한 비트 클럭과 좌우 채널 클럭은 WM8731 클럭 생성 모듈에서 생성됩니다. 11개의 레지스터는 어떻게 구성해야 할까요? 칩 매뉴얼을 참고하시면 됩니다. 구글에서 번역한 영어 버전과 중국어 버전이 포함된 칩 매뉴얼의 데이터도 업로드했습니다. 솔직히 제 경험에 따르면 성공하고 싶다면 이 칩 설명서를 반복해서 읽어야 합니다. 그때 몇 번을 읽었는지는 기억나지 않습니다. 제 영어 실력이 좋지 않아서 중국어와 영어를 섞어서 읽었습니다.

그림 2 WM8731 칩의 레지스터 구성

         그림의 볼륨 설정은 입력 신호를 통해 전달되며, 이는 후속 버튼 제어 모듈이 음악 플레이어 모듈의 볼륨 제어를 완료하는 데 편리합니다. 7번째 레지스터 R7의 샘플링 수 역시 조정이 가능하며, 송 샘플링이 16비트이므로 wl은 기본적으로 00으로 설정되어 있는데, 즉 WM8731의 샘플링 수는 16비트로 구성될 수 있음은 물론이다. 필요에 따라 조정.

3. WM8731 클록 생성 모듈

        이 모듈은 WM8731에서 요구하는 비트 클럭과 좌우 채널 구분 클럭을 생성하는 역할을 합니다. 이 모듈이 좌우 채널 구분 클록을 생성할 때 왼쪽 정렬 모드에서 16비트 오디오 데이터의 가장 높은 비트가 먼저 수신되고 가장 높은 비트는 비트 클럭이 도착하면 16비트 수신에 주의해야 합니다. 오디오 데이터 후 비트 클럭은 다음 16비트 오디오 데이터를 수신하기 전에 세 주기를 예약했습니다. 왼쪽 정렬 모드는 그림 3에 나와 있습니다. 여기에서 사용할 수 있는 I2S 형식 및 오른쪽 정렬 모드도 있지만 사용 시 타이밍 다이어그램의 차이에 주의하고 올바른 시계를 작성해야 합니다. 그렇지 않으면 음악 효과가 좋지 않고 소음.

그림 3 왼쪽 맞춤 모드

         이 모듈의 코드를 아래에 넣었습니다. 루틴에 따라 다시 작성했습니다. 직접 픽업해야 합니다. 먼저 WM8731 칩 설명서를 읽어야 합니다. 그렇지 않으면 이해하지 못할 수 있습니다. 이해가 되지 않으면 로직 애널라이저를 사용하여 더 직관적이고 생생한 각 신호를 관찰하십시오. 특히 왼쪽과 오른쪽 채널 클럭을 구분하는데 주의하세요.왼쪽 정렬 모드에 따르면 16비트 오디오 데이터가 수신될 때마다 3개의 쓸모없는 사이클이 발생하므로 코드 36-1의 주석에서 이 3개의 사이클이 생성됩니다. , 왼쪽 정렬을 따르도록 패턴의 형식.

        아래와 같이 코드 쇼:

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. 오디오 전송 모듈

        이 모듈은 SD 카드에서 읽은 디지털 신호를 WM8731 칩으로 보내 아날로그 신호 출력으로 변환하는 역할을 합니다. WM8731 칩이 비트 클럭의 상승 에지에서 신뢰할 수 있는 데이터를 읽을 수 있도록 하기 위해 모듈은 비트 클럭의 하강 에지에서 데이터를 전송합니다.매회 16비트 오디오 데이터를 전송한 후 3비트 동안 유지됩니다. 전송 전 클록 주기 왼쪽 맞춤 모드를 준수하는 16비트 오디오 데이터.

        이 모듈의 코드 일부는 그림 4에 나와 있습니다. 데이터가 비트 클럭의 하강 에지에서 전송되고 높은 비트가 먼저 전송된 다음 3비트 클럭 주기 동안 머문다는 것을 코드에서 볼 수 있습니다. 다음 16비트 오디오 데이터를 보내기 전에 오디오 전송 모듈의 온라인 로직 분석기 디버깅 렌더링은 그림 5에 나와 있습니다. 또한 그림 5에서 데이터가 비트 클럭의 하강 에지에서 전송되고 높은 비트가 먼저 전송된 다음 전송 후 3비트 클럭 주기 후에 다음 16비트 오디오 데이터가 전송됨을 알 수 있습니다. 왼쪽 정렬 모드의 타이밍 요구 사항을 충족합니다.

그림 4 오디오 전송 모듈 코드의 일부
그림 5 오디오 전송 모듈 온라인 로직 애널라이저 디버깅 효과 다이어그램


요약하다

        위는 이 프로젝트의 전체 내용입니다.이 작업을 한 지 오래되었고 글이 좋지 않습니다.이 문서는 이 프로젝트에서 WM8731의 작동을 간략하게 소개합니다.

추천

출처blog.csdn.net/m0_66360845/article/details/126462919