SPI总线详解见:SPI总线协议
DAC3282的datasheet可去免费下载:DAC3282datasheet免费下载
本文基于TI公司的DAC3283进行讲解,通过SPI对其进行讲解,并且配有详细的Verilog代码和testbench。
DAC寄存器:一共有32个寄存器,每个寄存器的详细配置手册上均有写。
每个寄存器可以写入的数据为5Byte,其中第1Byte的用法在Table2中详细说明,包括读写控制,剩余4Byte的用法,寄存机地址等。
读写时序如下图所示:Figure32中的写时序中,默认的是[N1:N0]=00,表示只传输1Byte的数据,SDENB为片选信号,SDIO为双向接口,此图是按照三线模式分析的,和四线模式的唯一差别就是四线模式用SDI、SDO来分别表示写数据和读数据。
详细设置过程:
a. 首先明确SDIO数据中心对应着SCLK时钟的上升沿,在时钟的下降沿读出数据,因此在产生SCLK的数据时,需要将时钟进行180度的相移。此时的建立保持时间也最大,效果最好。
b. 建立状态机,确定SPI的状态迁移,一上电为IDLE状态,其中WAIT状态用于分割两次命令的等待时间,READ_MEM是读取ROM中的状态信息,WRITE_REG将读取的信息写入DAC芯片,当32个寄存器均被配置完成,跳入STOP状态。
Verilog代码如下:
`timescale 1ns / 1ps
//
// Engineer:mxGe_UESTC
//
// Create Date: 2020/04/25 19:02:16
// Design Name:
// Module Name: spi_ctrl
// Project Name:
//
module spi_ctrl(
input wire sclk,//系统时钟50Mhz
input wire rst_n,
input wire work_en,//触发配置操作的使能
output reg conf_end,
output wire spi_clk,//50-60mhz
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;
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;
always @(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;
always @(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'b1;
else if(state != WAIT)
wait_cnt <= 4'd0;
//fsm 命令8bit,数据8bit,一共16bit
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)
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(state == R_MEM)
r_addr <= r_addr + 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;
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};
//数据的输出
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
sdi <= 1'b0;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
csn <= 1'b1;
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;
always @(posedge sclk or negedge rst_n)
if(rst_n == 1'b0)
conf_end <= 1'b0;
else if(state == STOP)
conf_end <= 1'b1;
ram_16x32_sr ram_16x32_sr_inst (
.clka(sclk), // input wire clka
.ena(1'b1), // input wire ena
.wea(wren), // input wire [0 : 0] wea 写使能高有效,读使能低有效
.addra(r_addr), // input wire [4 : 0] addra
.dina(16'd0), // input wire [15 : 0] dina
.douta(r_data) // output wire [15 : 0] douta
);
endmodule
testbench
`timescale 1ns / 1ps
//
// Engineer: mxGe_UESTC
// Create Date: 2020/04/25 20:32:06
// Design Name:
// Module Name: tb_spi_ctrl
// Project Name:
//
module tb_spi_ctrl();
reg sclk,rst_n;
reg work_en;
wire spi_clk,sclk_csn,spi_sdi;
reg [15:0] send_mem [31:0];
reg [15:0] shift_buf;
initial begin
rst_n =0;
sclk =0;
#100;
rst_n =1;
end
initial begin
work_en =0;
#150
work_en =1;
end
initial begin
$readmemb("dac_ini_16x32.mif",send_mem);
end
always #10 sclk = ~sclk;
initial begin
rec_spi();
end
spi_ctrl spi_ctrl_inst(
.sclk (sclk),//系统时钟50Mhz
.rst_n (rst_n),
.work_en (work_en),//触发配置操作的使能
.conf_end (),
.spi_clk (spi_clk),//50-60mhz
.spi_sdi (spi_sdi),
.spi_csn (spi_csn),
.spi_sdo ()//读输入管脚不进行编程
);
task rec_spi();
integer i,j;
begin
for (i=0;i<32;i=i+1)begin
for(j=0;j<16;j=j+1)begin
@(posedge spi_clk);
shift_buf = {shift_buf[14:0],spi_sdi};
if(j==15 && shift_buf == send_mem[i])
$display("ok data index is %d rec_d=%d send_d=%d",i,shift_buf,send_mem[i]);
else if(j== 15)
$display("error");
end
end
end
endtask
endmodule