赞
踩
1.SPI(Serial Peripheral Interface)是一种高速、全双工、同步串行通信总线,由摩托摩拉公司推出。
2.优缺点:全双工通信,通信方式简单,相对数据传输速度较快;SPI没有应答机制确认数据是否接收,数据可靠性上有一定缺陷。(相对IIC协议)。
SCK :时钟信号线,用于同步通讯数据。
MOSI:主设备输出/从设备输入引脚。
MISO:主设备输入/从设备输出引脚。
CS :片选信号。也称CS_N(片选信号低电平有效)。片选信号线独立,有多少个从机就有多少个CS。
CPOL(时钟极性) : 片选信号处于空闲状态时,时钟的电平。
CPHA(时钟相位):采样的时钟边沿(奇数沿/偶数沿);CPHA=0,采样的时钟边沿为奇数沿;CPHA=1,采样的时钟边沿为偶数沿;采样沿保持数据稳定,另一沿改变数据。
1.1 储存属性:16Mbit,一共32个扇区,每个扇区256页,每页256字节,Flash断电后数据不会擦除
1.2 SPI模式支持:M25P16支持SPI模式0和模式3
1.3 引脚
指令传输时是MSB传输
WREN( Write Enable)指令通过驱动芯片选择(S)低,发送指令代码,然后驱动芯片选择(S)高。必须在每个页程序(PP)、扇区擦除(SE)、批量擦除(BE)和写入状态寄存器(WRSR)指令之前,输入WREN指令以设置写入启用锁存器(WEL)位。
扇区擦除(SE)指令将所选扇区内的所有位设置为1 (FFh)。扇区内的任何地址都是扇区擦除(SE)指令的有效地址。指令序列如下所示。芯片选择(S)必须在最后一个地址字节的第八位被锁存后驱动为高电平,否则扇区擦除(SE)指令不执行。一旦芯片选择(S)驱动高,自定时扇区擦除周期(其持续时间为tse)被启动
地址例子:
当扇区擦除周期正在进行时,可以读取状态寄存器以检查正在进行的写入(WIP)位的值。在自定时扇区擦除周期中,WIP位为1,擦除完成后为0。在周期完成之前的某个未指定时间,写使能锁存(WEL)位被重置。一个扇区擦除(SE)指令,应用于由块保护(BP2, BP1, BPO)位保护的页(见表2和表3)不执行。
全擦除指令将flash内的所有位都置1
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)指令不再被执行。
如果数据超过256字节,那么前面的数据会被覆盖,只有最后的256字节数据会被保留。如果小于256字节,则正常写入,且不会对本页其他字节数据造成影响
如果页编程的字节地址不是全零的话,就会在本页内循环写入数据(到了本页最后一字节,下一字节会回到本页起始地址编程,即字节地址全零的地方开始
可以任意地址读,读完该地址后,地址自加1;
写指令和擦除指令执行时,读取指令不会执行,且不会影响正在执行的指令。
S拉低 + 读ID指令 + 3字节ID(设备ID、存储类型、存储容量)+ S拉高
读取识别(RDID)指令在数据输出期间的任何时间通过驱动芯片选择(S)高来终止。
当擦除或程序周期正在进行时,任何读标识(RDID)指令都不会被解码,并且对正在进行的周期没有影响。
- /*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
- 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
- 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
UART模块、按键消抖模块;
- `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
本次测试采用JCOM上位机,观察下图可见,输入Flash的数据与从中读取到的输出数据一致,至此SPI控制Flash读写代码设计完成。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。