SPI协议是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输。一主多从spi设备图如下:
各信号线解释如下:SCK (Serial Clock):时钟信号线,用于同步通讯数据。由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不同,两个设备之间通讯时,通讯速率受限于 低速设备(这里产生的sck时钟频率为12.5MHz)。MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从 这条信号线输出,从机由这条信号线读入主机发送的数据,数据方向由主机到从机。MISO (Master Input,Slave Output):主设备输入/从设备输出引脚。主机从这条信号 线读入数据,从机的数据由这条信号线输出到主机,数据方向由从机到主机。CS (Chip Select):片选信号线,也称为 CS_N,以下用 CS_N 表示。当有多个 SPI 从 设备与 SPI 主机相连时,设备的其它信号线 SCK、MOSI 及 MISO 同时并联到相同的 SPI 总线上,即无论有多少个从设备,都共同使用这 3 条总线;而每个从设备都有独立的这一 条 CS_N 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。
I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI 协议中没有设备地址,它使用 CS_N 信号线来寻址,当主机要选择从设备时,把该从设备 的 CS_N 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中 的从设备进行 SPI 通讯。所以 SPI 通讯以 CS_N 线置低电平为开始信号,以 CS_N 线被拉 高作为结束信号。
SPI 通讯协议的 4 种模式如下,通讯模式时序图,具体见图 46-3。 模式 0:CPOL= 0,CPHA=0。空闲状态时 SCK 串行时钟为低电平;数据采样在 SCK 时钟的奇数边沿,本模式中,奇数边沿为上升沿;数据更新在 SCK 时钟的偶数边沿,本模 式中,偶数边沿为下降沿。 模式 1:CPOL= 0,CPHA=1。空闲状态时 SCK 串行时钟为低电平;数据采样在 SCK 时钟的偶数边沿,本模式中,偶数边沿为下降沿;数据更新在 SCK 时钟的奇数边沿,本模 式中,偶数边沿为上升沿。 模式 2:CPOL= 1,CPHA=0。空闲状态时 SCK 串行时钟为高电平;数据采样在 SCK 时钟的奇数边沿,本模式中,奇数边沿为下降沿;数据更新在 SCK 时钟的偶数边沿,本模 式中,偶数边沿为上升沿。 模式 3:CPOL= 1,CPHA=1。空闲状态时 SCK 串行时钟为高电平;数据采样在 SCK 时钟的偶数边沿,本模式中,偶数边沿为上升沿;数据更新在 SCK 时钟的奇数边沿,本模 式中,偶数边沿为下降沿。
全擦除(BE)指令写入前必须先对 Flash 芯片写入写使能 (WREN)指令,使芯片处于写使能锁存(WEL)状态。此状态下写入全擦除指令才会被 Flash 芯片响应,否则,全擦除指令无效。写使能(Write Enable)指令,简称 WREN,操作指令为 8’b0000_0110(06h)。全擦除(Bulk Erase)操作,简称 BE,操作指令为 8’b1100_0111(C7h),全擦除指令是将 Flash 芯片中的所有存储单元设 置为全 1。在指令写入前后,需要满足一定的建立与保持时间,具体如下:
主要代码如下:
module flash_ctrl
(
input wire sys_clk , //50MHz
input wire sys_rst_n,
input wire key , //按键输入
output reg cs_n, //片选信号
output reg sck , //串行时钟输出
output reg mosi //主输出从输入数据
);
reg key_flag;
//计数器--cnt_1 产生4分频时钟
reg [1:0] cnt_1 ; //计数器位宽
wire add_cnt_1 ; //计数器开始条件
wire end_cnt_1 ; //计数器结束条件
parameter MAX_1 = 'd4 ; //计数的最大值
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
cnt_1 <= 0;
end
else if(add_cnt_1)begin
if(end_cnt_1)
cnt_1 <= 0;
else
cnt_1 <= cnt_1 + 1;
end
else
cnt_1 <= cnt_1;
end
assign add_cnt_1 = 1;
assign end_cnt_1 = (add_cnt_1 && (cnt_1==MAX_1-1));
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
key_flag <= 0;
else if(key)
key_flag <= 1;
else
key_flag <= key_flag;
end
//计数器--cnt_2 计数640ns
reg [5:0] cnt_2 ; //计数器位宽
wire add_cnt_2 ; //计数器开始条件
wire end_cnt_2 ; //计数器结束条件
parameter MAX_2 = 'd32 ; //计数的最大值
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
cnt_2 <= 0;
end
else if(add_cnt_2)begin
if(end_cnt_2)
cnt_2 <= 0;
else
cnt_2 <= cnt_2 + 1;
end
else
cnt_2 <= cnt_2;
end
assign add_cnt_2 = key_flag;
assign end_cnt_2 = (add_cnt_2 && (cnt_2==MAX_2-1));
//计数器--cnt_3 每640ns计数一次
reg [2:0] cnt_3 ; //计数器位宽
wire add_cnt_3 ; //计数器开始条件
wire end_cnt_3 ; //计数器结束条件
parameter MAX_3 = 'd7 ; //计数的最大值
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
cnt_3 <= 0;
end
else if(add_cnt_3)begin
if(end_cnt_3)
cnt_3 <= 0;
else
cnt_3 <= cnt_3 + 1;
end
else
cnt_3 <= cnt_3;
end
assign add_cnt_3 = end_cnt_2;
assign end_cnt_3 = (add_cnt_3 && (cnt_3==MAX_3-1));
//三段式状态机
parameter IDLE = 'd0; //空闲状态
parameter WR_EN = 'd1; //写使能状态
parameter DELAY = 'd2; //延迟状态
parameter BE = 'd3; //擦除状态
reg [2:0] STATE_NOW; //现态
reg [2:0] STATE_NEXT; //次态
//(1)状态切换
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
STATE_NOW <= 'd0;
//STATE_NEXT <= 'd0;
end
else
STATE_NOW <= STATE_NEXT;
end
//(2)状态转移
always@(*)begin
case(STATE_NOW)
'd0:
begin
if(key) //按键有效
STATE_NEXT<='d1; //进入写使能态
else
STATE_NEXT<=STATE_NOW;
end
'd1:
begin
if(cnt_3==3) //计数2*640ns
STATE_NEXT<='d2; //进入延迟状态
else
STATE_NEXT<=STATE_NOW;
end
'd2:
begin
if(cnt_3==5) //计数2*640ns
STATE_NEXT<='d3; //进入擦除状态
else
STATE_NEXT<=STATE_NOW;
end
'd3:
begin
if(cnt_3==0) //计数2*640ns
STATE_NEXT<='d0; //进入空闲状态
else
STATE_NEXT<=STATE_NOW;
end
default:
STATE_NEXT<='d0; //回到空闲态
endcase
end
//(3)输出控制
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
//cs_n <= 'd1; //空闲时片选信号拉高
sck <= 'd0; //空闲时时钟输出低电平
//mosi <= 'd0;
end
else
case(STATE_NOW)
'd0://if(end_cnt_1) //四分频时钟输入一次
begin
//cs_n <= 'd1; //空闲时片选信号拉高
sck <= 'd0; //空闲时时钟输出低电平
//mosi <= 'd0;
end
'd1:if(cnt_3=='d1)
begin
//cs_n <= 'd0;
if(cnt_1=='d0||cnt_1=='d2)
sck <= ~sck; //产生4分频时钟
/* if(cnt_2<'d3)
mosi <= 'd0; //输入擦除指令 8'b0000_0110
else if(cnt_2<'d7)
mosi <= 'd0;
else if(cnt_2<'d11)
mosi <= 'd0;
else if(cnt_2<'d15)
mosi <= 'd0;
else if(cnt_2<'d19)
mosi <= 'd0;
else if(cnt_2<'d23)
mosi <= 'd1;
else if(cnt_2<'d27)
mosi <= 'd1;
else if(cnt_2<'d31)
mosi <= 'd0;
else
mosi <= 'd0; */
end
'd2://if(cnt_3==1)
begin
sck <= 'd0; //空闲时时钟输出低电平
//mosi <= 'd0;
end
'd3:if(cnt_3==5||cnt_3==6)
begin
if(cnt_3==5&&(cnt_1==0||cnt_1==2))
sck <= ~sck; //产生4分频时钟
else if(cnt_3==6&&(cnt_1==0||cnt_1==2))
sck=0;
/* if(cnt_2<'d3)
mosi <= 'd1; //输入写指令 8'b1100_0111
else if(cnt_2<'d7)
mosi <= 'd1;
else if(cnt_2<'d11)
mosi <= 'd0;
else if(cnt_2<'d15)
mosi <= 'd0;
else if(cnt_2<'d19)
mosi <= 'd0;
else if(cnt_2<'d23)
mosi <= 'd1;
else if(cnt_2<'d27)
mosi <= 'd1;
else if(cnt_2<'d31)
mosi <= 'd1;
else
mosi <= 'd0; */
end
endcase
end
//mosi数据输出
always@(*)begin
if(!sys_rst_n)begin
mosi <= 'd0;
end
else
case(STATE_NOW)
'd0://if(end_cnt_1) //四分频时钟输入一次
begin
mosi <= 'd0;
end
'd1:if(cnt_3=='d1)
begin
if(cnt_2<'d3)
mosi <= 'd0; //输入擦除指令 8'b0000_0110
else if(cnt_2<'d7)
mosi <= 'd0;
else if(cnt_2<'d11)
mosi <= 'd0;
else if(cnt_2<'d15)
mosi <= 'd0;
else if(cnt_2<'d19)
mosi <= 'd0;
else if(cnt_2<'d23)
mosi <= 'd1;
else if(cnt_2<'d27)
mosi <= 'd1;
else if(cnt_2<'d31)
mosi <= 'd0;
else
mosi <= 'd0;
end
'd2://if(cnt_3==1)
begin
//sck <= 'd0; //空闲时时钟输出低电平
mosi <= 'd0;
end
'd3:if(cnt_3==5||cnt_3==6)
begin
if(cnt_3==5)
begin
if(cnt_2<'d3)
mosi <= 'd1; //输入写指令 8'b1100_0111
else if(cnt_2<'d9)
mosi <= 'd1;
else if(cnt_2<'d11)
mosi <= 'd0;
else if(cnt_2<'d15)
mosi <= 'd0;
else if(cnt_2<'d21)
mosi <= 'd0;
else if(cnt_2<'d23)
mosi <= 'd1;
else if(cnt_2<'d32)
mosi <= 'd1;
else
mosi <= 'd0;
end
else if(cnt_3==6)
begin
if(cnt_2>'d0)
mosi <= 'd0;
else
mosi <= mosi;
end
end
endcase
end
//片选信号输出
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cs_n <= 'd1; //空闲时片选信号拉高
else if(key)
cs_n <= 'd0;
else if(cnt_3==3)
cs_n <= 'd1;
else if(cnt_3==4)
cs_n <= 'd0;
else if(cnt_3==0&&STATE_NOW=='d0)
cs_n <= 'd1;
end
endmodule
仿真代码如下:
`timescale 1ns/1ns
module flash_ctrl_tb();
reg sys_clk ; //50MHz
reg sys_rst_n;
reg key;
wire cs_n; //片选信号
wire sck ; //串行时钟输出
wire mosi ; //主输出从输入数据
initial begin
sys_clk = 0;
sys_rst_n = 0;
key = 0;
#34;
sys_rst_n = 1;
#20;
key = 1;
#20;
key = 0;
//#278;
//key = 1;
end
always #10 sys_clk = ~sys_clk;
spi_flash flash_ctrl_inst
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key(key),
.cs_n(cs_n),
.sck(sck),
.mosi(mosi)
);
endmodule
按键消抖:
module key_fliter//按键消抖
(
input wire sys_clk,
input wire sys_rst_n,
input wire key,
output reg key_flag
);
//计数器--cnt_1
reg key_1;
reg [31:0] cnt_1 ; //计数器位宽
wire add_cnt_1 ; //计数器开始条件
wire end_cnt_1 ; //计数器结束条件
parameter MAX = 'd100_0000 ; //计数的最大值
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
cnt_1 <= 0;
end
else if(add_cnt_1)begin
if(end_cnt_1)
cnt_1 <= 0;
else
cnt_1 <= cnt_1 + 1;
end
else
cnt_1 <= cnt_1;
end
assign add_cnt_1 = key_1;
assign end_cnt_1 = (add_cnt_1 && (cnt_1==MAX-1));
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
key_1 <= 0;
else if(key==0)
key_1 <= 1;
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
key_flag <= 0;
else if(key_1==1 && key==0 && end_cnt_1)
key_flag <= 1;
else
key_flag <= 0;
end
endmodule
顶层模块:
module spi_flash
(
input wire sys_clk,
input wire sys_rst_n,
input wire key,
output wire cs_n,
output wire sck,
output wire mosi
);
key_fliter key_fliter_inst
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key(key),
.key_flag(key_flag)
);
flash_ctrl flash_ctrl_inst
(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key(key_flag),
.cs_n(cs_n),
.sck(sck),
.mosi(mosi)
);
endmodule