Verilog SPI发送实现

系列文章目录

FPGA Verilog 串口发送
使用modelsim进行Verilog仿真(包含testbench编写)
FPGA 串口收发


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

之前折腾Verilog折腾了几次,把串口收发弄出来了,但是总感觉问题还是挺多的,从单片机转过来搞数字逻辑电路搞得我没有逻辑了。看时序总是要不少一个状态,要不就多一个状态,反正就是达不到想要的,挺头秃的。现在决心先专心搞通搞懂一样东西再说其他的,后面毕竟目标是要把数字信号处理在FPGA上实现的。


提示:以下是本篇文章正文内容。

一、SPI

想到搞SPI是为了以后有可能FPGA和ARM这些相互通信传数据,所以专门研究下这个。相比于串口,SPI总线对时钟要求没有那么严格,只关心SCK线的上升沿或者下降沿时的数据线的状态。此外,SPI的时钟可以达到很高的速率,传输的效率挺高。SPI的定义啥的就不多说了,对于初学者,SPI和I2C应该是用的最多的总线了,都可以很多个器件共用同一个时钟和数据线。方便。与SPI相关的数据线有四根,分别是SPI、MOSI、MISO和CS,在半双工的情况下MOSI和MISO可能只有其一。
SPI的模式有4种,模式0、模式1、模式2和模式3.区别就在于起始时刻的时钟线的电平高低和数据动作是在时钟的上升沿和下降沿上。
本文应用于模式0,即时钟起始电平为低电平,数据在SCK下降沿动作。下图是对W25Q128的操作,可以看到在CLK信号实线对应的就是模式0。
在这里插入图片描述
对于时序来说,可以这样理解:

1.在最原始的时刻,CS电平为高,CLK电平为低,MISO和MOSI无所谓;
2. 将CS电平拉低,SCK时钟线拉高,同时把数据放到数据线上,由于本文先做SPI发送,所以MOSI作为数据线;
3. 将SCK时钟线拉低;
4. 重复步骤2和步骤3,直到数据发送完成;
5. 将CS线拉高,标志着一次传输结束。

二、代码构思

1.直接法

本人最开始的思路就是使用直接法,直接按照上面的时序来进行操作,中间出现了一些问题,有点不太好解决。

  1. 首先本人对时序不太熟,写出来的东西要么多一个状态,要么少一个状态,很难搞;
  2. 对于下面的代码,好像被编译器给优化了,或者说根本就不能行
reg SCK;
always @(posedge clk or negedge rst)
begin
SCK<=1;
SCK<=0;
end

编译器就只取了最新的状态即SCK<=0,而对于这个状态的翻转,体现不出来。

2.状态机

思来想去,还是选择状态机(和谐~说机不说吧文明你我他)吧,这个东西思路也容易理清楚,然后程序的健壮性也相当好。
状态机的思路参考了@jgliu的【接口时序】4、SPI总线的原理与Verilog实现,这一篇写的还是相当不错的,有原理又有代码,讲的挺清楚的。这个老哥是对8bit的SPI总线的mode 0进行分析,其中状态机是将每个时钟周期拆成了两个状态,在电平的上升沿和下降沿进行单独罗列。如下图所示。
在这里插入图片描述
考虑到SPI的位宽不总是8bit,有可能是16bit甚至更多,所以本人在@jgliu老哥的基础上对状态机进行拓展。状态机如下思路所示:
首先也就是最初始的时刻,令该状态为stage=0,如果没有tx_en的使能信号,则一直停留在该状态,反之进入到奇偶状态,这个状态可以理解为一旦开始,就能一直顺着状态机往下走,在stage为偶时,将数据放到数据线上,状态+1ÿ

### Verilog 实现 SPI 发送功能 以下是基于提供的参考资料以及标准 SPI 协议的 Verilog 示例代码,该代码实现SPI 主设备的发送功能: ```verilog module spi_master ( input wire clk, // 系统时钟 input wire reset_n, // 复信号,低电平有效 input wire start, // 开始传输标志 input wire [7:0] tx_data,// 待发送数据 output reg ss_n, // 片选信号,低电平时选通 output reg sck, // 串行时钟 output reg mosi, // 主设备输出数据线 output reg done // 完成标志 ); parameter IDLE = 2'b00; parameter TRANSFER = 2'b01; reg [1:0] state; // 状态机变量 reg [3:0] bit_count; // 记录当前发送的比特数 reg [7:0] shift_reg; // 移寄存器 always @(posedge clk or negedge reset_n) begin if (!reset_n) begin state <= IDLE; ss_n <= 1; sck <= 0; mosi <= 0; bit_count <= 0; shift_reg <= 0; done <= 0; end else begin case (state) IDLE : begin if (start) begin state <= TRANSFER; ss_n <= 0; // 激活片选 bit_count <= 7; // 初始化计数值为最高 shift_reg <= tx_data;// 加载待发送数据到移寄存器 done <= 0; end end TRANSFER : begin sck <= ~sck; // 切换时钟相 if (sck == 1) begin // 在上升沿处理数据 mosi <= shift_reg[7]; // 将最高置于 MOSI 上 shift_reg <= {shift_reg[6:0], 1'b0}; // 左移一并填充零 if (bit_count == 0) begin state <= IDLE; ss_n <= 1; // 取消片选 done <= 1; // 设置完成标志 end else begin bit_count <= bit_count - 1; // 减少计数器值 end end end endcase end end endmodule ``` #### 关键点说明 - **状态机设计**:采用简单的两态有限状态机(FSM),`IDLE` 和 `TRANSFER` 表示等待和传输两个阶段[^1]。 - **时钟控制**:通过切换 `sck` 的高低电平来模拟 SPI 时钟信号。在每个时钟周期中更新一次数据[^2]。 - **数据发送机制**:利用移寄存器逐数据从 MSB 至 LSB 输出至 `mosi` 引脚上[^3]。 此模块适用于大多数常见的 SPI 应用场景,在实际应用中可能还需要根据具体芯片的要求调整协议细节。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值