好久没动手了,使用Verilog编写一个AD7356的SPI驱动程序。
AD7356是一个双通道、12位、低功耗的ADC。最高采样速率可达5MSPS,全功率输入带宽为110MHz。AD7356的引脚图如下。
SPI的时序图如下,为了使单通道的采样速率达到最高的5MSPS,只能选择第一种时序。第二种时序,可以实现只使用一根数据线读取两个通道的数据的功能,但理论最高采样速率就减半了。
下表是手册中SPI时序的约束。
为了使采样速率达到5MSPS,必须在200ns内完成数据转换与数据读取。经分析,可以使用
f
=
160
MHz
f=160\text{ MHz}
f=160 MHz的时钟,周期为
t
=
6.25
ns
t=6.25\text{ ns}
t=6.25 ns,再使用该时钟分频出
80
MHz
80\text{ MHz}
80 MHz的SPI时钟,SPI时钟周期为
t
s
c
l
k
=
12.5
ns
t_{sclk}=12.5\text{ ns}
tsclk=12.5 ns。则
t
CONVERT
=
t
2
+
13
×
t
sclk
t_{\text{CONVERT}}=t_2 + 13 \times t_{\text {sclk}}
tCONVERT=t2+13×tsclk
取
t
2
=
t
t_2 = t
t2=t,则
t
CONVERT
=
168.75
ns
t_{\text{CONVERT}}=168.75 \text { ns}
tCONVERT=168.75 ns,再加上半个SPI时钟周期
t
s
c
l
k
/
2
=
6.25
ns
t_{sclk}/2=6.25 \text { ns}
tsclk/2=6.25 ns和最小
t
QUIT
=
t
=
6.25
ns
t_{\text{QUIT}}=t =6.25\text{ ns}
tQUIT=t=6.25 ns。则总时间为
181.25
ns
181.25 \text{ ns}
181.25 ns,可见在160MHz的时钟下实现该模块是可以满足需求的。
同时,为了实现连续触发和单次触发的功能,加入采样触发信号,以触发信号的上升沿为一次数据读取的标志。程序如下:
module spi_80M_5M(
input clk, //160M
input rst_n,
input clk_5M,//5M
input sdata_A,
input sdata_B,
output cs,
output sclk, //80M
output [11:0] out_A,
output [11:0] out_B,
output out_A_valid,
output out_B_valid
);
//上升沿检测
wire start_flag;
reg [1:0] start_flag_dly;
assign start_flag = ~start_flag_dly[1] & start_flag_dly[0];
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
start_flag_dly <= 2'd0;
end
else begin
start_flag_dly <= {start_flag_dly[0], clk_5M};
end
end
reg trans_valid;
reg [4:0] clk_cnt;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
trans_valid <= 1'b0;
end
else if(start_flag == 1'b1) begin
trans_valid <= 1'b1;
end
else if(clk_cnt == 5'd31) begin
trans_valid <= 1'b0;
end
else begin
trans_valid <= trans_valid;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
clk_cnt <= 5'd0;
end
else if(trans_valid == 1'b1) begin
clk_cnt <= clk_cnt + 5'd1;
end
else begin
clk_cnt <= clk_cnt;
end
end
//sclk计数 0-31
reg [4:0] sclk_reg_cnt;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
sclk_reg_cnt <= 5'd0;
end
else if(trans_valid == 1'b1) begin
sclk_reg_cnt <= sclk_reg_cnt + 5'd1;
end
else begin
sclk_reg_cnt <= sclk_reg_cnt;
end
end
//CS控制
reg cs_reg;
assign cs = cs_reg;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cs_reg <= 1'b1;
end
else if(sclk_reg_cnt == 5'd0 && trans_valid == 1'b1) begin
cs_reg <= 1'b0;
end
else if(sclk_reg_cnt == 5'd28 && trans_valid == 1'b1) begin
cs_reg <= 1'b1;
end
else begin
cs_reg <= cs_reg;
end
end
//产生SPI串行时钟
reg sclk_reg;
assign sclk = (cs_reg == 1'b0) ? sclk_reg : 1'b1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
sclk_reg <= 1'b1;
end
else if(sclk_reg_cnt[0] == 1'b1) begin
sclk_reg <= 1'b0;
end
else if(sclk_reg_cnt[0] == 1'b0) begin
sclk_reg <= 1'b1;
end
else begin
sclk_reg <= 1'b1;
end
end
//sclk上升沿更新数据
reg [13:0] data_A_reg;
reg [13:0] data_B_reg;
always @(negedge sclk or negedge rst_n) begin
if(!rst_n) begin
data_A_reg <= 'd0;
data_B_reg <= 'd0;
end
else begin
data_A_reg <= {data_A_reg, sdata_A};
data_B_reg <= {data_B_reg, sdata_B};
end
end
//输出12位有效数据
assign out_A = ( (cs_reg == 1'b1) && (sclk_reg_cnt > 5'd29) ) ? data_A_reg[11:0] : 12'd0;
assign out_B = ( (cs_reg == 1'b1) && (sclk_reg_cnt > 5'd29) ) ? data_B_reg[11:0] : 12'd0;
assign out_A_valid = ( (cs_reg == 1'b1) && (sclk_reg_cnt > 5'd29) )? 1'b1 : 1'b0;
assign out_B_valid = ( (cs_reg == 1'b1) && (sclk_reg_cnt > 5'd29) )? 1'b1 : 1'b0;
endmodule
为了验证SPI时序的正确性,简单起见,芯片的连接方式如下。
设置sclk、cs、sdataA、sdataB的引脚。vinA=2.5V,vinB = 0V。使用ila抓取数据如下。
ila的时钟为160MHz,上图可以看出单次采样的整个时序的长度为32个时钟周期,即200ns。
cs的下降沿到sclk的第一个下降沿的时间为6.25ns,大于时序约束中的
t
2
t_2
t2最小值5ns。
sclk时钟为80MHz,小于sclk时钟的最大值100MHz。
t
CONVERT
t_{\text{CONVERT}}
tCONVERT为27个ila时钟周期,即168.75 ns。
t
QUIT
t_{\text{QUIT}}
tQUIT 为4个ila时钟周期,即25 ns,大于时序约束最小值5 ns。
最后在sclk的下降沿取1比特数据,构成12位采样数据输出。此时,out_A[11:0] = 4095,out_B[11:0] = 2047。AD7356的模拟输入与数字输出对应关系如下图。