最近研究了一下SPI协议的FPGA实现,发现网上很多大佬分享的方法都是针对某一特定的flash芯片或者某一传感器芯片来设计电路结构的。所以想根据SPI(Serial Peripheral Interface)的基本通讯协议实现一个通用版的SPI Master驱动。SPI在嵌入式领域是一个很成熟且应用非常广泛的通信协议,其通信协议的具体内容在此不再赘述。
SPI协议有四种模式,0模式和3模式应用最为广泛,本文以0模式为基础设计FPGA电路结构。
如上图所示,SPI通信可以理解为主机和从机之间两个双向移位寄存器之间的数据交换,所以每个时钟节拍数据的发送和接收都是同时进行的。
模块结构
一个模块的设计首先要站在用户的角度去考虑,比如其他开发者调用该模块的时候应该如何使用,这样就比较容易确定模块的输入输出信号。本模块设计的特点在于可以由用户自己定义每次数据传输的长度。所以调用该模块接收数据时,要事先知道所对接的从机的数据什么时候发送过来,根据byte_cnt 和bit_cnt来定位接收到的数据。
输入信号:
- clk 该模块的驱动时钟,根据需要提供
- rst_n 模块复位信号,低电平有效
- spi_start 模块唤醒信号,只允许cs为高定平时提供一个时钟宽度的脉冲
- user_data 要发送的数据
- data_width 本次唤醒要发送的数据宽度,代表要发送多少个字节
- miso 主机输入从机输出
输出信号:
- bit_cnt 数据传输位计数器
- byte_cnt 数据传输字节计数器
- cs 片选信号
- mosi 主机输出,从机输入
- rev_data 模块接收到的数据
verilog代码实现
驱动设计代码
module spi_drv (input clk, //50Minput rst_n,input spi_start,input [31:0] data_width,input [7:0] user_data,output reg [2:0] bit_cnt,output reg [31:0] byte_cnt,output reg [7:0] rev_data,output sck, //spi通信同步时钟output cs, //片选信号output mosi, // master output slave inputinput miso // master input slave output
);wire spi_en; //模块使能信号
reg spi_run; //模块状态寄存器
reg spi_clk; //sck时钟
reg [7:0] sen_buf; //输出缓冲寄存器
reg [7:0] rev_buf; //输入缓冲寄存器assign cs = ~spi_run;
assign sck = (spi_run == 1'b1) ? spi_clk : 1'b0;
assign mosi = (spi_run == 1'b1) ? sen_buf[7 - bit_cnt] : 1'b0;
assign spi_en = spi_start & (~spi_run);always @(posedge clk or negedge rst_n) beginif(!rst_n)spi_run <= 1'b0;else if(spi_en == 1'b1)spi_run <= 1'b1;else if((byte_cnt == data_width - 1'b1)&&(bit_cnt == 3'd7)&&(spi_clk == 1'b1))spi_run <= 1'b0;elsespi_run <= spi_run;
endalways @(posedge clk or negedge rst_n) beginif(!rst_n)sen_buf <= 8'd0;else if(spi_en == 1'b1)sen_buf <= user_data;else if((bit_cnt == 8'd7)&&(spi_clk == 1'b1))sen_buf <= user_data;elsesen_buf <= sen_buf;
endalways @(posedge clk or negedge rst_n) beginif(!rst_n)spi_clk <= 1'b0;else if(spi_run == 1'b1)spi_clk <= ~spi_clk;elsespi_clk <= 1'b0;
endalways @(posedge clk or negedge rst_n) beginif(!rst_n)bit_cnt <= 3'd0;else if(spi_run == 1'b1)beginif((bit_cnt == 3'd7)&&(spi_clk == 1'b1))bit_cnt <= 3'd0;else if(spi_clk == 1'b1)bit_cnt <= bit_cnt + 1'b1;elsebit_cnt <= bit_cnt;endelsebit_cnt <= 3'd0;
endalways @(posedge clk or negedge rst_n) beginif(!rst_n)byte_cnt <= 32'd0;else if(spi_run == 1'b1)beginif((byte_cnt == data_width - 1'b1)&&(bit_cnt == 3'd7)&&(spi_clk == 1'b1))byte_cnt <= 32'd0;else if((bit_cnt == 3'd7)&&(spi_clk == 1'b1))byte_cnt <= byte_cnt + 1'b1;elsebyte_cnt <= byte_cnt;endelsebyte_cnt <= 32'd0;
endalways @(posedge spi_clk or negedge rst_n) beginif(!rst_n)rev_buf <= 8'd0;else if(spi_run == 1'b1)rev_buf <= {rev_buf[6:0],miso};elserev_buf <= 8'd0;
endalways @(posedge clk or negedge rst_n) beginif(!rst_n)rev_data <= 8'd0;else if(spi_run == 1'b1)beginif((bit_cnt == 3'd7)&&(spi_clk == 1'b1))rev_data <= rev_buf;elserev_data <= rev_data;endelserev_data <= 8'd0;
endendmodule
仿真激励代码
`timescale 1ns/1nsmodule spi_drv_tb();parameter T = 10;parameter [7:0] CMD = 8'b1010_0000;
parameter [7:0] REG_ADR = 8'b0000_0001;reg [7:0] reg_value0;
reg [7:0] reg_value1;reg clk;
reg rst_n;
reg spi_start;reg [31:0] data_width;
reg [7:0] user_data;wire [2:0] bit_cnt;
wire [31:0] byte_cnt;reg miso;initial beginclk <= 1'b0;rst_n <= 1'b0;spi_start <= 1'b0;data_width <= 32'd4;user_data <= 8'd0;miso <= 1'b0;reg_value0 <= 8'b1010_1010;reg_value1 <= 8'b1010_1010;
endinitial begin#(2*T) rst_n <= 1'b1;
endinitial begin#(5*T) spi_start <= 1'b1; #(2*T) spi_start <= 1'b0;
endalways @(negedge clk)beginif(spi_start)user_data <= CMD;else if((bit_cnt == 3'd7)&&(byte_cnt == 32'd0)) user_data <= REG_ADR;else if((bit_cnt == 3'd7)&&(byte_cnt == 32'd1)) user_data <= 8'd0;else if((bit_cnt == 3'd7)&&(byte_cnt == 32'd2)) user_data <= 8'd0;else if((bit_cnt == 3'd7)&&(byte_cnt == 32'd3)) user_data <= 8'd0;elseuser_data <= user_data;
endalways @(*) beginif(byte_cnt == 32'd2)miso <= reg_value0[7 - bit_cnt];else if(byte_cnt == 32'd3)miso <= reg_value1[7 - bit_cnt];elsemiso <= 8'd0;
endspi_drv spi_drv_m0(.clk(clk),.rst_n(rst_n),.spi_start(spi_start),.data_width(data_width),.user_data(user_data),.bit_cnt(bit_cnt),.byte_cnt(byte_cnt),.miso(miso)
);always #T clk = ~clk;endmodule