本帖最后由 Zhao_kar 于 2024-1-29 19:32 编辑
【Tang Primer25K Dock】七——I2S加麦克风阵列的基本驱动测试
备注:
- 下一节第八节就是测评计划提到的数字时钟了,更新顺序比较乱,但是都会在备注里面简单说一下
- 本节理论部分会多一点,毕竟涉及到一个新的概念
- 事先声明,因为只是简单的测试,实际上的演示效果会比较差
- 然后补充一下,实际学下来,实际上我数字时钟的代码甚至都没有分层,而这个I2S却是分了I2S的驱动部分和LED的驱动部分两个子模块。
一、I2S的原理
1、首先了解一下基本概念:
- I2S(Inter-IC Sound)。I2S是一种用于在集成电路之间传输音频数据的串行通信接口标准。
- 它通常用于连接音频编解码器、数字信号处理器(DSP)、微控制器和其他音频设备。
- I2S接口使用了三个主要的信号线:时钟信号(SCK),帧同步信号(WS或LRCLK),和串行数据线(SD)。
- 这种接口广泛用于数字音频传输,支持高质量的音频传输和处理。
2、协议介绍:
- I2S是一种通过三根线(时钟线、数据线和帧选择线)进行全双工音频通信的协议。
- 时钟线(SCK)用于同步数据传输。
- 数据线(SD)传输左右声道的音频数据。
- 帧选择线(WS,或LRCLK)用于指示是左声道还是右声道的数据。
3、时钟同步:
- I2S协议需要时钟的同步,这是通过时钟线(SCK)实现的。所有设备都需要基于相同的时钟频率进行操作。
- 其中时钟可以是主设备生成,也可以是外部提供。
4、数据格式:
- I2S支持不同的数据格式,如标准I2S、左对齐(Left-Justified)和右对齐(Right-Justified)等。
- 标准I2S数据格式是16位,每个帧有一个左声道和一个右声道的采样数据。
5、帧同步:
- I2S协议中的帧同步线(WS)在每个音频帧的开始指示左右声道的数据。
- 帧同步信号通常以低电平表示左声道,高电平表示右声道。
6、补充:
二、I2S的子模块部分
module i2s_top(
input clk,//输入时钟
input rst_n,//复位信号
output reg [23:0] ldata,//输出左声道数据
output reg [23:0] rdata,//输出右声道数据
output ready,//数据就绪一次给一个脉冲
output mic_sck,//输出麦克风时钟,连麦克风阵列mic_sck
output mic_ws,//输出麦克风左右声道信号,连麦克风阵列mic_ws
input mic_sd//麦克风输入
);
//对时钟分频
localparam div_clk = 50;
localparam div_ws = div_clk * 64;
reg [15:0] sck_cnt;
reg [21:0] ws_cnt;
reg [23:0] shift_reg_l;
reg [23:0] shift_reg_r;
assign mic_sck = (sck_cnt > div_clk/2-1) ? 1'b1 : 1'b0;
assign mic_ws = (ws_cnt > div_ws/2-1) ? 1'b1 : 1'b0;
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
sck_cnt <= 0;
end
else begin
if(sck_cnt == div_clk-1) begin
sck_cnt <= 0;
end
else begin
sck_cnt <= sck_cnt + 1;
end
end
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
ws_cnt <= 0;
end
else begin
if(ws_cnt == div_ws-1) begin
ws_cnt <= 0;
end
else begin
ws_cnt <= ws_cnt + 1;
end
end
end
//根据I2S时序采集数据,并通过shift_reg更新
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
shift_reg_l <= 0;
shift_reg_r <= 0;
end
else begin
case(ws_cnt)
div_clk*1+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*2+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*3+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*4+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*5+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*6+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*7+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*8+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*9+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*10+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*11+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*12+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*13+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*14+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*15+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*16+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*17+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*18+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*19+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*20+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*21+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*22+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*23+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*24+div_clk/2-1: begin shift_reg_l <= {shift_reg_l[22:0],mic_sd}; shift_reg_r <= shift_reg_r; end
div_clk*33+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*34+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*35+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*36+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*37+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*38+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*39+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*40+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*41+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*42+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*43+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*44+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*45+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*46+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*47+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*48+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*49+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*50+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*51+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*52+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*53+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*54+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*55+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
div_clk*56+div_clk/2-1: begin shift_reg_l <= shift_reg_l; shift_reg_r <= {shift_reg_r[22:0],mic_sd}; end
default: begin shift_reg_l <= shift_reg_l; shift_reg_r <= shift_reg_r; end
endcase
end
end
//根据时序逻辑将shift_reg最终输出为左右声道音频值
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
ldata <= 0;
rdata <= 0;
end
else begin
if(ws_cnt == div_clk*56+div_clk/2) begin
ldata <= shift_reg_l;
rdata <= shift_reg_r;
end
else begin
ldata <= ldata;
rdata <= rdata;
end
end
end
assign ready = (ws_cnt == div_clk*56+div_clk/2) ? 1'b1 : 1'b0;//就绪脉冲
endmodule
三、顶层模块部分
module top(
input clk50M,//系统输入时钟,H11
input rst_n,//系统复位信号,低电平复位
output mic_sck,//输出麦克风时钟,连麦克风阵列mic_sck
output mic_ws,//输出麦克风左右声道信号,连麦克风阵列mic_ws
input mic_sd0,//麦克风输入,连mic_d0
input mic_sd1,//麦克风输入,连mic_d1
input mic_sd2,//麦克风输入,连mic_d2
input mic_sd3,//麦克风输入,连mic_d3
output led_sck,//输出灯带时钟,连led_ck
output led_dat//输出灯带数据,连led_da
);
wire signed [23:0] mic_data00;//麦克风1数据(有符号整数)
wire signed [23:0] mic_data01;//麦克风2数据(有符号整数)
wire signed [23:0] mic_data10;//麦克风3数据(有符号整数)
wire signed [23:0] mic_data11;//麦克风4数据(有符号整数)
wire signed [23:0] mic_data20;//麦克风5数据(有符号整数)
wire signed [23:0] mic_data21;//麦克风6数据(有符号整数)
wire signed [23:0] mic_data3;//麦克风7数据(有符号整数)
wire [23:0] abs_mic_data00;//麦克风1数据绝对值
wire [23:0] abs_mic_data01;//麦克风2数据绝对值
wire [23:0] abs_mic_data10;//麦克风3数据绝对值
wire [23:0] abs_mic_data11;//麦克风4数据绝对值
wire [23:0] abs_mic_data20;//麦克风5数据绝对值
wire [23:0] abs_mic_data21;//麦克风6数据绝对值
wire [23:0] abs_mic_data3;//麦克风7数据绝对值
//对接收到的麦克风数据取绝对值
assign abs_mic_data00 = (mic_data00 >= 0) ? $unsigned(mic_data00) : $unsigned(-mic_data00);
assign abs_mic_data01 = (mic_data01 >= 0) ? $unsigned(mic_data01) : $unsigned(-mic_data01);
assign abs_mic_data10 = (mic_data10 >= 0) ? $unsigned(mic_data10) : $unsigned(-mic_data10);
assign abs_mic_data11 = (mic_data11 >= 0) ? $unsigned(mic_data11) : $unsigned(-mic_data11);
assign abs_mic_data20 = (mic_data20 >= 0) ? $unsigned(mic_data20) : $unsigned(-mic_data20);
assign abs_mic_data21 = (mic_data21 >= 0) ? $unsigned(mic_data21) : $unsigned(-mic_data21);
assign abs_mic_data3 = (mic_data3 >= 0) ? $unsigned(mic_data3 ) : $unsigned(-mic_data3 );
reg [11:0] dir;//12个灯亮灭控制寄存器
reg [31:0] set_cnt;//灯带刷新计数寄存器
reg [2:0] comp_state;//麦克风强度比较状态机state
reg [31:0] maixv;//麦克风最大强度值缓存
reg [2:0] maixn;//强度麦克风序号缓存
reg [31:0] maxv;//麦克风最大强度值
reg [2:0] maxn;//强度麦克风序号
//例化麦克风驱动,麦克风1 2
i2s_top i2s_u0(
.clk(clk50M),
.rst_n(rst_n),
.ldata(mic_data00),
.rdata(mic_data01),
.ready(),
.mic_sck(mic_sck),
.mic_ws(mic_ws),
.mic_sd(mic_sd0)
);
//例化麦克风驱动,麦克风3 4
i2s_top i2s_u1(
.clk(clk50M),
.rst_n(rst_n),
.ldata(mic_data10),
.rdata(mic_data11),
.ready(),
.mic_sck(),
.mic_ws(),
.mic_sd(mic_sd1)
);
//例化麦克风驱动,麦克风5 6
i2s_top i2s_u2(
.clk(clk50M),
.rst_n(rst_n),
.ldata(mic_data20),
.rdata(mic_data21),
.ready(),
.mic_sck(),
.mic_ws(),
.mic_sd(mic_sd2)
);
//例化麦克风驱动,麦克风7
i2s_top i2s_u3(
.clk(clk50M),
.rst_n(rst_n),
.ldata(),
.rdata(mic_data3),
.ready(),
.mic_sck(),
.mic_ws(),
.mic_sd(mic_sd3)
);
//灯带驱动
led12 led12_u(
.clk(clk50M),
.rst_n(rst_n),
.direct(dir),
.bgr(24'h0000fc),//颜色红色 蓝:24'hfc0000 绿:24'h00hc00
.refresh(1'b1),
.led_sck(led_sck),
.led_dat(led_dat)
);
//比较7个麦克风的声音强度,获取最强的麦克风序号
always @(posedge clk50M or negedge rst_n) begin
if(~rst_n) begin
comp_state <= 0;
maixv <= 0;
maixn <= 0;
maxv <= 0;
maxn <= 0;
end
else begin
case(comp_state)
0: begin
if(abs_mic_data00 > maixv) begin
maixv <= abs_mic_data00;
maixn <= 1;
end
else begin
maixv <= maixv;
maixn <= maixn;
end
comp_state <= comp_state + 1;
end
1: begin
if(abs_mic_data01 > maixv) begin
maixv <= abs_mic_data01;
maixn <= 2;
end
else begin
maixv <= maixv;
maixn <= maixn;
end
comp_state <= comp_state + 1;
end
2: begin
if(abs_mic_data10 > maixv) begin
maixv <= abs_mic_data10;
maixn <= 3;
end
else begin
maixv <= maixv;
maixn <= maixn;
end
comp_state <= comp_state + 1;
end
3: begin
if(abs_mic_data11 > maixv) begin
maixv <= abs_mic_data11;
maixn <= 4;
end
else begin
maixv <= maixv;
maixn <= maixn;
end
comp_state <= comp_state + 1;
end
4: begin
if(abs_mic_data20 > maixv) begin
maixv <= abs_mic_data20;
maixn <= 5;
end
else begin
maixv <= maixv;
maixn <= maixn;
end
comp_state <= comp_state + 1;
end
5: begin
if(abs_mic_data21 > maixv) begin
maixv <= abs_mic_data21;
maixn <= 6;
end
else begin
maixv <= maixv;
maixn <= maixn;
end
comp_state <= comp_state + 1;
end
6: begin
if(abs_mic_data3 > maixv) begin
maixv <= abs_mic_data3;
maixn <= 7;
end
else begin
maixv <= maixv;
maixn <= maixn;
end
comp_state <= comp_state + 1;
end
7: begin
maxv <= maixv;
if(maixv>9000)//设置个阈值,声音太小全都忽略,都不亮
maxn <= maixn;
else
maxn <= 0;
maixv <= 0;
maixn <= 0;
comp_state <= 0;
end
default: begin
comp_state <= 0;
end
endcase
end
end
//根据最强的麦克风序号亮相应的指示灯
always @(posedge clk50M or negedge rst_n) begin
if(~rst_n) begin
set_cnt <= 0;
dir <= 0;
end
else begin
if(set_cnt == 2699999) begin
set_cnt <= 0;
case(maxn)
0: dir <= 12'b000000000000;
1: dir <= 12'b000000000001;
2: dir <= 12'b000000000100;
3: dir <= 12'b000000010000;
4: dir <= 12'b000001000000;
5: dir <= 12'b000100000000;
6: dir <= 12'b010000000000;
7: dir <= 12'b111111111111;
default: dir <= 12'b000000000000;
endcase
end
else begin
set_cnt <= set_cnt + 1;
end
end
end
endmodule
四、实际视频演示
PS:因为比较简单,所以实际演示效果并不好,且中间那个麦克风的声音最大时,全部的灯都会亮,加上延时问题,会出现经常全部亮的情况,可以在判别的部分降低频率进行测试,之前试过,就不演示了
i2s