0.序言
使用vivado联合modelsim实现SPI协议基于ADC128S022进行模拟信号连续采集。
1.SPI协议简介
(1)结构
SPI是串行外设接口,是一种同步/全双工/主从式接口。通常由四根信号线构成:
CS_N:片选信号,主从式接口,可以有多个从机,用片选信号进行从机选择;
SCLK:串行时钟线,由主机提供给从机;
MISO:主机接收(采集)从机数据信号线;
MOSI:主机发送数据给从机信号线;
(2)工作模式
CKP:时钟极性,用来配置时钟线SCLK的电平处于何种状态是空闲状态或者有效状态;
CKE:时钟相位,配置发送数据和采集数据分别是在时钟上升沿还是下降沿;
2.ADC128S022芯片简介
(1)ADC128S022
ADC128S022模数转换芯片有8个通道和12位的分辨率,时钟要求时钟频率范围在1~3.2MHz。可以看出芯片的八个通道(channel),在SPI协议中需要选择采集数据的通道。
(2)信号时序
ADC128S022的SPI协议时序图如下图所示(由数据手册截出):
3.实例
(1)结构框图
设计完成ADC128S022控制模块,根据指定的通道产生控制信号,控制ADC128S022进行连续采集模拟信号,最后采集的信号通过Sam_data传出。
(2)时序图
A 控制信号(截选,因为完整的太长了只截了一部分)
(a)首先测试给控制模块提供复位(rst_n)和通道选择(channel),通道可以随时变化(8个通道[0:7]);
(b)cs_n:片选信号,低电平有效。只使用一片,在复位信号无效后,直接将片选拉低,让芯片处于工作状态;
©cnt:分频计数器,系统时钟使用的是50MHz,ADC芯片时钟范围是0.8~3.2MHz,进行20分频到2.5MHz;20分频,每10个时钟周期产生一个标志位,时钟状态反转一次。
(d)sclk_cnt:sclk状态计数器,共33个状态[0:32],0为初始状态,其后32个状态为有效循环状态;(为什么有效循环状态是32?由上图2(2)ADC的信号时序图可以看出,数据采集周期sclk共16个周期,因为数据发送和采集分别发送在上升沿和下降沿,所以将每个周期一分为2,对应32个状态)
注意:这只给出了时序图截选,完整的太长了,所以对剩下部分进行了描述:
rst_n:保持不变
channel[2:0]:可以随时进行变化,表示ADC采集的数据的通道,本文只仿真产生了一个通道的数据;
cs_n:后续不变
cnt:一直保持0到9的10状态循环计数;
cnt_flag:当cnt状态为0,cnt_flag为1;
sclk_cnt:第一次循环会有0到32共33个状态,因为多了一个初始状态0,后面循环一直为1到32共32个状态的循环;
注意:sclk这里给出只是为了显示相对时序,后续会说明;
B SPI信号(对照2(2)ADC芯片数据手册给出的时序图)
这里给出了一个周期
cs_n:片选信号开始已经给出;
sclk:最开始有一个初始状态(空闲状态,及开始的高电平段),其后对应有效循环中的16个周期,每个周期先低后高,数字表示第几个周期,l和h表示高低电平
din:FPGA控制模块输出给AD芯片的数据,前8个状态为控制信号,其中第3到5个状态需要输出通道选择信号(channel[2:])的高到低位;注意:数据一定要保证在sclk上升沿处采集,给值是在下降沿给;
DOUT:FPGA输入的AD芯片采集的数据;在第5到16个状态得到AD采集的数据的高到低位,共12位;注意:ADC模块采集数据是在SCLK下降沿,所以DOUT数据在SCLK上升沿是稳定的
C SPI连续采集有效循环
B中给出了初始状态和连续采集数据的有效循环状态,这里截出有效循环状态,以后一直保持有效循环:
(3)test_bench
test_bench的核心是模拟ADC采集的数据,并将采集的数据按照要求时序,按位赋给DOUT:
(A) 使用matlab产生模拟的ADC采集的数据
说明:模拟数据为正弦信号,信号频率为10000Hz,以16进制格式保存为txt文件。
clc
clear
close all
%% 模拟采集的正弦信号
fs=2.5e6/16; %采样频率
f0=10000; %信号频率
sam_point=100; %采样点数
t=0:1/fs:1-1/fs;
s=sin(2*pi*f0*t);
s=s.*(2^11);
plot(t(1:100),s(1:100))
s_12bit=zeros(1,sam_point);
for nn=1:sam_point
if(s(nn)<0)
s_12bit(nn) = uint32(2^12+s(nn));
else
s_12bit(nn) = uint32(s(nn));
end
end
fp = fopen('s_sample.txt','w');
for nn=1:sam_point
fprintf(fp,'%X\n',s_12bit(nn));
end
fclose(fp);
(B) test_ben读取模拟数据给DOUT
a首先:在initial模块读出产生的模拟的正弦波信号;
$readmemh("D:/graduate_stuty/FPGA/interface/SPI/s_sample.txt",adc_sam_data);
我们要保证跨过初始状态,在每个有效循环状态中的sclk的4到15个周期的下降沿把采集的数据的11到0位,依次赋给DOUT,保证控制模块在sclk 5到16个状态采集到采样数据的12位。
4.完整代码
(1)ADC128S022控制模块
module SPI_interface(
input clk ,
input rst_n ,
input [2:0] channel ,
output reg ADC_cs_n,
output reg ADC_sclk,
output reg ADC_din , //fpga给adc芯片的输出信号
input ADC_dout, //adc芯片给fpga的采样数据
output reg [11:0] Sam_data
);
//片选信号产生模块,只有一片,片选信号直接由复位信号产生
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
ADC_cs_n <= 1'b1;
else
ADC_cs_n <= 1'b0;
end
//20分频器产生,每10个状态产生一个状态反转标志信号
reg [3:0] cnt_10;
reg cnt_flag;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_10 <= 4'd0;
cnt_flag <= 1'b0;
end
else if(ADC_cs_n == 1'b0)begin
if(cnt_10 == 4'd9)begin
cnt_10 <= 4'd0;
cnt_flag <= 1'b1;
end
else begin
cnt_10 <= cnt_10 + 1'b1;
cnt_flag <= 1'b0;
end
end
end
//SCLK状态计数器产生,33个状态[0:32],初始状态为0,有效循环状态为[1:32]
reg [5:0] sclk_cnt;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
sclk_cnt <= 6'd0;
else if(ADC_cs_n == 1'b0)begin
if(cnt_flag == 1'b1)
if(sclk_cnt == 6'd32)
sclk_cnt <= 6'd1;
else
sclk_cnt <= sclk_cnt + 1'b1;
else
sclk_cnt <= sclk_cnt;
end
else
sclk_cnt <=6'd0;
end
//SCLK,DIN赋值及DOUT数据采集
reg [11:0] Sam_data_r;//输出数据存储临时变量
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
Sam_data <= 12'd0;
Sam_data_r <= 12'd0;
ADC_sclk <= 1'd1;
ADC_din <= 1'd0;
end
else begin
case(sclk_cnt)
6'd1:begin ADC_sclk <= 1'd0; end
6'd2:begin ADC_sclk <= 1'd1; Sam_data <= Sam_data_r; end
6'd3:begin ADC_sclk <= 1'd0; end
6'd4:begin ADC_sclk <= 1'd1; end
6'd5:begin ADC_sclk <= 1'd0; ADC_din <= channel[2]; end
6'd6:begin ADC_sclk <= 1'd1; end
6'd7:begin ADC_sclk <= 1'd0; ADC_din <= channel[1];end
6'd8:begin ADC_sclk <= 1'd1; end
6'd9:begin ADC_sclk <= 1'd0; ADC_din <= channel[0];end
6'd10:begin ADC_sclk <= 1'd1; Sam_data_r[11] <= ADC_dout; end
6'd11:begin ADC_sclk <= 1'd0; end
6'd12:begin ADC_sclk <= 1'd1; Sam_data_r[10] <= ADC_dout;end
6'd13:begin ADC_sclk <= 1'd0; end
6'd14:begin ADC_sclk <= 1'd1; Sam_data_r[9] <= ADC_dout;end
6'd15:begin ADC_sclk <= 1'd0; end
6'd16:begin ADC_sclk <= 1'd1; Sam_data_r[8] <= ADC_dout;end
6'd17:begin ADC_sclk <= 1'd0; end
6'd18:begin ADC_sclk <= 1'd1; Sam_data_r[7] <= ADC_dout;end
6'd19:begin ADC_sclk <= 1'd0; end
6'd20:begin ADC_sclk <= 1'd1; Sam_data_r[6] <= ADC_dout;end
6'd21:begin ADC_sclk <= 1'd0; end
6'd22:begin ADC_sclk <= 1'd1; Sam_data_r[5] <= ADC_dout;end
6'd23:begin ADC_sclk <= 1'd0; end
6'd24:begin ADC_sclk <= 1'd1; Sam_data_r[4] <= ADC_dout;end
6'd25:begin ADC_sclk <= 1'd0; end
6'd26:begin ADC_sclk <= 1'd1; Sam_data_r[3] <= ADC_dout;end
6'd27:begin ADC_sclk <= 1'd0; end
6'd28:begin ADC_sclk <= 1'd1; Sam_data_r[2] <= ADC_dout;end
6'd29:begin ADC_sclk <= 1'd0; end
6'd30:begin ADC_sclk <= 1'd1; Sam_data_r[1] <= ADC_dout;end
6'd31:begin ADC_sclk <= 1'd0; end
6'd32:begin ADC_sclk <= 1'd1; Sam_data_r[0] <= ADC_dout;end
default:begin Sam_data <= 12'd0; Sam_data_r <= 12'd0; ADC_sclk <= 1'd1; ADC_din <= 1'd0;end
endcase
end
end
endmodule
(2)test_bench
`timescale 1ns / 1ps
module SPI_interface_tb;
reg clk ;
reg rst_n ;
reg [2:0] channel ;
wire ADC_cs_n;
wire ADC_sclk;
wire ADC_din ; //fpga给adc芯片的输出信号
reg ADC_dout; //adc芯片给fpga的采样数据
wire [11:0] Sam_data;
parameter T = 20;
//ADC采集的模拟信号
reg [11:0] adc_sam_data [0:99];
//初始化模块
initial begin
$readmemh("D:/graduate_stuty/FPGA/interface/SPI/s_sample.txt",adc_sam_data);
clk = 1'b0;
rst_n = 1'b0;
channel = 3'd0;
#(T*4);
rst_n = 1'b1;
channel = 3'd3;
end
//时钟信号产生模块
always#(T/2) clk = ~clk;
//模块例化
SPI_interface u_SPI_interface(
.clk (clk ),
.rst_n (rst_n ),
.channel (channel ),
.ADC_cs_n (ADC_cs_n),
.ADC_sclk (ADC_sclk),
.ADC_din (ADC_din ), //fpga给adc芯片的输出信号
.ADC_dout (ADC_dout), //adc芯片给fpga的采样数据
.Sam_data (Sam_data)
);
//模拟ADC采集的信号
//复位信号上升沿检测
reg rst_n_r;
reg rst_flag=0;
always@(posedge clk)begin
rst_n_r <= rst_n;
end
always@(posedge clk)begin
if((rst_n==1'b1)&&(rst_n_r==1'b0))
rst_flag <= 1'b1;
else
rst_flag <= rst_flag;
end
//初始计数器
reg [3:0] cnt_12;
always@(posedge clk)begin
if(!rst_n)
cnt_12 <= 0;
else if((rst_flag==1)&&(cnt_12<4'd11))
cnt_12 <= cnt_12 +1'b1;
else
cnt_12 <= cnt_12;
end
//有效循环计数器20*16=320
reg [8:0] cnt_320;
reg cnt_320_flag;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_320 <= 9'd0;
cnt_320_flag <= 1'b0;
end
else if(cnt_12 == 4'd11)begin
if(cnt_320 == 9'd319)begin
cnt_320 <= 9'd0;
cnt_320_flag <= 1'b1;
end
else begin
cnt_320 <= cnt_320 + 1'b1;
cnt_320_flag <= 1'b0;
end
end
else begin
cnt_320 <= 9'd0;
cnt_320_flag <= 1'b0;
end
end
//数据地址
reg [6:0] addr_99;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
addr_99 <= 7'd0;
else if(cnt_320_flag == 1)begin
if(addr_99 == 7'd99)
addr_99 <= 0;
else
addr_99 <= addr_99 + 1'b1;
end
else
addr_99 <= addr_99;
end
//状态赋值
reg [11:0] sam_data_r;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ADC_dout <= 1'b0;
end
else begin
case(cnt_320)
9'd1:begin sam_data_r <= adc_sam_data[addr_99]; end
9'd81:begin ADC_dout <= sam_data_r[11]; end
9'd101:begin ADC_dout <= sam_data_r[10]; end
9'd121:begin ADC_dout <= sam_data_r[9]; end
9'd141:begin ADC_dout <= sam_data_r[8]; end
9'd161:begin ADC_dout <= sam_data_r[7]; end
9'd181:begin ADC_dout <= sam_data_r[6]; end
9'd201:begin ADC_dout <= sam_data_r[5]; end
9'd221:begin ADC_dout <= sam_data_r[4]; end
9'd241:begin ADC_dout <= sam_data_r[3]; end
9'd261:begin ADC_dout <= sam_data_r[2]; end
9'd281:begin ADC_dout <= sam_data_r[1]; end
9'd301:begin ADC_dout <= sam_data_r[0]; end
default:begin ADC_dout <= ADC_dout; end
endcase
end
end
endmodule