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임을 알 수 있습니다.
그림에서 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개의 레지스터는 어떻게 구성해야 할까요? 칩 매뉴얼을 참고하시면 됩니다. 구글에서 번역한 영어 버전과 중국어 버전이 포함된 칩 매뉴얼의 데이터도 업로드했습니다. 솔직히 제 경험에 따르면 성공하고 싶다면 이 칩 설명서를 반복해서 읽어야 합니다. 그때 몇 번을 읽었는지는 기억나지 않습니다. 제 영어 실력이 좋지 않아서 중국어와 영어를 섞어서 읽었습니다.
그림의 볼륨 설정은 입력 신호를 통해 전달되며, 이는 후속 버튼 제어 모듈이 음악 플레이어 모듈의 볼륨 제어를 완료하는 데 편리합니다. 7번째 레지스터 R7의 샘플링 수 역시 조정이 가능하며, 송 샘플링이 16비트이므로 wl은 기본적으로 00으로 설정되어 있는데, 즉 WM8731의 샘플링 수는 16비트로 구성될 수 있음은 물론이다. 필요에 따라 조정.
3. WM8731 클록 생성 모듈
이 모듈은 WM8731에서 요구하는 비트 클럭과 좌우 채널 구분 클럭을 생성하는 역할을 합니다. 이 모듈이 좌우 채널 구분 클록을 생성할 때 왼쪽 정렬 모드에서 16비트 오디오 데이터의 가장 높은 비트가 먼저 수신되고 가장 높은 비트는 비트 클럭이 도착하면 16비트 수신에 주의해야 합니다. 오디오 데이터 후 비트 클럭은 다음 16비트 오디오 데이터를 수신하기 전에 세 주기를 예약했습니다. 왼쪽 정렬 모드는 그림 3에 나와 있습니다. 여기에서 사용할 수 있는 I2S 형식 및 오른쪽 정렬 모드도 있지만 사용 시 타이밍 다이어그램의 차이에 주의하고 올바른 시계를 작성해야 합니다. 그렇지 않으면 음악 효과가 좋지 않고 소음.
이 모듈의 코드를 아래에 넣었습니다. 루틴에 따라 다시 작성했습니다. 직접 픽업해야 합니다. 먼저 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비트 오디오 데이터가 전송됨을 알 수 있습니다. 왼쪽 정렬 모드의 타이밍 요구 사항을 충족합니다.
요약하다
위는 이 프로젝트의 전체 내용입니다.이 작업을 한 지 오래되었고 글이 좋지 않습니다.이 문서는 이 프로젝트에서 WM8731의 작동을 간략하게 소개합니다.