用Verilog实现一个SPI从机
source link: https://www.taterli.com/9824/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
用Verilog实现一个SPI从机
用Verilog实现一个SPI从机
网上很多教程都是怎么做SPI主机,怎么控制外部的东西,但是有时候FPGA是作为接受上游信息的存在的,有必要实现一个从机协议.我这里就一步一步的描述我是怎么思考然后最终实现一个从机的.
首先我这里确定一下我需要的信号.
module top(
input wire clk_i,
input wire rst_i,
input wire sdi_csn_i,
input wire sdi_clk_i,
input wire sdi_dat_i,
output wire sdi_dat_o,
output wire [5:0] led
);
因为我的开发板只有6个LED,所以就暂时让收到的8B数据中的6B显示在LED,其余的都是SPI本身的信号,以及系统主时钟,也可以理解成采样时钟,并且采样时钟是快于SPI时钟的.
然后跨时钟域采集他们,并且要捕获SCK的上升沿.
reg [2:0] sync_sck_ff;
reg [1:0] sync_csn_ff;
reg [1:0] sync_sdi_ff;
// SCK 边缘采集
assign sync_sck = sync_sck_ff[1] ^ sync_sck_ff[2];
assign sync_csn = sync_csn_ff[1];
assign sync_sdi = sync_sdi_ff[1];
// 跨时钟域处理
always @(posedge clk_i or negedge rstn_i) begin
if(!rstn_i) begin
sync_sck_ff <= 1'b0;
sync_csn_ff <= 1'b0;
sync_sdi_ff <= 1'b0;
end else begin
sync_sck_ff <= {sync_sck_ff[1:0],sdi_clk_i};
sync_csn_ff <= {sync_csn_ff[0],sdi_csn_i};
sync_sdi_ff <= {sync_sdi_ff[0],sdi_dat_i};
end
end
其中sync_sck指示目前时钟跳变了,sync_csn是传递到clk_i时钟域的CS,sync_sdi是传递到clk_i时钟域的DAT,sdi_dat_o为什么没在这里呢,因为我们clk_i是快时钟,我们只要保证快时钟有足够的周期数,慢时钟的sdi_clk_i就能采集,而因为我们一定会保持到下一个sdi_clk_i来到,所以肯定是足够的.
接下来到最难的状态机部分了,我这里定义了三个状态,用serial_state来记录.
- serial_state = 00,属于IDLE状态,等CSN有效后转到10状态.
- serial_state = 10,属于等待SCK状态,当SCK发生上升沿,转到11状态.
- serial_state = 11,锁存数据,如果计算到8位数据了,说明传输完成.
- 传输完成或者CSN中途拉高会使得serial_state = 00.
- 传输完成会触发serial_done上升沿,传输开始会触发serial_start上升沿.
- 数据应存在serial_sreg,当serial_done上升沿时,sreg有效,应立即取走.
- sdi_dat_o数据也是在serial_sreg,当然这个可以另外实现.
reg [1:0] serial_state;
reg [3:0] serial_cnt;
reg [7:0] serial_sreg;
reg serial_sdi_ff;
reg serial_start;
reg serial_done;
assign sdi_dat_o = serial_sreg[7];
always @(posedge clk_i or negedge rstn_i) begin
if(!rstn_i) begin
serial_start <= 1'b0;
serial_done <= 1'b0;
serial_state <= 3'b0;
serial_cnt <= 4'b0;
serial_sreg <= 8'b0;
serial_sdi_ff <= 1'b0;
end
else begin
serial_start <= 1'b0;
serial_done <= 1'b0;
case (serial_state)
2'b00:begin
serial_cnt <= 4'b0;
serial_sreg <= 8'h00;
if(!sync_csn) begin
serial_start <= 1'b1;
serial_state[1:0] <= 2'b10;
end
end
2'b10:begin
serial_sdi_ff <= sync_sdi;
if(sync_csn) begin
serial_state[1:0] <= 2'b00;
end
else if (sync_sck) begin
serial_cnt <= serial_cnt + 1'b1;
serial_state[1:0] <= 2'b11;
end
end
2'b11:begin
if(sync_csn) begin
serial_state[1:0] <= 2'b00;
end
else if(sync_sck) begin
serial_sreg <= {serial_sreg[6:0],serial_sdi_ff};
if(serial_cnt[3] == 1'b1) begin
serial_done <= 1'b1;
serial_state[1:0] <= 2'b00;
end
else begin
serial_state[1:0] <= 2'b10;
end
end
end
default begin
serial_state[1:0] <= 2'b00;
end
endcase
end
end
把数值附给LED.
reg [7:0] led_reg;
assign led = ~led_reg[5:0];
always @(posedge clk_i or negedge rstn_i) begin
if(!rstn_i) begin
led_reg[7:0] = 8'b0;
end
else if(serial_done)
begin
led_reg[7:0] = serial_sreg[7:0];
end
end
但是在实测中serial_sreg仍有低概率是错误的,这时候可以再前置一个滤波采集,完整代码如下.
https://gist.github.com/nickfox-taterli/fe3713455b0ba55c73b63d45512f2bd9
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK