今天讲的是FPGA SPI的配置主要是以TI的DAC3283为例
主要有写时序和读时序如下图1,图2所示:
图1:写时序
图2:读时序
今天主要介绍的是写时序:SCLK是DAC3283的串行接口时钟输入。串行数据使能SDENB是DAC3283的使能端。SDIO是串行数据输入端,将DAC3283的输入数据在SCLK的上升边缘上触发。
图3:写时序状态流程图
代码实现:
module spi_ctrl(
input wire sclk,//系统时钟50Mhz
input wire rst_n,
input wire work_en,
ouput reg conf_end,
output wire spi_clk,//小于50-60Mhz,这里使用1Mhz
output wire spi_sdi,
output wire spi_csn
input wire spi_sdo//读输入管脚先不进行编程
);
parameter IDLE = 5'b0_0001;
parameter WAIT = 5'b0_0010;
parameter R_MEM= 5'b0_0100;
parameter W_REG= 5'b0_1000;
parameter STOP = 5'b1_0000;
parameter H_DIV_CYC = 5'd25-1;
//按位与a=&4'b001=1'b0,相邻两位相与
reg [4:0] state;//状态机寄存器变量,编码方式采用独热码
reg [4:0] div_cnt;
reg clk_p=1'b0;
wire clk_n;
reg pose_flag;
reg [3:0] wait_cnt;
reg [3:0] shift_cnt;
reg [4:0] r_addr;
wire [15:0] r_data;
wire wren;
reg [15:0] shift_buf;
reg data_end;
reg sdi;
reg csn;
reg tck;
//分频计数器
always @(posedge sclk or negedge rst_n)
if(rst_n==1'b0)
div_cnt <= 5'd0;
else if(div_cnt==H_DIV_CYC)
div_cnt <= 'd0;
else
div_cnt=div_cnt+1'b1;
//发送数据时钟,分频计数器时钟不允许做寄存器触发时钟,也就是不能卸载always块的触发列表中
always @ (posedge sclk or negedge rst_n)
if(rst_n==1'b0)
clk_p<=1'b0;
else if(div_cnt==H_DIV_CYC)
clk_p<= ~clk_p;
assign clk_n=~clk_p;
//时钟沿
alwsys @ (posedge sclk or negedge rst_n)
if(rst_n==1'b0)
pose_flag<=1'b0;
else if(clk_p==1'b0 && div_cnt==H_DIV_CYC)
pose_flag<=1'b1;
else
pose_flag<=1'b0;
//等待时间
alwsys @ (posedge sclk or negedge rst_n)
if(rst_n==1'b0)
wait_cnt<='d0;
else if(state==WAIT &&pose_flag==1'b1)
wait_cnt<=wait_cnt+1;
else if(state !=WAIT)
wait_cnt<=4'd0;
//shift移出数据
alwsys @ (posedge sclk or negedge rst_n)
if(rst_n==1'b0)
shift_cnt<='d0;
else if(state==W_REG && pose_flag==1'b1)
shift_cnt=shift_cnt+1'b1;
else if(state != W_REG)
shift_cnt<=4'd0;
//读mem的地址产生
always@(posedge sclk or negedge rst_n)
if(rst_n==1'b0)
r_addr<='d0;
else if(stete=R_MEM)
r_addr<=r_addr+1'b1;
assign wren=1'b0;
always@(posedge sclk or negedge rst_n)
if(rst_n==1'b0)
shift_buf<='d0;
else if(state==R_MEM)
shift_buf<=r_data;
else if(state==W_REG && pose_flag==1'b1)
shift_buf<=(shift_buf[14:0],1'b1);
//data_end 最后一个需要移位的数据
always@(posedge sclk or negedge rst_n)
if(rst_n==1'b0)
data_end<=1'b0;
else if(state==R_MEM && (&r_addr) ==1'b1)//等效于r_addr==5'd31
data_end<=1'b1;
//数据输出
always @ (posedge sclk or negedge rst_n)
if(rst_n==1'b0)
sdi<=1'b0;
else if(state==W_REG)
sdi<=shift_buf[15];
else if(state!=W_REG)
sdi<=1'b0;
//使能信号
always @ (posedge sclk or negedge rst_n)
if(rst_n==1'b0)
csn<=1'b0;
else if(state==W_REG)
csn<=1'b0;
else
csn<=1'b1;
//时钟
always @ (posedge sclk or negedge rst_n)
if(rst_n==1'b0)
tck<=1'b0;
else if(state<=W_REG)
tck<=clk_n;
else
tck<=1'b0;
assign spi_clk=tck;
assign spi_csn=csn;
assign spi_sdi=sdi;
//fms
always@(posedge sclk or negedge rst_n)
if(rst_n==1'b0)
state<=IDLE;
else case (state)
IDLE:if(work_en==1'b1)
state<=WAIT;
WAIT:if(wait_cnt[3]=1'b1)
state<=R_MEM;
R_MEM:state<=W_REG;
W_REG:if(shift_cnt==4'd15&& pose_flag==1'b1&&data_end!=1'b1)
state<=WAIT;
else if (shift_cnt==4'd15&&pose_flag==1'b1&&data_end==1'b1)
state<STOP;
STOP: state<=STOP;
default: state<=IDLE;
endcase
//输出标志,级联使用
always @ (posedge sclk or negedge rst_n)
if(rst_n==1'b0)
conf_end<=1'b0;
else if(state==STOP)
conf_end<=1'b1;
//例化语句
endmodule
//时钟skew时钟的相位偏差度,时钟偏斜
测试代码
1ns/1ns
module tb_ex_spi;
reg sclk;
reg rat_n;
reg work_en;
initial begin
rst_n=0;
sclk=0;
#100;
rst_n=1;
end
initial begin
work_en=0;
#150
work_en=1;
end
always #10 sclk=~sclk;
spi_ctrl spi_ctrl_inst(
(sclk);
(rst_n);
(work_en);
();
();
();
();
();
);
endmodule