本文主要是学习SPI通信时记下的笔记
SPI通信协议原理:
串行外设接口(SPI)是微控制器和外围IC(如传感器、ADC、DAC、移位寄存器、SRAM等)之间使用最广泛的接口之一。
SPI是一种同步、全双工、主从式接口。来自主机或从机的数据在时钟上升沿或下降沿同步。主机和从机可以同时传输数据。SPI接口可以是3线式或4线式。
SPI四种通信模式:
SPI通信有4种不同的模式,不同的从设备可能在出厂是就是配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以我们可以对我们的主设备的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式,具体如下:
Mode0:CPOL=0,CPHA=0
Mode1:CPOL=0,CPHA=1
Mode2:CPOL=1,CPHA=0
Mode3:CPOL=1,CPHA=1
时钟极性CPOL是用来配置SCLK的电平出于哪种状态时是空闲态或者有效态,时钟相位CPHA是用来配置数据采样是在第几个边沿:
CPOL=0,CPHA=0:此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,
SCLK由低电平到高电平的跳变,所以数据采样是在上升沿,数据发送是在下降沿。
CPOL=0,CPHA=1:此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,
SCLK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=0:此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,
SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。
CPOL=1,CPHA=1:此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,
SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。
采用ADC128S022仿真SPI通信接口:
DIN数据只会在低5个SCLK下降时刻进行输入,等数据稳定后在第6个上升沿时进行采样,后面的数据同理。
DOUT数据第9时刻输出,在第10个时刻等输出数据稳定后再采样后统一传给r_data寄存器。
更细致的时序图:
驱动该adc使用的线性序列机编写:
//
// Company:
// Engineer:
//
// Create Date: 2025/04/01 15:33:36
// Design Name:
// Module Name: adc128_dri
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
//这个驱动是SPI接口的
module adc128_dri(
input clk, //50Mhz
input rst_n,
input start,
input [2:0] channel,
//*****ADC128S022*****//
output reg DIN,
output reg CS_N,
output reg SCLK,
input DOUT,
output reg done,
output reg [11:0] data
);
reg en;
reg [2:0] r_channel;
reg [4:0] cnt;
reg cnt_flag;
reg [5:0] SCLK_CNT;
reg [11:0] r_data;
//转换使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
en <= 0;
else if(start)
en <= 1;
else
en <= en;
end
//r_channel
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
r_channel <=0;
else if(start)
r_channel <= channel;
else
r_channel <= r_channel;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt<=0;
else if(en)begin
if(cnt == 5'd9)
cnt <= 5'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 5'd0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_flag <= 1'b0;
else if(cnt == 5'd9)
cnt_flag <= 1'b1;
else
cnt_flag <= 1'b0;
end
//***********SCLK_CNT**********//
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
SCLK_CNT <= 6'd0;
else if(en)begin
if(SCLK_CNT ==6'd33)
SCLK_CNT <= 6'd0; //分频计数器
else if(cnt_flag)
SCLK_CNT <= SCLK_CNT + 6'd1;
else
SCLK_CNT <= SCLK_CNT;
end
else
SCLK_CNT <= 0;
end
//*******线性序列机,数据发送\接收********//
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
SCLK <= 1'b1;
CS_N <= 1'b1;
DIN <= 1'b1;
end
else if(en)begin
case(SCLK_CNT)
6'd0:begin CS_N <= 1'b0;end
6'd1:begin SCLK <= 1'b0; DIN <=1'b0;end
6'd2:begin SCLK <= 1'b1;end
6'd3:begin SCLK <= 1'b0;end
6'd4:begin SCLK <= 1'b1;end
6'd5:begin SCLK <= 1'b0;DIN <= r_channel[2];end //通道第一个是最高位
6'd6:begin SCLK <= 1'b1;end
6'd7:begin SCLK <= 1'b0;DIN <= r_channel[1];end
6'd8:begin SCLK <= 1'b1;end
6'd9:begin SCLK <= 1'b0;DIN <= r_channel[0];end //奇数位输入进去
6'd10,6'd12,6'd14,6'd16,6'd18,6'd20,6'd22,6'd24,6'd26,6'd28,6'd30,6'd32:
begin SCLK <= 1'b1;r_data <= {r_data[10:0],DOUT};end //紧接着偶数倍采样进去
6'd11,6'd13,6'd15,6'd17,6'd19,6'd21,6'd23,6'd25,6'd27,6'd29,6'd31:
begin SCLK <= 1'b0;end //控制SCLK的时序
6'd33:begin CS_N <= 1'b1;end
default:begin CS_N <= 1'b1;end //SCLK <= 1'b1;
endcase
end
else begin
SCLK <= 1'b1;
CS_N <= 1'b1;
DIN <= 1'b1;
end
end
//*********done***********//
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
done <= 1'b0;
else if(SCLK_CNT == 6'd33)
done <= 1'b1;
else
done <= 1'b0;
end
//*********data***********//
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
data <= 12'b0;
else if(SCLK_CNT == 6'd33)
data <= r_data;
else
data <= data;
end
endmodule
仿真:SCLK时钟输入为400ns一周期,即2.5MHz,符合0.8~3.2MHz的时钟频率要求。
再CS_N有效期内各状态符合逻辑设定
tb:
`timescale 1ns/1ns
`define sin_data_file "C:/Users/LISON/Desktop/SPI/SPI/sim/sin_12bit.txt"
module tb_adc18s022;
reg clk;
reg rst_n;
reg start;
reg [2:0]channel;
wire SCLK;
wire DIN;
wire CS_N;
reg DOUT;
wire done;
wire [11:0]data;
reg [11:0]memory[4095:0];//测试波形数据存储空间
reg [11:0]address;//存储器地址
adc128_dri SPI_inst(
.clk(clk),
.rst_n(rst_n),
.start(start),
.channel(channel),
//========ADC128S022===========//
.SCLK(SCLK),
.DIN(DIN),
.CS_N(CS_N),
.DOUT(DOUT),
.done(done),
.data(data)
);
initial clk = 1'b1;
always#10 clk = ~clk;
initial $readmemh(`sin_data_file,memory);//读取原始波形数据读到memory中
integer i;
initial begin
rst_n = 1'b0;
channel = 'd0;
start = 1'b0;
DOUT = 1'b0;
address = 0;
#100;
rst_n = 1'b1;
#100;
channel = 3;
for(i=0;i<3;i=i+1)begin
for(address=0;address<4095;address=address+1)begin
start = 1;
#20;
start = 0;
gene_DOUT(memory[address]); //依次将存储器中存储的波形读出,按照ADC的转换结果输出方式送到DOUT信号线上
@(posedge done); //等待转换完成信号
#200;
end
end
#20000;
$stop;
end
//将并行数据按照ADC的数据输出格式,送到DOUT信号线上,供控制模块采集读取
task gene_DOUT;
input [15:0]vdata;
reg [4:0]cnt;
begin
cnt = 0;
wait(!CS_N);
while(cnt<16)begin
@(negedge SCLK) DOUT = vdata[15-cnt]; //高位到低位的串行输出
cnt = cnt + 1'b1;
end
end
endtask
endmodule