【FPGA】SPI协议详解及对flash读写操作

本文介绍了使用FPGA(Cyclone IV EP4CE6F17C8)通过SPI协议读写Flash存储器的实现过程。首先,详细解释了SPI协议的工作原理和Flash芯片(如M25P16)的特性,包括WREN、RDID、WRSR、READ、PP和SE等关键指令。接着,展示了SPI时钟产生的状态图和代码实现,以及完整的FPGA模块设计,包括SPI主控器、写控制模块、读取模块和Flash写入模块。最后,通过实例验证了读写操作的正确性。整个设计实现了通过UART接口接收数据,按键触发读写操作,并通过UART输出读取结果。

概括

内容用FPGA芯片Cylone IV E:EP4CE6F17C8 实现对flash读写操作,数据通过uart寄存在FIFO_0,按键_0按下读取数据通过SPI协议写入flash,再按键_1按下通过SPI协议读出数据寄存在FIFO_2经uart协议输出

一、SPI协议、flash讲解

1、SPI协议

SPI(Serial Peripheral interface)是由摩托罗拉公司定义的一种串行外围设备接口, 是一种全双工、同步的通信总线,只需要四根信号线即可,节约引脚,同时有利于 PCB 的布局。正是出于这种简单易用的特性,现在越来越多的芯片集成了 SPI 通信协议,如 FLASH、AD 转换器等。
在这里插入图片描述
SPI 协议支持一主多从、全双工、半双工模式。
SPI 通信需要四根线,分别为 cs_n、sclk、mosi 和 miso。其中 cs_n、sclk、mosi 是由主机输出给从机,而 miso 由从机输出给主机。cs_n 用于控制从机是否被选中,也就是说只有片选信号有效时,对从机的操作才有效;sclk 是由主机产生的同步时钟,用于同步数据;mosi 和 miso 是主机发送和接收的数据脚。传输数据时,以 MSB 的形式传输

SPI 通信一般有 4 种不同的模式,不同的从设备在出厂时被厂家配置为其中几种通信模式,通信模式是不允许用户修改的。主设备和从设备必须在同一模式下进行通信,否则数据会接收错误。SPI 的通信模式是由 CPOL(时钟极性)和 CPHA(时钟相位)来决定的;
模式 0:CPOL = 0,CPHA = 0;
模式 1:CPOL = 0,CPHA = 1;
模式 2:CPOL = 1,CPHA = 0;
模式 3:CPOL = 1,CPHA = 1。
在这里插入图片描述
当 CPOL = 1 时,sclk 在空闲时为高电平,发起通信后的第一个时钟沿为下降沿;
当 CPOL = 0 时,SPI 时钟信号 sclk 空闲时为低电平,发起通信后的第一个时钟沿为上升沿
当 CPHA = 1 时,时钟的第一个变化沿(上升沿或者下降沿)数据开始改变, 那么也就意味着时钟的第 2 个变化沿(与第一个变化沿相反)锁存数据;
当 CPHA = 0 时,数据在时钟的第一个变化沿之前就已经改变,并且保持稳定,也就意味着在时钟的第一个变化沿锁存数据。

2、flash

M25P16 是一款带有写保护机制的串行 Flash 存储芯片,容量为 16Mbit(2MB);一共
分为 32 个 sector,每个 sector 共 256B 存储容量,每个 sector 擦写次数为 10 万次、数
据保存期限至少 20 年。支持 SPI 通信模式 0 和模式 3。在对 Flash 芯片写数据之前,需要先执行擦除操作,将所有数据变为 FF。

flash指令表
在这里插入图片描述
实现读写操作只用了WREN、RDID、WRSR、READ、PP、SE命令

(1)WREN

在手册已经说了写使能必须在每个PP、SE、BE、WRSR指令前设置,在写使能发送结束后片选信号拉高
在这里插入图片描述

(2)RDID

读取标识(RDID)指令允许读取8位的制造商标识,然后是两个字节的设备标识。制造商标识由JEDEC指定,意法半导体的识别值为20小时。设备标识由设备制造商指定,表示第一个字节的内存类型(20小时),以第二个字节表示设备的内存容量(15小时)。同样数据读取结束后片选信号拉高
在这里插入图片描述

(3)WRSR

读取状态寄存器(RDSR)指令允许读取状态寄存器。状态寄存器可以在任何时候被读取,即使在一个程序、擦除或写状态寄存器周期正在进行中。这个命令我认为是在读取其状态的一个命令可有可无有需要可以去看手册,其时序图如下
在这里插入图片描述

(4)READ

首先通过驱动芯片选择低来选择设备。读取数据字节(READ)指令的指令代码后面是一个3字节地址,每个位在串行时钟的上升边缘期间被锁定然后,在该地址的内存内容在串行数据输出上移出,在串行时钟的下降边缘期间
这里指出下flash只支持SPI的模式0、模式3,
时序图如下
在这里插入图片描述

(5)PP

页面程序(PP)指令允许在内存中编程字节(将位从1改为0)当页面程序周期正在进行时,可以读取状态寄存器以检查正在写入(WIP)位的值。在自定时页面程序周期中,正在写入(WIP)位为1,完成时为0。
在这里插入图片描述

(6)SE

扇区擦除(SE)指令将选定扇区内的所有位设置为1(FFh)。在接受它之前,必须先执行了写入启用(WREN)指令。在写入启用(WREN)指令被解码后,设备将设置写入启用锁存器(WEL)。扇区擦除(SE)指令通过驱动芯片选择低输入,然后是指令代码和串行数据输入.
在这里插入图片描述
下表有时钟频率、擦除、写入、片选信号拉高再次拉低的时间
在这里插入图片描述
在这里插入图片描述

二、状态图

在这里插入图片描述
在这里插入图片描述

三、代码

1、代码分析

always @(*)begin
        case (spi_mode)
           0 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
               sck <= 0;
           end
           else if(cnt_sck > `SCK_HALF-1)begin
                sck <= 1;
           end 
           else begin
              sck <= 0; 
           end  
           end
           1 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
               sck <= 1;
           end
           else if(cnt_sck > `SCK_HALF-1)begin
                sck <= 0;
           end 
           else begin
               sck <= 0; 
           end 
           end
           2 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1 ))begin
               sck <= 1;
           end
           else if(cnt_sck > `SCK_HALF-1 )begin
                sck <= 0;
           end  
           else begin
               sck <= 1;
           end
           end
           3 : begin if(spi_vld && (cnt_sck <= `SCK_HALF-1))begin
               sck <= 0;
           end
           else if(cnt_sck > `SCK_HALF-1 )begin
                sck <= 1;
           end  
           else begin
               sck <= 1;
           end
           end 
            default: sck <= sck;
        endcase
    end

SPI时钟的产生思路是以时钟去对齐数据,这里SPI四种模式都写出来了的再数据读取只写了模式0
在这里插入图片描述
数据传输时序图
在这里插入图片描述
因为这次没画完时序图,代码写的时候有点小乱,整里了下

2、全部代码

(1)param

//参数定义
//spi时钟参数
`define  SCK_PERIOD  20
`define  SCK_HALF    10



//spi command
`define  CMD_WREN       8'h06               // 
`define  CMD_WRDI       8'h04               // 
`define  CMD_RDID       8'h9f               // 
`define  CMD_RDSR       8'h05               // 
`define  CMD_WRSR       8'h01               // 
`define  CMD_READ       8'h03               // 
`define  CMD_FAST_READ  8'h0b               // 
`define  CMD_PP         8'h02               // 
`define  CMD_SE         8'hd8               // 
`define  CMD_BE         8'hc7               // 
`define  CMD_DP         8'hb9               // 
`define  CMD_RES        8'hab               //  

//spi byte
`define  CMD_BYTE        1               // 命令1字节 
`define  ADDR_BYTE       3               // 地址3字节 
`define  ID_BYTE         3               // id3字节 
`define  DATA_BYTE       1               // id3字节 
`define  RDSR_BYTE       100             // 读状态寄存器最大可读1000次
`define  DELAY_5MS       250_000         // 5ms 数据写入后需要等待5ms才能读出

//波特率
`define  BAUD_9600   5208
`define  BAUD_19200  2604
`define  BAUD_38400  1302
`define  BAUD_115200 434

`define STOP_BIT  1'b1      //数据停止位
`define START_BIT 1'b0      //数据开始位

(2)test

module test( 
    input				clk		,
    input				rst_n	,
    input   [2:0]       key_in  ,
    input               uart_rxd,
    input               miso    ,
    output              sck     ,
    output              mosi    ,
    output              cs_n    ,
    output              uart_txd
);								 
                        
    //中间信号定义
    wire        [2:0]           key_out             ;

    wire        [7:0]           rx_byte             ;
    wire                        rx_byte_vld         ;

    wire        [7:0]           dout_m              ;
    wire        [7:0]           dout                ;
    wire                        dout_vld            ;
    wire                        spi_vld             ;
    wire                        en                  ;

    wire        [7:0]           din_m               ; 
    wire                        busy_m              ; 
    wire                        wr_done             ; 

    wire                        busy_t              ;



    //模块例化
key_filter_fsm # (.KEY_W(3),.TIME_20MS(1_000_000))u_key_filter_fsm
(
    /*input 			            */.clk		(clk            ),
    /*input 			            */.rst_n	(rst_n          ),
    /*input 		[KEY_W - 1:0]	*/.key_in	(key_in         ),
    /*output 		[KEY_W - 1:0]	*/.key_out	(key_out        )
);

uart_rx u_uart_rx
(
    /*input                       */.clk         (clk           ),
    /*input                       */.rst_n       (rst_n         ),
    /*input                       */.rx_din      (uart_rxd      ),
    /*input           [1:0]       */.baud_sel    (2'b0          ),
    /*output          [7:0]       */.rx_byte     (rx_byte       ),
    /*output                      */.rx_byte_vld (rx_byte_vld   )
);

wr_control u_wr_control( 
    /*input				  */.</
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值