三大低速总线之SPI
文章目录
- 三大低速总线之SPI
- 前言
- 一、基本概念
- 1.1 物理层
- 1.2 协议
- 1.3 传输过程
- 二、实战FLASH芯片
- 2.1 SPI-Flash 全擦除实验
- 2.1.1 程序设计
- 2.2 SPI-Flash 扇区擦除实验
- 2.2.1 整体设计
- 2.3 SPI-Flash 页写实验
- 2.3.1 操作时序
- 2.4 SPI_Flash 读数据实验
- 2.4.1 时序
- 总结
前言
SPI(串行外设接口)以其高速度而著称,使其成为快速通信的首选。与 I2C 不同,SPI 使用四线工作:MISO(主输入从输出)、MOSI(主输出从输入)、SCK(串行时钟)和 SS(从选择),允许全双工通信(发送和同时接收)。尽管简单且速度快,但 SPI 比 I2C 需要更多的引脚,这可能是电路设计中需要考虑的一个因素。优点:
高速:SPI通信速度较快,适用于对速度要求较高的应用。
**全双工:**SPI支持全双工通信,可以同时进行数据发送和接收。
**简单:**SPI的通信协议相对简单,适用于快速开发和实现。
SPI的模式选择可以通过查看设备的数据手册或通过软件设置来实现。 SPI通信协议支持多种模式,这些模式由时钟极性(CPOL)和时钟相位(CPHA)的不同组合定义。具体选择哪种模式,取决于设备的具体需求和设计。
缺点:
连线复杂:SPI需要多根线进行连接,可能会增加硬件设计的复杂性。
长距离传输受限:SPI的传输距离受到限制,过长的线路可能导致信号衰减和干扰。
主从模式限制:SPI通常采用主从模式,主设备数量受限,不适用于多主设备场景。
应用案例:SPI 非常适合需要快速可靠的数据传输的情况,例如 TFT 显示器、SD 存储卡和无线通信模块。然而,在具有许多从站的复杂系统中,其有效性会降低。
一、基本概念
1.1 物理层
SPI 通讯协议包含 1 条时钟信号线、2 条数据总线和 1 条片选信号线, 时钟信号线为SCK,2 条数据总线分别为 MOSI(主输出从输入)、MISO(主输入从输出),片选信号线为CS(低有效),
1.2 协议
时钟极性和相位就是什么时候空闲,什么时候采样。
模式 0、模式 1、模式 2 以及模式 3,这 4 种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义。
有4中模式,具体使用哪种模式根据硬件需求来确定。
CPOL 比较好理解,就是表示设备未被选中的空闲状态时,串行时钟 SCK 的电平状态,CPOL = 0,空闲状态时 SCK 为低电平,CPOL = 1,空闲状态时SCK 为高电平;CPHA 的不同参数则规定了数据采样是在 SCK 时钟的奇数边沿还是偶数边沿,CPHA = 0,数据采样是在 SCK 时钟的奇数边沿,CPHA = 1,数据采样是在 SCK 时钟的偶数边沿,这里不使用上升沿或下降沿表示,是因为不同模式下,奇数边沿或偶数边沿
即CPOL = 0表示空闲时,SCK为低电平,CPHA 表示偶数或者奇数边沿采样。
1.3 传输过程
模式 0 为例:
此外,还需要确定一下几点:
- 一次传输的bit
- 先传高位还是低位(每次传输时不受限制的,一般是8/16bit)
- 使用什么模式
二、实战FLASH芯片
2.1 SPI-Flash 全擦除实验
擦除方式有两种:
- 通过 Quartus 软件的“programmer”窗口,将烧录到Flash 的*.jic 文件擦除
- 编写全擦除程序
实验目标:事先向 Flash 芯片中烧录流水灯程序,FPGA 上电执行流水灯程序,下载 Flash 芯片全擦除程序到 FPGA 内部 SRAM 并执行,擦除 Flash 芯片中烧录的流水灯程序,FPGA 重新上电后,无程序执行
此外,需要了解flash芯片需求的时序要求
片选信号自下降沿始到第一个有效数据写入时止,这一段等待时间定义为片选信号有效建立时间 tSLCH,由图 38-15 可知,这一时间段必须大于等于 5ns;片选信号自最后一个有效数据写入时始到片选信号上升沿止,这一段等待时间定义为片选信号有效保持时tCHSH,这一时间段必须大于等于 5ns;片选信号自上一个上升沿始到下一个下降沿止,这一段等待时间定义为片选信号高电平等待时间 tSHSL这一时间段必须大于等于 100ns。
即片选信号和有效数据写入之间的时序关系;和两个片选型号之间的时间间隔
2.1.1 程序设计
整体设计如下:
时序图如下:
设计要点:
- flash芯片时钟频率选择为12.5MHz,即一个完整的bit指令输入为32个时钟周期
- 为了节省计数器资源,统一规划片选和数据之间,片选和片选之间的等待时间也是32个时钟周期,因此只需要一个计数器,计数到32即可
`timescale 1ns/1ns
module flash_be_ctrl
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire key , //按键输入信号
output reg cs_n , //片选信号
output reg sck , //串行时钟
output reg mosi //主输出从输入数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter IDLE = 4'b0001 , //初始状态
WR_EN = 4'b0010 , //写状态
DELAY = 4'b0100 , //等待状态
BE = 4'b1000 ; //全擦除状态
parameter WR_EN_INST = 8'b0000_0110, //写使能指令
BE_INST = 8'b1100_0111; //全擦除指令
//reg define
reg [2:0] cnt_byte; //字节计数器
reg [3:0] state ; //状态机状态
reg [4:0] cnt_clk ; //系统时钟计数器
reg [1:0] cnt_sck ; //串行时钟计数器
reg [2:0] cnt_bit ; //比特计数器
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//cnt_clk:系统时钟计数器,用以记录单个字节
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_clk <= 5'd0;
else if(state != IDLE)
cnt_clk <= cnt_clk + 1'b1;
//cnt_byte:记录输出字节个数和等待时间
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_byte <= 3'd0;
else if((cnt_clk == 5'd31) && (cnt_byte == 3'd6))
cnt_byte <= 3'd0;
else if(cnt_clk == 31)
cnt_byte <= cnt_byte + 1'b1;
//cnt_sck:串行时钟计数器,用以生成串行时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sck <= 2'd0;
else if((state == WR_EN) && (cnt_byte == 1'b1))
cnt_sck <= cnt_sck + 1'b1;
else if((state == BE) && (cnt_byte == 3'd5))
cnt_sck <= cnt_sck + 1'b1;
//cs_n:片选信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cs_n <= 1'b1;
else if(key == 1'b1)
cs_n <= 1'b0;
else if((cnt_byte == 3'd2) && (cnt_clk == 5'd31) && (state == WR_EN))
cs_n <= 1'b1;
else if((cnt_byte == 3'd3) && (cnt_clk == 5'd31) && (state == DELAY))
cs_n <= 1'b0;
else if((cnt_byte == 3'd6) && (cnt_clk == 5'd31) && (state == BE))
cs_n <= 1'b1;
//sck:输出串行时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sck <= 1'b0;
else if(cnt_sck == 2'd0)
sck <= 1'b0;
else if(cnt_sck == 2'd2)
sck <= 1'b1;
//cnt_bit:高低位对调,控制mosi输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_bit <= 3'd0;
else if(cnt_sck == 2'd2)
cnt_bit <= cnt_bit + 1'b1;
//state:两段式状态机第一段,状态跳转
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= IDLE;
else
case(state)
IDLE: if(key == 1'b1)
state <= WR_EN;
WR_EN: if((cnt_byte == 3'd2) && (cnt_clk == 5'd31))
state <= DELAY;
DELAY: if((cnt_byte == 3'd3) && (cnt_clk == 5'd31))
state <= BE;
BE: if((cnt_byte == 3'd6) && (cnt_clk == 5'd31))
state <= IDLE;
default: state <= IDLE;
endcase
//mosi:两段式状态机第二段,逻辑输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
mosi <= 1'b0;
else if((state == WR_EN) && (cnt_byte == 3'd2))
mosi <= 1'b0;
else if((state == BE) && (cnt_byte == 3'd6))
mosi <= 1'b0;
else if((state == WR_EN) && (cnt_byte == 3'd1) && (cnt_sck == 5'd0))
mosi <= WR_EN_INST[7 - cnt_bit]; //写使能指令
else if((state == BE) && (cnt_byte == 3'd5) && (cnt_sck == 5'd0))
mosi <= BE_INST[7 - cnt_bit]; //全擦除指令
endmodule
2.2 SPI-Flash 扇区擦除实验
实验目标:
编写扇区擦除工程,擦除事先烧录到 Flash 中的流水灯程序所占的某个扇区,使流水灯程序不能正常工作。在此次实验工程,我们选择擦除第 0 个扇区,擦除地址为24’h00_04_25。
2.2.1 整体设计
2.3 SPI-Flash 页写实验
使用页写指令,向 Flash 中写入 N 字节数据,N 为整数,且大于 0 小于等于 256。在本本 实 验 中 我 们 向 Flash 芯 片 中 写 入 0-99 , 共 100 字 节 数 据 , 数 据 初 始 地 址 为24’h00_04_25
2.3.1 操作时序
由数据手册中页写操作介绍部分可知,页写指令是根据写入数据将存储单元中的“1”置为“0”,实现数据的写入。在写入页写指令之前,需要先写入写使能(WREN)指令,将芯片设置为写使能锁存(WEL)状态;随后要拉低片选信号,写入页写指令、扇区地址、页地址、字节地址,紧跟地址写入要存储在 Flash 的字节数据,在指令、地址以及数据写入过程中,片选信号始终保持低电平,待指令、地址、数据被芯片锁存后,将片选信号拉高;片选信号拉高后,等待一个完整的页写周期(tPP),才能完成 Flash 芯片的页写操作。读者还要注意的是,Flash 芯片中一页最多可以存储 256 字节数据,这也表示页写操作一次最多向 Flash 芯片写入 256 字节数据。页写指令写入后,随即写入 3 字节数据写入首地址,首地址为扇区地址、页地址、字节地址组成,扇区地址与页地址是确定数据写入 Flash的特定扇区的特定页,字节地址位再该页数据写入的字节首地址。当数据写入的字节首地址为该页的首地址,及字节首地址为 8’b0000_0000,数据写入个数为 0-256 字节,数据可以被正确写入 Flash 芯片;当数据写入的字节首地址不是该页的首地址,及字节首地址不是 8’b0000_0000,数据写入个数为 0-256 字节,若数据写入个数少于字节首地址地址到末地址之间的存储单元个数,数据可以被正确写入 Flash 芯片;若数据写入个数多于字节首地址地址到末地址之间的存储单元个数,等于字节首地址地址到末地址之间的存储单元个数的数据可以被正确写入 Flash 芯片,超出的那部分数据,会以 8’b0000_0000 为字节首地址顺序写入本页,覆盖改地址之前存储的数据。
2.4 SPI_Flash 读数据实验
使用页写或连续写操作向 Flash 芯片写入数据,再使用数据读操作读取之前写入数
据,将读取的数据使用串口传回 PC 机,使用串口助手传回数据并与之前写入数据比较,
判断正误。
*注意:在向 Flash 芯片写入数据之前,先要对芯片执行全擦除操作。
2.4.1 时序
要执行数据读指令,首先拉低片选信号选中 Flash 芯片,随后写入数据读(READ)指令,紧跟指令写入 3 字节的数据读取首地址,指令和地址会在串行时钟上升沿被芯片锁存。随后存储地址对应存储单元中的数据在串行时钟下降沿通过串行数据总线输出。数据读取首地址可以为芯片中的任何一个有效地址,使用数据读(READ)指令可以对芯片内数据连续读取,当首地址数据读取完成,会自动对首地址的下一个地址进行数据读取。若最高位地址内数据读取完成,会自动跳转到芯片首地址继续进行数据读取,只有再次拉高片选信号,才能停止数据读操作,否者会对芯片执行无线循环读操作
总结
其实对于spi协议来说就只需要确定时什么时候采样什么时候空闲,然后空闲的时候变化数据,每次传输的bit数和先传高还是低和了解从设备的硬件特性。
对于flash芯片来说需要注意的时序就是cs和有效数据之间的时序,cs和cs之间的时序。