FPGA基础协议三:SPI读取FLASH

FPGA基础协议三:SPI读取FLASH

一、基本概念

1. SPI协议

1.1 协议简介

SPI (Serial Peripheral Interface)串行外设接口是由摩托罗拉公司定义的一种串行外围设备接口,是一种全双工、同步的通信总线,只需要四根信号线即可,节约引脚,同时有利于PCB的布局。正是因为SPI具有这种简单易用的特性,越来越的芯片集成了SPI通信协议,如FLASH、AD转换器等。

特点: 高速、全双工、同步。

模式: SPI分为主、从两种模式。一个SPI通讯系统必须包含一个且只能是一个主设备(Master),外带一个或多个从设备(Slave)。提供时钟的为主设备,接收时钟的设备为从设备,SPI接口的读写操作都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

1.2 SPI信号线

SPI一共使用4条信号线进行通讯:

  • MISO:Master Input Slave Output;
  • MOSI:Master Output Slave Input;
  • SCLK:串行时钟信号,由主设备产生;
  • CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定的从设备通讯,避免数据线上的冲突。

image-20220725204258675

1.3 SPI数据收发

使用SPI协议的主从机设备都有一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。

  1. 首先拉低片选信号,表示与该设备进行通讯;
  2. 主机发送SCLK时钟信号,通知从机即将读写数据。这里根据工作模式判断时钟是低电平有效还是高电平有效;
  3. 主机将要发送的数据先发送到数据缓存区(Memory),缓存区经过移位寄存器,串行移位寄存器通过MOSI信号线将字节一位一位地移出去传送给从机,同时MISO接口接收到的数据经过移位寄存器一位一位地移到接收缓存区。
  4. 从机也可将数据从Memory中提取,交给移位寄存器后从MISO依次发送给主机。同时通过MOSI信号线接收主机发送的数据,这样,两个移位寄存器中的内容就被交换了。

在这里插入图片描述

SPI只有主模式和从模式之分,没有读和写的说法,外设的写操作和读操作是同步完成的。如果只进行写操作,主机只需忽略接收到的字节;反之,若主机要读取从机的一个字节,就必须发送一个空字节来引发从机的传输。【收发同步】

1.4 SPI工作模式

CPOL: 时钟极性。取0表空闲时间低电平,第一个时钟沿为上升沿,取1表示空闲期间高电平,第一个时钟沿是下降沿。

扫描二维码关注公众号,回复: 14467552 查看本文章

CPHA: 时钟相位。取0表从第一个时钟跳边沿开始检测,取1则从第二个跳边沿开始检测。

然后将极性与相位排列组合,得到以下四个工作模式:

模式 CPOL【极性】 CPHA【相位】 描述
0 0 0 空闲时为低电平,时钟前沿采样【上升沿或第一个跳变沿】
1 0 1 空闲时为低电平,时钟后沿采样【下降沿或第二个跳变沿】
2 1 0 空闲时为高电平,时钟前沿采样【下降沿或第一个跳变沿】
3 1 1 空闲时为高电平,时钟后沿采样【上升沿或第二个跳变沿】

1.5 SPI优缺点

优点

  • 全双工串行通信;
  • 高速数据传输速率;
  • 简单的软件配置;
  • 非常灵活的数据传输,不限于8位,可以是任意大小;
  • 极其简单的硬件结构:
    • 从站不需要唯一地址【不同于I2C】
    • 从机使用主机时钟,不需要精密时钟振荡器/晶振【不同于UART】
    • 不需要收发器【不同于CAN】

缺点

  • 没有硬件从机应答信号【从机可能在不知情的情况下无处发送】;
  • 仅支持一个主设备;
  • 需要更多的引脚【不同于I2C只需要两个】;
  • 没有硬件级别的错误检查协议;
  • 与RS232和CAN总线相比,只支持极短距离传输。

2. M25P16 FLASH存储芯片

什么是FLASH?

  • 一种存储芯片,通过特定的程序可以修改里面的数据;

  • FLASH在电子及半导体领域往往表示Flash Memory的意思,即平时所说的“闪存”,全名叫Flash E2PROM Memory;

  • FLASH存储器又名闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除编程E2PROM的性能,还具有可以快速读取存储数据(NVRAM)的优势,使数据不会因为断电而丢失。

2.1 芯片接口

image-20220726091952556

C: 串行时钟。在串行时钟的下降沿,输入的数据data被串行地移出;为接口提供时序;

D: 串行数据输入。输入指令、地址、数据,在串行时钟的上升沿被锁存;

Q: 串行数据输出;指令、地址或数据在时钟上升沿被锁存,在时钟下降沿之后,输出数据Q改变;

S_N: 片选。片选信号S为高时,flash未被选中,数据输出端Q为高阻态,除非内部程序擦除或写状态寄存器时,其处于待机模式,片选信号S为低时,flash处于激活状态;

W_N: 写保护信号。主要目的是冻结受程序或删除指令保护的内存区域;

HOLD_N: 保持信号。用于在片选信号S为低时,取消flash与其他任何设备之间的通信。

2.2 芯片特性

容量: 16Mbit/2MByte;一共32个扇区,每个扇区256页,每页256Byte;

页编程速度: 256Bytes/1.4ms;(约为1.462bit/ns)

擦除: 支持512Kbit单扇区擦除以及2MByte整块擦除;

工作模式: 支持00和11两种模式;

  • 00:时钟低电平空闲,在第一个跳边沿也就是上升沿进行采样。

img

  • 11:时钟高电平空闲,在第二个跳边沿也就是下降沿进行采样。

img

2.3 命令

该存储芯片的命令有很多,我们这里只提取出来对本次工程有用的命令。

image-20220726224041031

核心命令:

Instruction Code(H) Code(B) 说明
WREN 06 0000 0110 写使能
RDID 9F 1001 1111 读ID
RDSR 05 0000 0101 读寄存器判断最后一位是0
READ 03 0000 0011 读数据
PP 02 0000 0010 页编程
SE D8 1101 1000 扇区擦除

2.4 时序

  • 写使能: 1字节指令

    image-20220726225735643

  • 读ID: 1字节指令,3字节的ID数据

    image-20220726225755364

  • 写状态寄存器: 写入状态寄存器(WRSR)写入状态寄存器(WRSR)指令允许将新值写入状态寄存器。在接受它之前,必须先执行了写入启用(WREN)指令。在写入启用(WREN)指令被解码并执行后,设备将设置写入启用锁存器(WEL)。

    image-20220804145833628

  • 读状态寄存器: 读取状态寄存器(RDSR)指令允许读取状态寄存器。状态寄存器可以在任何时候被读取,即使在一个程序、擦除或写状态寄存器周期正在进行中。当其中一个循环正在进行时,建议在向设备发送新指令之前检查“正在写入”(WIP)位。也可以连续读取状态寄存器,如下面时序图所示。

    image-20220726225818779

    WEL :写使能锁存bit,高电平代表写使能激活;

    WIP :写状态bit,此bit拉高代表正在写,相当于一个忙碌信号;

    BP0 BP1 BP2 :块保护,拉高时代表指定内存区域不接收PP SE的指令;当且仅当两个块保护命令为0时才能使用批量擦除BE命令;

    SRWD :状态寄存器写入禁用。字面意思,很好理解。

    image-20220726225833051

  • 读数据: 1字节指令,三字节地址,1字节数据

    image-20220726225841545

  • **页编程: ** 1字节指令,3字节地址,1字节数据

    image-20220726225851797

  • 扇区擦除: 1字节指令,3字节地址

    image-20220726225900915

2.5 重要的时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mFDYP41m-1644821130467)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220124155351427.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k3QKdgU6-1644821130467)(C:\Users\Jin\AppData\Roaming\Typora\typora-user-images\image-20220124155408491.png)]

名称 持续时间 描述
C_TIME 100ns 一个操作结束后到下一个操作片需要100ns,比如一个命令到下一个命令
PP_TIME 1.4-5ms 页编程的所需要的时间5ms
SE_TIME 1-3s 擦除所需要的时间3s

二、逻辑设计

1. 实验需求

使用FPGA通过SPI协议从M25P16 FLASH芯片中读取/存储数据,并使用数码管显示读出来的设备ID、写入数据、读出数据。

2. 设计思路

经过手册我们可以看到三种操作:

  • 读ID操作: 读ID指令0x9F + 接收(ID高字节ID中字节ID低字节);

  • 读数据操作: 读数据指令0x03 + (地址高字节地址中字节地址低字节)+接收数据字节

  • 写操作: 分为以下4种子操作

    • 写使能:写使能指令0x06 + 延时;
    • 扇区擦除:擦除指令0xD8 + 地址高字节 + 地址中字节 + 地址低字节 + 延时;
    • 读状态寄存器: 读状态寄存器指令0x05 + 接收状态字
    • 页编程:页编程指令0x02 + 地址高字节 + 地址中字节 + 地址低字节 + 数据字节 + 延时;

    这四个子操作相互之间都有联系:

    比如在进行扇区擦除和页编程时,都需要先发送一个写使能命令,在完成写使能或扇区擦除后也可以再发送一次读状态寄存器命令;

    然后根据返回的状态字中的WELWIP来判断操作是否做完或者设备是否忙碌,如果符合条件则可以进行下一步操作(页编程)或者回到初始位;这也是为什么我们把读状态寄存器归类到写操作里面。

    这里的读状态寄存器与I2C读取AHT10读状态字来检验校验位有异曲同工之处,如果你要坚持严谨的操作,那么按照规定频繁使用这个操作是没有问题的,但如果只是为了完成单纯的写入操作,只要等待的时间够长,就可以抛开读状态寄存器这一步。

2.1 模块:

image-20220806110150938

image-20220806110220885

key_debounce: 按键消抖模块;

sig_driver: 数码管驱动模块;

spi_master: spi接口模块,负责将数据以符合spi协议的方式接收或发送;

flash_ctrl(flash_wr,flash_rd): flash读写控制模块,负责将需要发送的命令与数据转交给spi接口模块,并负责处理从spi接口读取的数据。

2.2 工作步骤:

  • 读ID操作:
    1. 发送读id命令;
    2. 依次接收id高中低字节;
  • 读数据操作:
    1. 发送读数据指令;
    2. 依次发送数据地址的高中低字节;
    3. 接收读取的数据;
  • 写数据操作:
    1. 发送写使能命令;
    2. 发送扇区擦除命令(写之前要擦除相应区域,因为写操作不能直接覆盖原本的数据);
    3. 等待操作延时;
    4. 读状态寄存器检验wip wel,观察设备是否忙碌,不再忙碌则进行下一步操作(延时足够可跳过);
    5. 再次发送写使能命令(如果跳过第四步则需要执行);
    6. 发送写数据指令;
    7. 发送写数据高中低地址;
    8. 发送被写入数据;

3. 程序框图

靓仔靓女,暂时看看这个RTL模块图,框图得等后面和核心代码一起上了。

4. 状态机

4.1 SPI_INTERFACE

image-20220806142550838

状态图很简单,一共三个状态三个条件:

**IDLE:**初始装填;

**READY:**准备传输状态,这个时候从ctrl模块接收要发送的数据或命令或操作模式;

**TRANS:**传输状态,这里是双向的;

IdleReady的条件是接收到req请求信号,这里的请求信号由读写控制模块发出,不代表单一的读或写,收到req信号后两条总线都会开始工作;

TransIdle的条件为end_byte,同样也是等待一个串并转换或并串转换的完成,并且因为是双总线,发与收各管各的,相对I2C而言轻松不少。

接口模块还有一个done信号,负责传递给读写控制模块告诉这边已经完成发送/接收1个字节,这个状态很重要,关系到读写状态机的状态跳转。

4.2 读状态机

image-20220806143356737

根据之前提到的操作步骤得出:根据读数据和读ID两种操作构成两条分支。

CMD即是发送命令,ADDR即是发送3字节地址,DATA则是接收数据,不同的是ReadData接收的数据是1字节,ReadID接收的数据是3字节;

因为这里设置的使用按键控制读写,key0,key1分别控制读id,读数据,所以IDLE的状态跳转就单纯由按键控制;

剩下的状态条状条件基本一致:end_byte。

这里同样存在一个byte计数器,而且因为每个状态发送或接收的byte数量不一样,所以end_byte拉高的条件也是跟随状态改变的。

add_byte的条件则跟据接口模式传递的done信号和非IDLE模式下拉高。

因为done信号代表发送或接收的那一字节已经完成,所以就不用再多说了~

4.3 写状态机

主状态机:

主状态机负责决定什么时候发送什么命令。

根据我分为的严谨模式粗暴模式分为两种:

image-20220806144926694

这是带RDSR的,即读状态寄存器用于检验是否可执行下一步操作,可以看出来这RDSR什么时候都可以发,发得越多越严谨,但是咱们初学者嘛,如果只是想验证读写功能,可以不用那么严格地要求自己,只要咱们做完一个操作后等得够久,那么这延时一定是满足要求的,所以我们可以简化一下主状态机:

image-20220806145237468

这样就通俗易懂了。一句话概括:先擦除,再页编程,而且擦除和页编程之前都需要写使能来激活写功能。

既然我们把RDSR简化了,这里就要讨论延时得延多久了,首先需要延时最长的操作是SE,最长3s,那咱们等3.1s就行了,所以我们可以吧从状态机的延时操作计数器设置为3.1s即可。

从状态机:

主状态机是负责命令发送的,那么发送命令操作需要的所有步骤都有从状态机来完成。

image-20220806145745129

结合上文中所分析的操作步骤得出这样一个结果:

命令状态是必须有的,有的操作不用发送地址,有的操作也不用发送数据,比如说写使能,这样就很好理解状态为什么会这样跳转了。

状态跳转条件也会相对而言灵活很多,除了延时操作一般都是根据byte计数器done决定,并且根据主状态机可以决定是否跳过当前状态,比如延时操作,虽然所有状态最终都指向它,但是写使能和读状态寄存器是可以根据主状态机的条件直接略过计数器跳回IDLE的。

三、代码实现

施工中…(根据状态机的描述很容易就写出来啦)

四、仿真测试

施工中…

五、上板测试

施工中…

附、知识点补充

升序与降序运算符

1.“+:”

变量[起始地址 +: 数据位宽] <–等价于–> 变量[(起始地址+数据位宽-1):起始地址]

data[0 +: 8] <–等价于–> data[7:0]

data[15 +: 2] <–等价于–> data[16:15]

2.“-:”

变量[结束地址 -: 数据位宽] <–等价于–> 变量[结束地址:(结束地址-数据位宽+1)]

data[7 -: 8] <–等价于–> data[7:0]

施工中…

尾、总结

全双工的SPI协议相比I2C而言简单不少,用途也更加广泛。

猜你喜欢

转载自blog.csdn.net/ChenJ_1012/article/details/126195296