【CPLD+Verilog】CPLD实现SPI接口
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
CPLD 实现SPI 接口
1 实现原理
CPLD 实现SPI 接口模块,通过对寄存器的操作,实现SPI 接口功能,对外部SPI 设备进行访问。
CPLD 内部SPI 模块逻辑框图如下所示。
CLK_DIV
RX_SHIFT_REG
STATUS LOGIC
spi_clk
spi_cs spi_do
spi_tx_data_we_pulse
reset_n spi_tx_data spi_rx_data spi_clk_div spi_rx_data_rd_pulse
spi_di
spi_tx_data_we_busy spi_rx_data_rd_ready spi_data_tx_we_overflow spi_data_rx_rd_nop spi_data_rx_rd_overtime cpld_clk
CONTROL LOGIC
COUNT LOGIC
通过对主时钟分频,得到的SPI 接口时钟,其分频值可通过spi_clk_div 值设定,最小为6分频,当设置分频值低于6分频时,默认6分频。
SPI 接口的片选信号spi_cs 在写数据传输寄存器spi_tx_data 时,通过自动产生写脉冲spi_tx_data_we_pulse ,逻辑综合生成。
spi_rx_data_rd_pulse 在读取接收数据spi_rx_data 的数据时自动产生。
spi_di 和spi_do 分别由数据移位寄存器通过数据移位产生和接收。
spi_clk 由内部分频时钟和传输状态逻辑综合输出,在有数据时输出,无数据时保持高电
平。
状态逻辑spi_tx_data_we_busy 信号分别表示数据正在传输,无法写入spi_tx_data 寄存器; spi_rx_data_rd_ready 信号表示数据接收完成,可读取spi_rx_data 寄存器的值。
错误状态逻辑spi_data_tx_we_overflow 信号表示在spi_tx_data 寄存器中写入了待传输值后,还未传输就又写入新的传输数据,表示写数据溢出。
spi_data_rx_rd_nop 信号表示在
spi_rx_data寄存器中没有接收值时,去读取了无效的数据,表示读空数据。
spi_data_rx_rd_overtime信号表示在spi_rx_data寄存器中有接收数据时,在下一次接收数据接收完成时都没有读走,表示读取数据超时。
产生接口错误时,通过reset_n复位SPI接口模块,可清楚错误。
最终传输生成的时序图如下。
在数据发送时,将待发送数据写入spi_tx_data,数据将自动发送,之后检测
spi_tx_data_we_busy状态值,待此信号无效(0)时,写入下一个待发送的数据,重复此动作,直到数据发送完成,在正在发送的数据传输完成都没有新数据写入时,传输自动完成。
在数据接收时,将任意数据写入spi_tx_data,然后等待spi_rx_data_rd_ready信号有效(1),然后读取接收到的数据,重复此动作,直到需要接收的数据完成,当不在写入后,传输完成。
2 CPLD代码
module spi(
reset_n,
cpld_clk,
spi_clk_div,
spi_tx_data,
spi_tx_data_we_pulse,
spi_tx_data_we_busy,
spi_rx_data,
spi_rx_data_rd_pulse,
spi_rx_data_rd_ready,
spi_do,
spi_clk,
spi_cs,
spi_di,
spi_data_tx_we_overflow,
spi_data_rx_rd_nop,
spi_data_rx_rd_overtime,
///////////////////////for debug/////////////////////////////////
spi_clk_inner,
spi_clk_posedge_pulse,
spi_clk_negedge_pulse,
data_shift_reg,
spi_busy,
count_spi,
spi_enable,
spi_en_sync,
spi_busy_end_pulse,
spi_busy_start_pulse
);
//////////////////////////内部接口信号////////////////////////////////
input [7:0] spi_clk_div; //设置时钟分频值,得到SPI时钟
input reset_n; //模块复位信号
input cpld_clk; //接口全局时钟
input [7:0] spi_tx_data; //SPI数据传输寄存器。
input spi_tx_data_we_pulse; /*SPI传输数据写入脉冲,脉冲宽度为1个cpld_clk时钟
周期*/
output spi_tx_data_we_busy; //SPI传输数据寄存器写忙标志,1表示不能写
output [7:0] spi_rx_data; //SPI数据接收寄存器
input spi_rx_data_rd_pulse; /*SPI数据接收寄存器读取标志,脉冲宽度为1个cpld_clk时钟周期*/
output spi_rx_data_rd_ready; //SPI接收数据ready标志,1表示可以读取接收数据。
output spi_data_tx_we_overflow; //SPI传输数据写溢出标志,1表示有错误产生output spi_data_rx_rd_nop; //SPI接收数据读无效标志,1表示有错误产生output spi_data_rx_rd_overtime; //SPI接收数据读超时标志,1表示有错误产生
//////////////////////////外出接口信号////////////////////////////////
output spi_do; //SPI接口数据输出信号,MOSI
output spi_clk; //SPI接口时钟信号
output spi_cs; //SPI接口片选信号
input spi_di; //SPI接口数据接收信号,MISO
/////////////////////////////for debug/////////////////////////////////
output spi_clk_inner; //分频出的SPI时钟
output spi_clk_posedge_pulse; //分频的SPI时钟上升沿
output spi_clk_negedge_pulse;//分频的SPI时钟下降沿
output [7:0] data_shift_reg; //数据移位寄存器
output spi_busy; //接口忙信号,表示数据正在传输
output [3:0] count_spi; //SPI数据移位传输计数
output spi_enable; //SPI数据传输开始信号
output spi_en_sync; //SPI数据传输开始信号与分频时钟同步信号
output spi_busy_start_pulse; //接口忙开始标志,一个cpld_clk时钟宽度output spi_busy_end_pulse; //接口忙结束标志,一个cpld_clk时钟宽度
//////////////////////////spi reference clock////////////////////////////
reg [7:0] counter_div;
reg spi_clk_inner; //内部分频得到的SPI时钟。
wire[7:0] spi_clk_div;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
counter_div <= 8'h01;
spi_clk_inner <= 1'b0;
end
else if(spi_clk_div > 8'h06) //当分频倍数大于6时,以分频值分频begin
if(counter_div == (spi_clk_div&8'hfe)>>1) //确保偶数倍分频
begin
spi_clk_inner <= ~spi_clk_inner;
counter_div <= counter_div+8'h01;
end
else if(counter_div == (spi_clk_div&8'hfe))
begin
spi_clk_inner <= ~spi_clk_inner;
counter_div <= 8'h01;
end
else //当分频倍数小于或等于6时,默认6分频
counter_div <= counter_div+8'h01;
end
begin
if(counter_div == 8'h03)
begin
spi_clk_inner <= ~spi_clk_inner;
counter_div <= counter_div+8'h01;
end
else if(counter_div == 8'h06)
begin
spi_clk_inner <= ~spi_clk_inner;
counter_div <= 8'h01;
end
else
counter_div <= counter_div+8'h01;
end
end
////////////////////////内部分频时钟延迟,用于得到SPI时钟上升沿和下降沿脉冲/////////////////// reg spi_clk_dly;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
spi_clk_dly <= 1'b1;
end
else
begin
spi_clk_dly <= spi_clk_inner;
end
wire spi_clk_posedge_pulse;
wire spi_clk_negedge_pulse;
assign spi_clk_posedge_pulse = ({spi_clk_dly,spi_clk_inner} == 2'b01) ? 1'b1 : 1'b0; /*SPI时钟上升沿脉冲*/
assign spi_clk_negedge_pulse = ({spi_clk_dly,spi_clk_inner} == 2'b10) ? 1'b1 : 1'b0; /*SPI时钟下升沿脉冲*/
////////////////////////////////////////////////////////////////产生传输使能/////////////////////////////////////////////////////// reg spi_enable;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
spi_enable <= 1'b0;
end
else if(spi_tx_data_we_pulse == 1'b1) //发送数据寄存器写脉冲标志
begin
spi_enable <= 1'b1;
end
else if((spi_tx_data_we_busy == 1'b0)&&(spi_busy_end_pulse == 1'b1))
/*在没有新发送数据要发送,且传输结束后,传输结束*/
begin
spi_enable <= 1'b0;
end
else;
end
/////////////////////////////////////////////////产生传输使能与cpld_clk时钟同步///////////////////////////////////// reg spi_en_sync;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
spi_en_sync <= 1'b0;
end
else if((spi_clk_posedge_pulse == 1'b1)&&(spi_enable == 1'b1)&&(spi_busy == 1'b1))
begin
spi_en_sync <= 1'b1;
end
else if((spi_clk_negedge_pulse == 1'b1)&&(spi_enable == 1'b0)&&(spi_busy == 1'b0))
begin
spi_en_sync <= 1'b0;
end
else;
end
/////////////////////////////////////////////////数据发送接收计数/////////////////////////////////////
reg [3:0] count_spi;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
count_spi <= 4'h0;
end
else if(spi_en_sync == 1'b0) //传输结束,计数归零
begin
count_spi <= 4'h0;
end
else if((spi_clk_negedge_pulse == 1'b1)&&(spi_en_sync == 1'b1)&&(spi_busy ==
1'b1)&&(count_spi != 4'h8)) //传输开始,在SPI时钟下降沿计数
begin
count_spi <= count_spi+4'h1;
end
else if((spi_clk_negedge_pulse == 1'b1)&&(count_spi >= 4'h8) &&(spi_en_sync == 1'b1))
/*在连续传输的情况下,从第二个字节数据开始,计数从1开始*/
begin
count_spi <= 4'h1;
end
else;
end
//////////////////////////////////////////产生数据写入发送数据寄存器忙闲标志
///////////////////////////////////// reg spi_tx_data_we_busy;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
spi_tx_data_we_busy <= 1'b0;
end
else if(spi_tx_data_we_pulse == 1'b1) /*有数据写入数据发送寄存器后,数据写入发
送数据寄存器标志忙*/
begin
spi_tx_data_we_busy <= 1'b1;
end
else if(spi_busy_start_pulse == 1'b1) /*数据发送寄存器中数据转移到移位寄存器之后,
数据写入发送数据寄存器标志闲*/
begin
spi_tx_data_we_busy <= 1'b0;
end
else;
end
//////////////////////////////////////////产生读取接收数据寄存器是否OK标志///////////////////////////////////// reg spi_rx_data_rd_ready;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
spi_rx_data_rd_ready <= 1'b0;
end
else if(spi_rx_data_rd_pulse == 1'b1) /*接收数据被读走后,数据接收寄存器读取OK标志无效*/
begin
spi_rx_data_rd_ready <= 1'b0;
end
else if(spi_busy_end_pulse == 1'b1) /*接收数据从移位寄存器转出后,数据接收寄存器读取OK 标志有效*/
begin
spi_rx_data_rd_ready <= 1'b1;
end
else;
end
//////////////////////////////////////////数据发送接收模块///////////////////////////////////// reg [7:0] data_shift_reg;
reg spi_busy;
reg spi_busy_start_pulse;
reg spi_busy_end_pulse;
reg [7:0] spi_rx_data;
wire spi_di; /*数据接收信号线*/
wire[7:0] spi_tx_data;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
data_shift_reg <= 8'h00;
spi_busy <= 1'b0;
spi_busy_start_pulse <= 1'b0;
spi_busy_end_pulse <= 1'b0;
spi_rx_data <= 8'h00;
end
else if((spi_tx_data_we_busy == 1'b1)&&(spi_busy == 1'b0)) /*在数据发送寄存器有发送数据,且当前没有数据传输时,发送数据转移到移位寄存器,同时置SPI忙标志*/ begin
data_shift_reg <= spi_tx_data;
spi_busy <= 1'b1;
spi_busy_start_pulse <= 1'b1;
spi_busy_end_pulse <= 1'b0;
end
else if((spi_clk_posedge_pulse == 1'b1)&&(spi_busy == 1'b1)&&(count_spi <
4'h8)&&(count_spi!= 4'h0)) /*数据传输开始后,在SPI时钟上升沿,数据移位寄存器移位,发送和接收数据*/
begin
data_shift_reg <= {data_shift_reg[6:0],spi_di};
end
else if((spi_clk_posedge_pulse == 1'b1)&&(spi_busy == 1'b1)&&(count_spi == 4'h8))
/*发送和接收一个字节的最后1bit数据,并产生此字节传输结束脉冲标志*/
begin
data_shift_reg <= {data_shift_reg[6:0],spi_di};
spi_busy_end_pulse <= 1'b1;
end
else if(spi_busy_end_pulse == 1'b1)
/*发送和接收完成后,将数据接收移位寄存器中数据转移到接收数据寄存器*/ begin
spi_rx_data <= data_shift_reg;
spi_busy <= 1'b0;
spi_busy_end_pulse <= 1'b0;
end
else
begin
spi_busy_start_pulse <= 1'b0;
spi_busy_end_pulse <= 1'b0;
end
end
//////////////////////////////////////////SPI模块MOSI信号产生模块/////////////////////////////////////
reg spi_do;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
spi_do <= 1'b0;
end
else if((spi_en_sync == 1'b1)&&(spi_clk_negedge_pulse == 1'b1)) /*在SPI时钟下降沿,将数据每个bit最高位开始,依次发送出去*/
begin
spi_do <= data_shift_reg[7];
end
else;
end
//////////////////////////////////////////SPI模块片选信号产生模块/////////////////////////////////////
wire spi_cs;
assign spi_cs = ~spi_en_sync;
//////////////////////////////////////////SPI模块时钟信号产生模块/////////////////////////////////////
wire spi_clk;
assign spi_clk = ((~((spi_enable|spi_busy)&spi_en_sync)) == 1'b1) ? 1'b1 : spi_clk_inner;
//////////////////////////////////////////SPI模块传输错误产生模块/////////////////////////////////////
reg spi_data_tx_we_overflow;
reg spi_data_rx_rd_nop;
reg spi_data_rx_rd_overtime;
always@(posedge cpld_clk or negedge reset_n)
begin
if(reset_n == 1'b0)
begin
spi_data_tx_we_overflow <= 1'b0;
spi_data_rx_rd_nop <= 1'b0;
spi_data_rx_rd_overtime <= 1'b0;
end
else
begin
if((spi_tx_data_we_busy == 1'b1)&&(spi_tx_data_we_pulse== 1'b1)) /*在数据发送寄存器已有数据时,再次写数据到数据发送寄存器,产生发送数据写溢出标志*/ begin
spi_data_tx_we_overflow <= 1'b1;
end
else;
if((spi_rx_data_rd_pulse == 1'b1)&&(spi_rx_data_rd_ready == 1'b0)) /*在数据接收寄存器没有数据时,读取数据接收寄存器内数据,产生接收数据写空标志*/
begin
spi_data_rx_rd_nop <= 1'b1;
end
else;
if((spi_busy_end_pulse == 1'b1)&&(spi_rx_data_rd_ready == 1'b1)) /*在多字节连续接收时,在数据接收寄存器内有数据,到下个接收字节数据转移到数据接收寄存器时都没有被读走,产生接收数据读取超时标志*/
begin
spi_data_rx_rd_overtime <= 1'b1;
end
else;
end
end
endmodule
3 仿真结果
发送和接收一个字节数据:
发送和接收多个字节数据:
出现传输错误,产生错误标志:
复位SPI模块,清除错误标志:
本文档下载自360文档中心,更多营销,职业规划,工作简历,入党,工作报告,总结,学习资料,学习总结,PPT模板下载,范文等文档下载;转载请保留出处:/doc/info-997ab517f7ec4afe05a1df1c.html。