【最通用版FPGA 实现 SPI 驱动】

最近研究了一下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,          //50M
    input              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 input
    input              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) begin
    if(!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;
    else
        spi_run <= spi_run;
end

always @(posedge clk or negedge rst_n) begin
    if(!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;
    else
        sen_buf <= sen_buf;
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        spi_clk <= 1'b0;
    else if(spi_run == 1'b1)
        spi_clk <= ~spi_clk;
    else
        spi_clk <= 1'b0;
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        bit_cnt <= 3'd0;
    else if(spi_run == 1'b1)begin
	if((bit_cnt == 3'd7)&&(spi_clk == 1'b1))
	    bit_cnt <= 3'd0;
        else if(spi_clk == 1'b1)
            bit_cnt <= bit_cnt + 1'b1;
        else
            bit_cnt <= bit_cnt;
    end
    else
        bit_cnt <= 3'd0;
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        byte_cnt <= 32'd0;
    else if(spi_run == 1'b1)begin
	if((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;
        else
            byte_cnt <= byte_cnt;
    end
    else
        byte_cnt <= 32'd0;
end

always @(posedge spi_clk or negedge rst_n) begin
    if(!rst_n)
        rev_buf <= 8'd0;
    else if(spi_run == 1'b1)
        rev_buf <= {rev_buf[6:0],miso};
    else
        rev_buf <= 8'd0;
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rev_data <= 8'd0;
    else if(spi_run == 1'b1)begin
	if((bit_cnt == 3'd7)&&(spi_clk == 1'b1))
        	rev_data <= rev_buf;
	else
		rev_data <= rev_data;
    end
    else
        rev_data <= 8'd0;
end

endmodule

仿真激励代码

`timescale 1ns/1ns

module 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 begin
    clk         <= 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;
end

initial begin
    #(2*T) rst_n <= 1'b1;
end

initial begin
    #(5*T) spi_start <= 1'b1; 
    #(2*T) spi_start <= 1'b0;
end

always @(negedge clk)begin
    if(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;
    else
	user_data <= user_data;
end

always @(*) begin
    if(byte_cnt == 32'd2)
        miso <= reg_value0[7 - bit_cnt];
    else if(byte_cnt == 32'd3)
	miso <= reg_value1[7 - bit_cnt];
    else
	miso <= 8'd0;
end

spi_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

Modelsim仿真结果

在这里插入图片描述

  • 11
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个简单的Verilog代码,用于在FPGA实现SPI驱动OLED屏幕: ```verilog module spi_oled( input wire clk, //时钟信号 input wire rst, //复位信号 output wire cs, //片选信号 output wire dc, //数据/命令选择信号 output wire sda, //数据信号 output wire sck //时钟信号 ); //定义寄存器 reg [7:0] command_reg; reg [7:0] data_reg; //状态机 reg [2:0] state; parameter [2:0] IDLE = 3'b000; parameter [2:0] COMMAND = 3'b001; parameter [2:0] DATA = 3'b010; //时钟分频计数器 reg [7:0] cnt; parameter [7:0] CLK_DIV = 8'hFF; //时钟分频器 parameter [7:0] CMD_DELAY = 8'h0F; //命令延迟 parameter [7:0] DATA_DELAY = 8'h0F; //数据延迟 //初始化状态和计数器 initial begin state = IDLE; cnt = 0; end //状态机逻辑 always @(posedge clk) begin if (rst) begin state <= IDLE; cnt <= 0; end else begin case (state) IDLE: begin cs <= 1'b1; dc <= 1'b0; sda <= 1'b1; sck <= 1'b1; if (cnt == CLK_DIV) begin //进入命令状态 state <= COMMAND; cnt <= 0; end else begin cnt <= cnt + 1; end end COMMAND: begin cs <= 1'b0; dc <= 1'b0; sda <= command_reg[7]; command_reg <= {command_reg[6:0], 1'b0}; sck <= 1'b0; if (cnt == CMD_DELAY) begin //进入数据状态 state <= DATA; cnt <= 0; end else begin cnt <= cnt + 1; end end DATA: begin cs <= 1'b0; dc <= 1'b1; sda <= data_reg[7]; data_reg <= {data_reg[6:0], 1'b0}; sck <= 1'b0; if (cnt == DATA_DELAY) begin //返回空闲状态 state <= IDLE; cnt <= 0; end else begin cnt <= cnt + 1; end end endcase end end endmodule ``` 这个代码中的状态机逻辑控制SPI通信以向OLED屏幕发送命令和数据。您需要根据OLED屏幕的规格和数据手册定制代码,以确保正确配置SPI通信和正确发送命令和数据。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值