一、SPI协议简介
1.SPI简介
1.SPI(Serial Peripheral Interface)是一种高速、全双工、同步串行通信总线,由摩托摩拉公司推出。
2.优缺点:全双工通信,通信方式简单,相对数据传输速度较快;SPI没有应答机制确认数据是否接收,数据可靠性上有一定缺陷。(相对IIC协议)。
2.SPI原理
2.1 主要端口:
SCK :时钟信号线,用于同步通讯数据。
MOSI:主设备输出/从设备输入引脚。
MISO:主设备输入/从设备输出引脚。
CS :片选信号。也称CS_N(片选信号低电平有效)。片选信号线独立,有多少个从机就有多少个CS。
2.2 通信模式(四种)
CPOL(时钟极性) : 片选信号处于空闲状态时,时钟的电平。
CPHA(时钟相位):采样的时钟边沿(奇数沿/偶数沿);CPHA=0,采样的时钟边沿为奇数沿;CPHA=1,采样的时钟边沿为偶数沿;采样沿保持数据稳定,另一沿改变数据。
2.3数据传输时序
二、Flash简介(M25P16)
1.基本属性
1.1 储存属性:16Mbit,一共32个扇区,每个扇区256页,每页256字节,Flash断电后数据不会擦除
1.2 SPI模式支持:M25P16支持SPI模式0和模式3
1.3 引脚
2.关键时间参数
3.指令
3.1指令集
指令传输时是MSB传输
3.2 使用到的各个指令详解
3.2.1写使能指令(WREN)
WREN( Write Enable)指令通过驱动芯片选择(S)低,发送指令代码,然后驱动芯片选择(S)高。必须在每个页程序(PP)、扇区擦除(SE)、批量擦除(BE)和写入状态寄存器(WRSR)指令之前,输入WREN指令以设置写入启用锁存器(WEL)位。
3.2.2 扇区擦除指令(SE)
扇区擦除(SE)指令将所选扇区内的所有位设置为1 (FFh)。扇区内的任何地址都是扇区擦除(SE)指令的有效地址。指令序列如下所示。芯片选择(S)必须在最后一个地址字节的第八位被锁存后驱动为高电平,否则扇区擦除(SE)指令不执行。一旦芯片选择(S)驱动高,自定时扇区擦除周期(其持续时间为tse)被启动
地址例子:
当扇区擦除周期正在进行时,可以读取状态寄存器以检查正在进行的写入(WIP)位的值。在自定时扇区擦除周期中,WIP位为1,擦除完成后为0。在周期完成之前的某个未指定时间,写使能锁存(WEL)位被重置。一个扇区擦除(SE)指令,应用于由块保护(BP2, BP1, BPO)位保护的页(见表2和表3)不执行。
3.2.3 全擦除指令 BE
全擦除指令将flash内的所有位都置1
3.2.4 读状态寄存器 RDSR
WIP位:Write In Progress (WIP)位表示内存是否正忙于写状态寄存器、程序或擦除周期。当设置为1时,这样的周期正在进行,当重置为0时,没有这样的周期在进行。
WEL位:写使能锁存(WEL)位表示内部写使能锁存的状态。当设置为1时,内部写使能锁存设置,当设置为0时,内部写使能锁存复位,不接受写状态寄存器,程序或擦除指令。
BP2、BP1、BPO位:块保护(BP2、BP1、BPO)位是非易失性的。它们定义了被软件保护以防止程序和擦除指令的区域的大小。这些位是用WRSR指令写入的。当块保护位(BP2、BP1、BPO)中的一个或两个位设置为1时,相关的内存区域(如下图中定义的)将受到保护,不受页程序(PP)和扇区擦除(SE)指令的影响。未设置“硬件保护”模式时,可以写入块保护位(BP2、BP1、BO)。当且仅当块保护(BP2, BP1, BPO)位都为0时,执行Bulk Erase (BE)指令。
SRWD位:状态寄存器写禁止(SRWD)位与写保护(W)信号一起工作。状态寄存器写禁止(SRWD)位和写保护(W)信号允许设备进入硬件保护模式(当状态寄存器写禁止(SRWD)位被置为1,写保护(W)被置为Low)。在这种模式下,状态寄存器(SRWD, BP2, BP1, BPO)的非易失位变成只读位,写状态寄存器(WRSR)指令不再被执行。
3.2.5 页编程指令 PP
如果数据超过256字节,那么前面的数据会被覆盖,只有最后的256字节数据会被保留。如果小于256字节,则正常写入,且不会对本页其他字节数据造成影响
如果页编程的字节地址不是全零的话,就会在本页内循环写入数据(到了本页最后一字节,下一字节会回到本页起始地址编程,即字节地址全零的地方开始
3.2.6 读取指令 READ
可以任意地址读,读完该地址后,地址自加1;
写指令和擦除指令执行时,读取指令不会执行,且不会影响正在执行的指令。
3.2.7 读ID RDID
S拉低 + 读ID指令 + 3字节ID(设备ID、存储类型、存储容量)+ S拉高
读取识别(RDID)指令在数据输出期间的任何时间通过驱动芯片选择(S)高来终止。
当擦除或程序周期正在进行时,任何读标识(RDID)指令都不会被解码,并且对正在进行的周期没有影响。
四、工程实践
1.系统框图
2.程序设计
2.1SPI接口设计
/*SPI主机接口模块,采用模式3*/
module spi_master(
input clk ,
input rst_n ,
//user
input [7:0] din ,
input req ,//主机传输数据请求 加uart 改为din_vld
output [7:0] dout ,//读出的数据
output done ,
//spi_slave
output sclk ,//串行时钟信号
output cs_n ,//片选信号
output mosi ,//主机输出从机输入信号
input miso //主机输入从机输入信号
);
//参数定义
parameter SCLK_PERIOD = 16, //16分频 3.125M 320ns
SCLK_LOW = 4 ,
SCLK_HIGH = 12,
BIT_NUM = 8 ; //bit数参数
parameter IDLE = 4'b0001, //空闲状态
READY = 4'b0010, //准备传输状态
DATA = 4'b0100, //传输过程状态
DONE = 4'b1000; //传输完成状态
//中间信号
reg [3:0] state_c ; //状态机
reg [3:0] state_n ;
reg [3:0] cnt_sclk ;//串行时钟计数器
wire add_cnt_sclk;
wire end_cnt_sclk;
reg [2:0] cnt_bit ; //比特计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg spi_sclk ;
reg spi_mosi ;
reg [7:0] rd_data ; //读取的数据
wire idle2ready ; //状态机跳转条件
wire ready2data ;
wire data2done ;
wire done2idle ;
//状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
always @(*)begin
case (state_c)
IDLE :begin
if(idle2ready)begin
state_n = READY;
end
else
state_n = state_c;
end
READY :begin
if(ready2data )begin
state_n = DATA ;
end
else
state_n = state_c;
end
DATA :begin
if(data2done )begin
state_n = DONE;
end
else
state_n = state_c;
end
DONE :begin
if(done2idle)begin
state_n = IDLE;
end
else
state_n = state_c;
end
default: state_n = IDLE;
endcase
end
assign idle2ready = (state_c == IDLE ) && (req );
assign ready2data = (state_c == READY) && (1'b1 );
assign data2done = (state_c == DATA ) && (end_cnt_bit);
assign done2idle = (state_c == DONE ) && (1'b1 );
//cnt_sclk
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_sclk <= 'd0;
end
else if(add_cnt_sclk)begin
if (end_cnt_sclk) begin
cnt_sclk <= 'd0;
end
else begin
cnt_sclk <= cnt_sclk + 1'b1;
end
end
end
assign add_cnt_sclk = (req);
assign end_cnt_sclk = add_cnt_sclk && cnt_sclk == (SCLK_PERIOD - 1);
//比特计数器cnt_bit
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if (end_cnt_bit) begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'b1;
end
end
end
assign add_cnt_bit = (state_c == DATA ) && end_cnt_sclk;
assign end_cnt_bit = add_cnt_bit && cnt_bit == BIT_NUM - 1;
//spi_sclk 生成一个时钟,低电平开始,可画图
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
spi_sclk <= 1'b1;
end
else if(cnt_sclk == SCLK_LOW - 1)begin
spi_sclk <= 1'b0;
end
else if(cnt_sclk == SCLK_HIGH - 1)begin
spi_sclk <= 1'b1;
end
end
//spi_csn
//spi_mosi 主机输出 模式3 前沿(下降沿)输出 高位传输
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
spi_mosi <= 1'b0;
end
else if(cnt_sclk == SCLK_LOW - 1)begin
spi_mosi <= din[7-cnt_bit];
end
end
// rd_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_data <= 'd0;
end
else if(cnt_sclk == SCLK_HIGH - 1)begin
rd_data[7-cnt_bit] <= miso;
end
end
//输出描述
assign sclk = spi_sclk ;
assign cs_n = ~req ;
assign mosi = spi_mosi ;
assign dout = rd_data ;
assign done = end_cnt_bit;
endmodule
2.2 SPI读写Flash控制模块设计
2.2.1 控制模块状态跳转
2.2.2 控制模块代码
module spi_contrl #(parameter READ_MAX = 5,WRITE_MAX=5,WAIT_MAX=150000000,DONE_MAX=250000)(
input clk ,
input rst_n ,
input wr_en ,
input rd_en ,
input done ,
input busy ,
input [7:0] rx_data ,//页写输入数据
input rx_data_vld ,//输入数据寄存信号
input [7:0] din_data ,//读取数据
output [7:0] tx_data ,
output tx_data_vld ,//读数据发送到tx模块
output reg [7:0] dout_data ,//输出给接口模块的数据
output reg req
);
/**************************************************************
信号定义
**************************************************************/
reg [30:0] cnt_delay ;
reg delay_flag ;
wire add_cnt_delay ;
wire end_cnt_delay ;
reg [9:0] cnt_byte ;
reg [9:0] byte_max ;
wire add_cnt_byte ;
wire end_cnt_byte ;
wire idle2wren1 ;
wire idle2read ;
wire wren12se ;
wire se2wait ;
wire wait2wren2 ;
wire wren22pp ;
wire pp2done ;
wire done2idle ;
wire read2done ;
reg [2:0] state ;
wire wfifo_rd ;
wire wfifo_wr ;
wire wfifo_empty ;
wire wfifo_full ;
wire [7:0] wfifo_qout ;
wire [5:0] wfifo_usedw ;
wire rfifo_rd ;
wire rfifo_wr ;
wire rfifo_empty ;
wire rfifo_full ;
wire [7:0] rfifo_qout ;
wire [5:0] rfifo_usedw ;
reg [7:0] dout_data_r ;
reg dout_data_vld_r ;
reg [7:0] din_data_r ;
reg [7:0] tx_data_r ;
parameter IDLE = 0 ,
WREN_1 = 1 ,
SE = 2 ,
WAIT = 3 ,
PP = 5 ,
WREN_2 = 4 ,
DONE = 7 ,
READ = 6 ;
/**************************************************************
命令码定义
**************************************************************/
parameter READ_ADDR_SECTOR = 8'b0000_0000,//地址
READ_ADDR_PAGE = 8'b0000_0000,
READ_ADDR_WORD = 8'b0000_0000,
WRITE_ADDR_SECTOR = 8'b0000_0000,
WRITE_ADDR_PAGE = 8'b0000_0000,
WRITE_ADDR_WORD = 8'b0000_0000;
parameter READ_CMD = 8'b0000_0011,//指令
READ_ID_CMD = 8'b1001_1111,
WREN_CMD = 8'b0000_0110,
SE_CMD = 8'b1101_1000,
PP_CMD = 8'b0000_0010;
/**************************************************************
状态机
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else case(state)
IDLE : if(idle2wren1)
state <=WREN_1 ;
else if(idle2read)
state <=READ ;
WREN_1 : if(wren12se )
state <=SE ;
SE : if(se2wait )
state <=WAIT ;
WAIT : if(wait2wren2 )
state <=WREN_2 ;
WREN_2 : if(wren22pp )
state <=PP ;
PP : if(pp2done )
state <=DONE ;
DONE : if(done2idle )
state <=IDLE ;
READ : if(read2done )
state <=DONE ;
default : state <=IDLE ;
endcase
assign idle2wren1 = state ==IDLE && wr_en ;
assign idle2read = state ==IDLE && rd_en ;
assign wren12se = state ==WREN_1 && end_cnt_delay ;
assign se2wait = state ==SE && end_cnt_delay ;
assign wait2wren2 = state ==WAIT && end_cnt_delay ;
assign wren22pp = state ==WREN_2 && end_cnt_delay ;
assign pp2done = state ==PP && end_cnt_byte ;
assign done2idle = state ==DONE && end_cnt_delay ;
assign read2done = state ==READ && end_cnt_byte ;
/**************************************************************
延迟计数器
**************************************************************/
//延迟计数器开启信号在数据传输完成后打开
always@(posedge clk or negedge rst_n)
if(!rst_n)
delay_flag<='d0;
else if (wren12se || wren22pp || se2wait || wait2wren2 || pp2done || done2idle)
delay_flag<='d0;
else if ((state == WREN_1 || state == WREN_2 || state == SE ) && end_cnt_byte)
delay_flag<='d1;
else if (state == WAIT || state == DONE)
delay_flag<='d1;
//计数器 wait-3s done-5ms other-120ns
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_delay <= 'd0;
else if(add_cnt_delay) begin
if(end_cnt_delay)
cnt_delay <= 'd0;
else
cnt_delay <= cnt_delay + 1'b1;
end
assign add_cnt_delay = delay_flag ;
assign end_cnt_delay = add_cnt_delay && cnt_delay == ((state==WAIT)?(WAIT_MAX-1):(state==DONE?(DONE_MAX-1):5)) ;
/**************************************************************
字节计数器
**************************************************************/
//字节计数最大值控制
always@(posedge clk or negedge rst_n)
if(!rst_n)
byte_max<=0;
else if (state == READ)
byte_max<=READ_MAX-1;
else if (state == SE)
byte_max<=3;
else if (state == WREN_1||state == WREN_2)
byte_max<=0;
else if (state == PP)
byte_max<=WRITE_MAX-1;
//字节计数器 //接口模块传输一字节后返回done信号开启
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_byte <= 'd0;
else if (wren12se || wren22pp || se2wait || wait2wren2 || pp2done || done2idle)
cnt_byte <= 'd0;
else if(add_cnt_byte) begin
if(end_cnt_byte)
cnt_byte <= 'd0;
else
cnt_byte <= cnt_byte + 1'b1;
end
assign add_cnt_byte =( (state == READ) || (state == SE) ||(state == PP)||(state == WREN_1)||(state == WREN_2) )&& done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == byte_max ;
/**************************************************************
数据控制逻辑
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
dout_data<='d0;
else if (state == READ) begin
case(cnt_byte)
0 : dout_data = READ_CMD ;
1 : dout_data = READ_ADDR_SECTOR ;
2 : dout_data = READ_ADDR_PAGE ;
3 : dout_data = READ_ADDR_WORD ;
READ_MAX-1 : tx_data_r = din_data ;
default : tx_data_r = din_data ;
endcase
end
else if (state == SE) begin
case(cnt_byte)
0 : dout_data = SE_CMD ;
1 : dout_data = WRITE_ADDR_SECTOR;
2 : dout_data = WRITE_ADDR_PAGE ;
3 : dout_data = WRITE_ADDR_WORD ;
default : dout_data = 8'h0 ;
endcase
end
else if (state == PP) begin
case(cnt_byte)
0 : dout_data = PP_CMD ;
1 : dout_data = WRITE_ADDR_SECTOR;
2 : dout_data = WRITE_ADDR_PAGE ;
3 : dout_data = WRITE_ADDR_WORD ;
WRITE_MAX-1 : dout_data = wfifo_qout ;
default : dout_data = wfifo_qout ;
endcase
end
else if (state == WREN_1||state == WREN_2) begin
case(cnt_byte)
0 : dout_data = WREN_CMD ;
default : dout_data = 8'h0 ;
endcase
end
/**************************************************************
req控制逻辑
**************************************************************/
always @(posedge clk or negedge rst_n)
if(!rst_n)
req <= 1'd0;
else if(end_cnt_byte || delay_flag)
req <= 1'b0;
else if((state == READ) || (state == SE) || (state == PP) || (state == WREN_1) || (state == WREN_2))
req <= 1'b1;
/**************************************************************
fifo控制逻辑
**************************************************************/
fifo_8x256 fifo_wr (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( rx_data ),
.rdreq ( wfifo_rd ),
.wrreq ( wfifo_wr ),
.empty ( wfifo_empty ),
.full ( wfifo_full ),
.q ( wfifo_qout ),
.usedw ( wfifo_usedw )
);
assign wfifo_rd = (state == PP) && done && (cnt_byte > 3) ;
assign wfifo_wr = ~wfifo_full && rx_data_vld ;
fifo_8x256 fifo_rd (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( din_data_r ),
.rdreq ( rfifo_rd ),
.wrreq ( rfifo_wr ),
.empty ( rfifo_empty ),
.full ( rfifo_full ),
.q ( rfifo_qout ),
.usedw ( rfifo_usedw )
);
assign rfifo_wr = ~rfifo_full && (state == READ) && (cnt_byte > 3) && done ;
assign rfifo_rd = (~rfifo_empty && busy) || read2done ;
/**************************************************************
返回数据寄存
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
din_data_r<='d0;
else
din_data_r<=din_data;
/**************************************************************
读取数据寄存
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
dout_data_r <=8'b0 ;
dout_data_vld_r <=1'b0 ;
end
else begin
dout_data_r <=rfifo_qout ;
dout_data_vld_r <=rfifo_rd ;
end
//输出端口
assign tx_data = dout_data_r ;
assign tx_data_vld = dout_data_vld_r;
endmodule
2.3 顶层模块设计
module top(
input clk ,
input rst_n ,
input rx ,
output tx ,
input [1:0] key_in ,
output sclk ,
output cs_n ,
output mosi ,
input miso
);
wire [1:0] key_out ;
wire [7:0] rx_dout ;
wire rx_dout_vld;
wire [7:0] tx_din ;
wire tx_din_vld ;
wire [7:0] dout_data ;
wire req ;
wire [7:0] din_data ;
wire done ;
wire tx_done ;
spi_contrl #(.READ_MAX (20),.WRITE_MAX(20),.WAIT_MAX(150000000),.DONE_MAX(250000)) u_spi_contrl(
.clk (clk ),
.rst_n (rst_n ),
.wr_en (key_out[0] ),
.rd_en (key_out[1] ),
.done (done ),
.busy (tx_done ),
.rx_data (rx_dout ),
.rx_data_vld (rx_dout_vld ),
.din_data (din_data ),
.tx_data (tx_din ),
.tx_data_vld (tx_din_vld ),
.dout_data (dout_data ),
.req (req )
);
spi_master u_spi_master(
.clk (clk ),
.rst_n (rst_n ),
.din (dout_data ),
.req (req ),
.dout (din_data ),
.done (done ),
.sclk (sclk ),
.cs_n (cs_n ),
.mosi (mosi ),
.miso (miso )
);
key_filter #(.KEY_W (2 ) ,.CNT_MAX_20MS(5 ) ) u_key_filter(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key_in ),
.key_out (key_out )
);
uart_rx u_uart_rx(
.clk (clk ),
.rst_n (rst_n ),
.rx_din (rx ),
.rx_dout (rx_dout ),
.rx_dout_vld (rx_dout_vld )
);
uart_tx u_uart_tx(
.clk (clk ),
.rst_n (rst_n ),
.tx_din (tx_din ),
.tx_din_vld (tx_din_vld ),
.tx_dout (tx ),
.tx_done (tx_done )
);
endmodule
2.4 其他模块设计
UART模块、按键消抖模块;
3.仿真测试
3.1 仿真测试代码
`timescale 1ns/1ps
module top_tb();
parameter CLK_CYCLE = 20;
defparam u_top.u_spi_contrl.READ_MAX = 4 + 5; //1字节指令,3字节ID,加上传输的数据量(此处设为5)
defparam u_top.u_spi_contrl.WRITE_MAX= 4 + 5;
defparam u_top.u_spi_contrl.WAIT_MAX=1500;
defparam u_top.u_spi_contrl.DONE_MAX=250;
reg clk,rst_n;
reg rx;
reg [1:0] key_in;
always #(CLK_CYCLE/2) clk = ~clk;
wire mosi;
wire miso;
top u_top(
.clk (clk ),
.rst_n (rst_n ),
.rx (rx ),
.tx (tx ),
.key_in (key_in ),
.sclk (sclk ),
.cs_n (cs_n ),
.mosi (mosi ),
.miso (miso )
);
m25p16 m25p16(sclk,mosi,cs_n,w,hold,miso);
integer i;
task my_uart_rx;
input [7:0]data_in;
begin
rx=0;
#(CLK_CYCLE*434);
for(i=0;i<=7;i=i+1)begin
rx = data_in[i];
#(CLK_CYCLE*434);
end
rx = 1;
#(CLK_CYCLE*434);
end
endtask
initial begin
clk = 1'b1;
rst_n = 1'b0;
rx=1;
key_in=2'b11;
#(CLK_CYCLE*2);
rst_n = 1'b1;
#(CLK_CYCLE*2);
my_uart_rx(8'h33);
#(CLK_CYCLE*2);
my_uart_rx(8'h22);
#(CLK_CYCLE*2);
my_uart_rx(8'h44);
#(CLK_CYCLE*2);
my_uart_rx(8'h55);
#(CLK_CYCLE*2);
my_uart_rx(8'h66);
#(CLK_CYCLE*200);
key_in=2'b10;
#(CLK_CYCLE*21);
key_in=2'b11;
wait(u_top.u_spi_contrl.done2idle);
#(CLK_CYCLE*200);
key_in=2'b01;
#(CLK_CYCLE*21);
key_in=2'b11;
wait(u_top.u_spi_contrl.done2idle);
#(CLK_CYCLE*20000);
$stop;
end
endmodule
3.2 仿真测试图
4.上板测试
本次测试采用JCOM上位机,观察下图可见,输入Flash的数据与从中读取到的输出数据一致,至此SPI控制Flash读写代码设计完成。
标签:wire,tx,读写,Flash,rx,SPI,指令,rst,data From: https://blog.csdn.net/weixin_56481118/article/details/137072272