1,SPI简介
SPI(Serial Peripheral Interface)串行外围设备接口通信协议,是Motorola公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线;只使用4条线来控制数据传输;常用于EEPROM、Flash、RTC、ADC、DSP以及数字信号解码器上。
SPI通信协议的优点是支持全双工通信,通信方式简单,且相对数据传输速率较快;缺点是没有指定的流控制,没有应答机制确认数据是否接收,在数据可靠性上存在一定缺陷。SPI通信协议一般分为物理层、协议层两部分。
2,物理层
SPI的物理层即通信设备的连接方式和引脚的功能描述。SPI通信设备的通信模式是主从通信模式,通信双方有主从之分,根据从机设备的数量,SPI通信设备之间的连接方式可分为一主一从、一主多从,如下图所示。
SPI通信协议包含一条时钟信号线、2条数据总线、一条片选信号线,具体介绍如下:
(1)SCK (Serial Clock):时钟信号线,用于同步通信数据。由通信主机产生,决定了通信的速率,不同设备支持的最高时钟频率不同,两个设备之间通信时,通信速率受限于低速设备。
(2)MOSI(Master Output,Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,数据方向由主机到从机。
(3)MISO(Master Input,Slave Output)主设备输入/从设备输出引脚,主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,数据方向由从机到主机。
(4)/CS(Chip Select):片选信号线,也称CS_N,当有多个从设备时,所有从设备共同使用SCK,MOSI,MISO三条总线;但是对于每一个从设备CS_N都是独立的,即每一个从设备有一个片选信号线。所以SPI通信以CS_N线置低电平为开始信号,以CS_N线被拉高作为结束信号。
3,协议层
SPI通信协议有四种通信模式,模式0、1、2、3;这四种模式分别由时钟机型(CPOL, Clock Polarity)和时钟相位(CPHA, Clock Phase)来定义。其中CPOL参数规定了空闲状态(CS_N为高电平,设备未被选中)时SCK时钟信号的电平状态,CPHA规定了数据采样是在SCK时钟的奇数边沿还是偶数边沿。
SPI的通信协议的四种模式如下:
CPOL | CPHA | 空闲时SCK状态 | 数据采样 | 数据更新 | |
---|---|---|---|---|---|
模式0 | CPOL=0 | CPHA=0 | 低电平 | 奇数边沿(上升沿) | 偶数边沿(下降沿) |
模式1 | CPOL=0 | CPHA=1 | 低电平 | 偶数边沿(下降沿) | 奇数边沿(上升沿) |
模式2 | CPOL=1 | CPHA=0 | 高电平 | 奇数边沿(下降沿) | 偶数边沿(上升沿) |
模式3 | CPOL=1 | CPHA=1 | 高电平 | 偶数边沿(上升沿) | 奇数边沿(下降沿) |
SPI通讯模式时序图
CPHA = 0 时的SPI通信模式
CPHA = 0 时的SPI通信模式
4,SPI通信过程
在四种模式中0和3较常用,下面内容以模式0为例讲解SPI的基本通讯过程;SPI模式0的通讯时序图如下(主机视角的通讯时序):
在上图中SCK,MOSI,CS_N信号均由主机控制产生,SCK是时钟信号,用来同步数据;MOSI是主机输出从机输入信号,主机通过此信号线传输数据给从机;CS_N为片选信号,用以选定从机设备,低电平有效;而MISO信号由从机产生,主机通过该信号线读取从机数据。MOSI,MISO的信号只在CS_N为低电平时有效,在SCK的每个时钟周期MOSI,MISO传输一位数据。
5,通信的起始和停止信号
CS_N信号线由高变低,是SPI的起始信号。CS_N是每个从机各自独占的信号线,当从机在自己的CS_N线检测到起始信号后,开始准备与主机通信。CS_N信号由低变高,是SPI通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。
6,数据有效性
MOSI和MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时一般采用MSB先行的模式。SPI每次数据传输可以以8位或16位为单位,每次传输的单位数不受限制。
7,SPI-Flash全擦除实验
Flash芯片是一种非易失性存储芯片,即掉电后数据不会丢失。对flash的全擦除就是将Flash所有的存储空间进行擦除操作,使各存储空间内存储数据恢复到初始值。FPGA实现Flash的全擦除有两种方式;一是利用FPGA编译软件,通过Quartus软件的“programmer”窗口,将烧录到Flash的xxx.jic文件擦除;而是通过编写全擦除程序。
操作时序
全擦除(Bulk Erase)操作,简称BE,操作指令为8’b1100_0111(C7h),
全擦除指令指将所有存储单元设置为1,在Flash芯片写入全擦除指令之前,需要先写入使能(WREN)指令,将芯片设置为写使能锁存(WEL)状态,随后要拉低片选信号,写入全擦除指令,在指令写入过程中,片选信号始终保持低电平,待指令被芯片锁存后,将片选信号拉高;全擦除指令被锁存并执行后,需要等待一个完整的全擦除周期(tBE),才能完成全擦除操作。
写使能(Write Enable)指令, 简称 WREN,操作指令为 8’b0000_0110(06h),具体见图
写使能指令可将 Flash 芯片设置为写使能锁存(WEL)状态;在每一次页写操作(PP)、扇区擦除(SE)、全擦除(BE)和写状态寄存器(WRSR)操作之前,都需要先进行写使能指令写入操作。操作时序为先拉低片选信号,写入写使能指令,在指令写入过程中,片选信号始终保持低电平,指令写入完成后,将片选信号拉高。时序如下:
串行输入时序图
如图所示,相关操作指令在写入芯片之前需要先拉低片选信号,在片选信号保持低电平时将指令写入数据输入端口,指令写入完毕,拉高片选信号,数据输出端口在指令写入过程中始终保持高阻态。片选信号自下降沿始到第一个有效数据写入时止,这一段等待时间定义为片选信号有效建立时间 tSLCH,这一时间段必须大于等于 5ns;片选信号自最后一个有效数据写入时始到片选信号上升沿止,这一段等待时间定义为片选信号有效保持时间 tCHSH,这一时间段必须大于等于5ns;片选信号自上一个上升沿始到下一个下降沿止,这一段等待时间定义为片选信号高电平等待时间 tSHSL,这一时间段必须大于等于 100ns。所以完整的全擦除操作时序图如下:
8,实现思路
(1)Flash全擦除波形图
(2)整体设计思路
由设计的要求,我们可以使用状态机来实现,定义状态机的各状态分别为初始状态(IDLE)、写使能状态(WR_EN)、两指令间等待状态(DELAY)、全擦除状态(BE)。状态机的跳转流程如下:系统上电后,状态机状态变量 state 一直处于初始状态(IDLE);当传入的全擦除触发信号 key 有效时,表示实验工程开始执行对 Flash 芯片的全擦除操作, 状态机跳转到写使能状态(WR_EN),同时片选信号拉低,选中要进行全擦除操作的 Flash 芯片;状态跳转到写使能状态且片选信号拉低后,要进行 tSLCH≥5ns 的等待时间,等待时间过后对主输出从输入信号写入写使能指令,指令写入完成后需要进行tCHSH≥5ns的等待时间,等待时间过后拉高片选信号,取消对 Flash 芯片的选择,同时状态机跳转到两指令间等待状态(DELAY);在此状态等待时间 tSHSL≥ 100ns 后,状态机跳转到全擦除状态(BE),同时片选信号拉低,选中已写入写使能指令的 Flash 芯片;状态机跳转到全擦除状态且片选信号拉低后,要进行 tSLCH≥5ns 的等待时间,等待时间过后对主输出从输入信号写入全擦除指令,指令写入完成后需要进行 tCHSH≥5ns 的等待时间,等待时间过后拉高片选信号,取消对 Flash 芯片的选择,同时状态机跳回初始状(IDLE),一次完整的全擦除操作完成
在此实验中输出的Flash芯片的时钟频率为12.5Mhz,由于指令为串行传输,每个时钟周期只能写入1bit数据,要写入一个完整的单字节指令需要8各完整的SCK时钟周期即32个完整的系统时钟,系统时钟为50Mhz,完整指令的写入需要640ns,所以将各等待时间统一设置为640ns
(3)各信号作用
key: 按键输入信号,当检测到按键按下开始擦除操作。
cnt_clk: 640ns计数,初值为0,在0-31计数范围内循环计数,在状态机处于初始状态时,始终保持为0;在状态机在状态机处于初始状态之外的其他状态时,每个系统时钟周期自加 1,计到最大值清 0,重新计数。
cnt_byte: 此计数器对cnt_clk的计数周期进行计数,当状态机处于初始状态时(IDLE)时,计数器的值始终保持0;在其他状态,当cnt_clk完成一个完整的循环计数时,计数器的值加1,其他时刻保持当前值不变。
state: 状态寄存器
cnt_sck: 四分频计数器,在0-3之间循环计数,计数到2时对sck信号取反。
cnt_bit:左右时实现指令或数据的高低位对调,计数器初值为 0,在 0-7范围内循环计数,计数时钟为串行时钟 sck,每个时钟周期自加 1,其他时刻恒为 0。
(4)关键部分代码
module flash_be_ctrl
(
input sys_clk ,
input sys_rst_n,
input key ,
output reg sck ,
output reg cs_n ,
output reg mosi
);
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 [2:0] cnt_byte ; //字节计数器
reg [3:0] state ; //状态机状态
reg [4:0] cnt_clk ; //系统始终计数器
reg [1:0] cnt_sck ; //串行时钟计数器
reg [2:0] cnt_bit ; //比特计数器
//系统时钟计数器用来记录单个字节
always @(posedge sys_clk,negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_clk <= 5'd0;
else if(state != IDLE)
cnt_clk <= cnt_clk + 1'b1;
else
cnt_clk <= cnt_clk;
end
//记录输出字节个数和等待时间
always @(posedge sys_clk,negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_byte <= 3'd0;
else if((cnt_clk == 5'd31) && (cnt_byte == 3'd6))
cnt_byte <= 3'd0;
else if(cnt_clk == 5'd31)
cnt_byte <= cnt_byte + 1'b1;
else
cnt_byte <= cnt_byte;
end
//串行时钟计数器,用来生成串行时钟
always @(posedge sys_clk,negedge sys_rst_n) begin
if(!sys_rst_n)
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;
else
cnt_sck <= cnt_sck;
end
//片选信号
always @(posedge sys_clk,negedge sys_rst_n) begin
if(!sys_rst_n)
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;
else
cs_n <= cs_n;
end
//输出串行时钟
always @(posedge sys_clk,negedge sys_rst_n) begin
if(!sys_rst_n)
sck = 1'b0;
else if(cnt_sck == 2'd0)
sck <= 1'b0;
else if(cnt_sck == 2'd2)
sck <= 1'b1;
else
sck <= sck;
end
//高低位对调,控制mosi输出
always @(posedge sys_clk,negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_bit <= 3'd0;
else if(cnt_sck == 2'd2)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
end
//状态跳转
always @(posedge sys_clk,negedge sys_rst_n) begin
if(!sys_rst_n)
state <= IDLE;
else begin
case(state)
IDLE: begin
if(key == 1'b1)
state <= WR_EN;
end
WR_EN: begin
if((cnt_byte == 3'd2) && (cnt_clk == 5'd31))
state <= DELAY;
end
DELAY: begin
if((cnt_byte == 3'd3) && (cnt_clk == 5'd31))
state <= BE;
end
BE: begin
if((cnt_byte == 3'd6) && (cnt_clk == 5'd31))
state <= IDLE;
end
default: state <= IDLE;
endcase
end
end
//逻辑输出
always @(posedge sys_clk,negedge sys_rst_n) begin
if(!sys_rst_n)
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];
else
mosi <= 1'b0;
end
endmodule
9,参考内容:
野火, FPGA Verilog 开发实战指南