I2S常用于音频数据通信,协议内容比较简单,主要由三根线信号线如下:
LRCLK:左右声道数据传输通道,低电平代表左声道数据,高电平代表右声道数据;
BCLK:数据时钟信号,上升沿采样;
SDATA:音频数据。
名词解析:
左右声道指的是在电子设备中模拟人类左右耳的听觉范围产生的声音输出。它们是相互独立的音频信号,用于在不同空间位置采集或回放声音,以确保声音的高还原性。左声道通常模拟人类左耳的听觉范围,而右声道模拟右耳的听觉范围。在录制音乐时,使用两个话筒按照左右声道进行录制,以确保播放时的立体声效果。
协议比较简单,有点类似与SPI协议,以DAC芯片AD1955为例讲解I2S协议,如下图所示为AD1955在I2S协议下数据图:
图1 音频数据时序图
图中第一个时序图是标准的I2S协议,DAC可以输出16位或者24位DAC数据,这里以16个DAC数据为例讲解。LRCLK和BCLK在代码编写时可以是同步信号,LRCLK可以是BCLK的1/64频率,工作流程如下:
1、LRCLK和BCLK同时低电平,在BCLK的第二个下降沿发送数据,数据在第二个上升沿到来之前保证数据稳定;
2、BCLK之后的每一个下降沿可以改变数据,上升沿采样直到16位/24位DAC数据发送完毕;
3、在第个时钟周期17个时钟周期结束时左声道数据发送完毕,此时LRCLK低电平还有15个时钟周期,这段时间数据无效。
4、BCLK第33个时钟周期下降沿和LRCLK的上升沿对齐,
5、第34时钟周期下降沿发送音频数据最高位MSB,接收端通过上升沿采样;
6、BCLK第49个时钟周期发送完右声道最小bit位;
7、BCLK继续发送完64个时钟周期,到这里左右声道一个采集单位输出完毕。
I2S协议不是很严谨,BCLK发送数据位宽没有限制,这里左右声道各32个时钟周期,实际上只用到17个时钟周期。设计中可以随意变更,只要时钟周期大于等于17个即可,如果发送24位DAC,时钟周期大于25位就可以。
另外AD1955可以选用RIGHT-JUSTIFIED/LEFT-JUSTIFIED MODE协议模式,这些协议模式和I2S协议基本一致,也可以驱动DAC左右声道模拟信息输出。
基于之前原则的是LEFT-JUSTIFIED驱动该DAC芯片,仿真波形图如下:
图2 AD1955 LEFT-JUSTIFIED模式
左声道发送0x1234,右声道发送0x5678;
由于该芯片我使用的是External Filter Mode模式,根据芯片手册写的该模式下左声道数据通过DA_SDATA接口输入,右声道通过EF_RDATA接口输入,所以左右声道数据经过FPGA两个接口输出。
芯片该处说明如下图所示:
图3 AD1955External Filter Mode模式数据接收
如下为FPGA仿真代码
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/07/17 16:02:09
// Design Name:
// Module Name: DAC_TB
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module DAC_TB(
);
reg sys_clk;
reg sys_rst_n;
reg[15:0] DATA_L_IN;
reg[15:0] DATA_R_IN;
reg DATA_EN;
always #4.88 sys_clk = ~sys_clk; 102.4MHz
initial begin
sys_clk = 'd0;
sys_rst_n = 'd0;
DATA_EN = 'd0;
DATA_L_IN = 'd0;
DATA_R_IN = 'd0;
#30_000
sys_rst_n = 'd1;
#500
repeat(1)@(posedge sys_clk);
DATA_EN = 'd1;
DATA_L_IN = 16'h1234;
DATA_R_IN = 16'h5678;
repeat(1)@(posedge sys_clk);
DATA_EN = 'd0;
end
JUSTIFED_MODE_DRV JUSTIFED_MODE_DRV(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.DATA_L_IN(DATA_L_IN),
.DATA_R_IN(DATA_R_IN),
.DATA_EN(DATA_EN),
//dac out
.DA_MCLK_out(DA_MCLK),
.DA_RDATA(DA_RDATA),
.DA_SDATA(DA_SDATA),
.DA_BCLK(DA_BCLK),
.DA_LRCLK(DA_LRCLK)
);
endmodule
FPGA LEFT-JUSTIFIED驱动代码
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/01/10 17:00:02
// Design Name:
// Module Name: JUSTIFED_MODE_DRV
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module JUSTIFED_MODE_DRV(
input sys_clk,
input sys_rst_n,
voice dds
input[15:0] DATA_L_IN,
input[15:0] DATA_R_IN,
input DATA_EN,
DAC IO
//dac out
output DA_MCLK_out, clk io FS 256 256/32*2 = 4锛?
output reg DA_RDATA,RIGHT DATA
output reg DA_SDATA,LEFT DATA
//output DA_SDATA,LEFT DATA
output reg DA_BCLK,
output reg DA_LRCLK
);
parameter DATA_EN_NUM = 32;
reg fifo_rd_en;
wire[31:0] fifo_data;
reg[31:0] data_l;
reg[31:0] data_r;
reg[7:0] DA_MCLK_cnt;
reg[7:0] DA_BCLK_cnt;
reg data_out_l_en;
reg data_out_r_en;
reg[7:0] i;
reg[7:0] q;
wire DA_MCLK;
// assign data_l = {fifo_data[31:16],16'd0};
// assign data_r = {fifo_data[15:0],16'd0};
// assign data_l = {16'h1234,16'd0};
// assign data_r = {16'h8765,16'd0};
//assign DA_SDATA = DA_RDATA;
always @(posedge DA_MCLK or negedge sys_rst_n)begin
if(sys_rst_n == 'd0)begin
DA_MCLK_cnt <= 'd0;
DA_BCLK <= 'd1;
end
else if(DA_MCLK_cnt >= 'd1)begin
DA_MCLK_cnt <= 'd0;
DA_BCLK <= ~DA_BCLK;
end
else begin
DA_MCLK_cnt <= DA_MCLK_cnt + 'd1;
end
end
always @(posedge DA_MCLK or negedge sys_rst_n)begin
if(sys_rst_n == 'd0)begin
DA_LRCLK <= 'd0;
DA_BCLK_cnt <= 'd0;
end
else if(DA_BCLK == 'd1 && DA_MCLK_cnt >= 'd1)begin
if(DA_BCLK_cnt == 'd64)begin
DA_BCLK_cnt <= 'd1;
DA_LRCLK <= 'd1;
end
else if(DA_BCLK_cnt >= 'd32)begin
DA_BCLK_cnt <= DA_BCLK_cnt + 'd1;
DA_LRCLK <= 'd0;
end
else begin
DA_BCLK_cnt <= DA_BCLK_cnt + 'd1;
DA_LRCLK <= 'd1;
end
end
end
always @(posedge DA_MCLK or negedge sys_rst_n)begin
if(sys_rst_n == 'd0)begin
data_out_l_en <= 'd0;
data_out_r_en <= 'd0;
DA_SDATA <= 'd0;
DA_RDATA <= 'd0;
i <= 'd31;
q <= 'd31;
end
else if(DA_BCLK == 'd1 && DA_MCLK_cnt >= 'd1 && DA_BCLK_cnt == 'd64)begin
data_out_l_en <= 'd1;
i <= i - 'd1;
q <= 'd0;
//DA_SDATA <= data_l[i];
end
else if(DA_BCLK == 'd1 && DA_MCLK_cnt >= 'd1 && DA_BCLK_cnt <= 'd31)begin
data_out_l_en <= 'd1;
i <= i - 'd1;
q <= 'd31;
DA_SDATA <= data_l[i];
end
else if(DA_BCLK == 'd1 && DA_MCLK_cnt >= 'd1)begin
data_out_r_en <= 'd1;
q <= q - 'd1;
i <= 'd31;
DA_RDATA <= data_r[q];
//DA_SDATA <= data_l[q];
end
else begin
data_out_l_en <= 'd0;
data_out_r_en <= 'd0;
end
end
always @(posedge DA_MCLK or negedge sys_rst_n)begin
if(sys_rst_n == 'd0)begin
fifo_rd_en <= 'd0;
data_l <= 'd0;
data_r <= 'd0;
end
else if(DA_BCLK == 'd1 && DA_MCLK_cnt >= 'd1 && DA_BCLK_cnt == 'd63)begin
fifo_rd_en <= 'd1;
data_l <= {fifo_data[31:16],16'd0};
data_r <= {fifo_data[15:0],16'd0};
end
else begin
fifo_rd_en <= 'd0;
end
end
FIFO_JUSTIFED FIFO_JUSTIFED_INST(
.wr_clk(sys_clk),
.rd_clk(DA_MCLK),
.rst(~sys_rst_n),
.din({DATA_L_IN,DATA_R_IN}),
.wr_en(DATA_EN),
.rd_en(fifo_rd_en),
.dout(fifo_data),
.full(),
.empty()
);
PLL_DA_MCLK PLL_DA_MCLK_INST(
// Clock out ports
.clk_out1(DA_MCLK_out),
.clk_out2(DA_MCLK),
// Clock in ports
.clk_in1(sys_clk)
);
endmodule
LEFT-JUSTIFIED时序和I2S时序非常相似,有兴趣的可以基于改代码做修改。
总结:
音频DAC相比较普通DAC是由两路DAC组成,并且满足I2S协议,I2S协议是专门为音频设计的,因为需要两个声音模仿人耳立体声。一般音频DAC的采样率在几百KHz以内,而人耳能识别的声音在44.1KHz以内。
如有疑问可留言,后面可能会提供FPGA源码,敬请期待