• 从VTI7064与W25Qxx了解SPI通信协议


    在学习过程中记录。

    学习背景

    最近在做的项目需要设计电路包含外扩FLASH(W25Q128)与SRAM(VTI7064),二者都用到了SPI通信协议,之前没学过,学习记录一下。

    顺便说一下这次学习中发现的好用工具WPS AI。可以对文档进行总结归纳,你可以对他提问相关的内容,回答十分令人满意,也是一次巧合,我在更新WPS后本想翻译VTI7064的芯片手册,全是英文属实难懂,看到了WPS AI。之前充值的会员Pro,他让我免费体验3个月,时间过后估计要收费,不过体验很好。

    SPI通信协议是一种高速、全双工、同步的串行通信协议,主要用于短距离通信。它使用主从模式进行通信,其中主设备负责发起通信,从设备负责响应通信。SPI通信协议使用四根信号线进行通信,分别是MOSI、MISO、SCLK和CS。其中,MOSI和MISO分别用于主设备向从设备发送数据和从设备向主设备发送数据,SCLK用于同步数据传输,CS用于选择要通信的从设备。SPI通信协议支持多种数据传输模式,包括标准模式、双线模式、四线模式和QPI模式等。
    相关原文: 5页 

    引脚

    通常 SPI 通过 4 个引脚与外部器件相连:
    MISO :主设备输入 / 从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
    MOSI :主设备输出 / 从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
    SCK :串口时钟,作为主设备的输出,从设备的输入
    NSS :从设备选择。这是一个可选的引脚,用来选择主 / 从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的NSS引脚可以由主设备的一个标准I/O 引脚来驱动。一旦被使能 (SSOE ) NSS 引脚也可以作为输出引脚,并在SPI 处于主模式时拉低;此时,所有的 SPI 设备,如果它们的 NSS 引脚连接到主设备的NSS 引脚,则会检测到低电平,如果它们被设置为 NSS 硬件模式,就会自动进入从设备状态。当配置为主设备、NSS 配置为输入引脚 (MSTR=1 SSOE=0) 时,如果 NSS被拉低,则这个SPI 设备进入主模式失败状态:即 MSTR 位被自动清除,此设备进入从模式

    在有些地方MISO=SO、MOSI=SI、NSS=CS、NSS、SS、SCK=SCLK

    只是称呼不同,但意义相同。

    模式介绍

    刚开始学的时候对SPI的四种模式很是糊涂,不过在回过头看数据手册的时序图就没那么复杂了,下面我说一下我的理解。

    SPI有四种模式,模式0、模式1、模式2、模式3.这四种模式取决于SPI_CR1寄存器的位0(CPHA)与位1(CPOL)

    CPOL(时钟极性)位控制在没有数据传输时时钟的空闲状态电平,此位对主模式和从模式下的设备都有效。如果CPOL被 清’0’SCK引脚在空闲状态保持低电平;如果CPOL被置’1’SCK引脚在空闲状态保持高电平。

    如果CPHA(时钟相位)位被置’1’SCK时钟的第二个边沿(CPOL位为0时就是下降沿,CPOL位 为’1’时就是上升沿)进行数据位的采样,数据在第二个时钟边沿被锁存。如果CPHA位被清’0’, SCK时钟的第一边沿(CPOL位为’0’时就是上升沿,CPOL位为’1’时就是下降沿)进行数据位采 样,数据在第一个时钟边沿被锁存。

    CPOL 时钟极性和 CPHA 时钟相位的组合选择数据捕捉的时钟边沿。
    212 显示了 SPI 传输的 4 CPHA CPOL 位组合。此图可以解释为主设备和从设备的 SCK 脚、
    MISO 脚、 MOSI 脚直接连接的主或从时序图。

    先分析CPHA=1、CPOL=1根据上面介绍,可以了解到对应的是,SCK引脚在空闲状态保持高电平。

    SCK时钟的第二个边沿也就是上升沿,进行数据位的采样,数据在第二个时钟边沿也是上升沿被锁存。

    看一下数据时钟的时序图

    在时钟的上升沿MISO信号线采集来自从机的信号,同时MOSI信号线输出的数据被从设备锁存。

    从图上看时钟信号线的两端都是高电平,对应了出CPOL(时钟极性)位=1.

    模式0

    CPOL=0:空闲状态时,SCK保持低电平;

    CPHA=0:数据采样从第一个时钟边沿开始,时钟上升沿进行数据采样。

    模式1

    CPOL=0:空闲状态时,SCK保持低电平;

    CPHA=1:数据采样从第二个时钟边沿开始,时钟下降沿进行数据采样。

    模式2

    CPOL=1:空闲状态时,SCK保持高电平;

    CPHA=0:数据采样从第一个时钟边沿开始,时钟下降沿进行数据采样。

    模式3

    CPOL=1:空闲状态时,SCK保持高电平;

    CPHA=1:数据采样从第一个时钟边沿开始,时钟下降沿进行数据采样。

    CPOL与CPHA组成四种不同模式,常用的是模式0.

    SPI硬件框图

    对于SPI的配置有软件模拟SPI与硬件SPI。软件的话可以是任意的IO口,更加灵活,但是对于软件编写比较麻烦。而硬件SPI是STM32内部自带的硬件电路,功能相对软件会少很多,但是操作简单。

    SPI作为一种串行通信协议是如何进行通信呢?

    所谓通信实际是数据的交换,数据的发送与接收,将所要发送的数据转化为电平信号(TTL)发送出去,而SPI的数据发送,数据接收是依靠硬件上的移位寄存器以及发送缓冲区接收缓冲区实现的。

    看一下SPI框图

    下图是一个单主和单从设备互连的例子

    数据发送过程

    在写操作中,数据字被并行地写入发送缓冲器。
    当从设备收到时钟信号,并且在 MOSI 引脚上出现第一个数据位时,发送过程开始 ( 译注:此时第一个位被发送出去) 。余下的位 ( 对于 8 位数据帧格式,还有 7 位;对于 16 位数据帧格式,还有15位 ) 被装进移位寄存器。当发送缓冲器中的数据传输到移位寄存器时, SPI_SP 寄存器的 TXE标志被设置,如果设置了SPI_CR2 寄存器的 TXEIE 位,将会产生中断。

    在时钟沿到来后主机的移位寄存器会在时钟沿将数据移出到MISO线进入从机,同时主机的MOSI接收来自从机的数据到移位寄存器。数据的 接收过程同理。

    初始状态

    8个时钟周期过后

    由此完成了一个字节数据的交换。

    另外在通信十还会有一个开始与结束的状态,这取决于NSS

    W25Q64

    上面介绍了如何发送一个字节,但是在实际应用中,以W25Q128为例,他需要先发送一个字节的指令码,指令码后根据实际情况,可以加其他字节,比如用SPI对W25Q128写数据时先发送写数据的指令码,随后发送地址,就是要写在哪里最后是写的内容。

    w25Q64写指令的代码是0x02,随后写入24位的地址码,最后加上指定的数据。

    这里解释一下为什么是24位的地址码。W25QXX后面的两位是存储空间。

    以W25Q64为,存储空间为64M-bit,8M字节、8MB=2^23.一共23位

    所以寻址时采用3字节24位足够查找全部的地址。

    同理W25Q128是128M-bit,16M字节,16M=2^24.需要24位的寻址空间。3字节24位刚好足够。

    注意

    写入数据操作时有几点注意事项:

    写入操作时:
    写入操作前,必须先进行写使能
    每个数据位只能由1改写为0,不能由0改写为1
    写入数据前必须先擦除,擦除后,所有数据位变为1擦除必须按最小擦除单元进行连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入
    写入操作结束后,芯片进入忙状态,不响应新的读写操作
    读取操作时:
    直接调用读取时序,无需使能,无需额外操作,没有页的限制读取操作结束后不会进入忙状态,但不能在忙状态时读取

    芯片寄存器

    W25Q64有两个状态寄存器

    比较重要的的是BUSY位(忙状态位)和WEL(写使能锁存)位 

    11.1.1BUSY
    BUSY是状态寄存器(S0)中的只读位,当设备执行页程序、扇区擦除、块擦除、芯片擦除或写入状态寄存器指令时BUSY位置1。在此期间时,设备将忽略除读取状态寄存器和擦除挂起之外的其他指令指令(参见AC特性中的tW、tPP、tSE、tBE和tCE)。当编程时,擦除或写入状态寄存器指令完成,BUSY位将被清除为0状态,表示设备准备就绪以获得进一步的指示。 
    11.1.2Write Enable Latch(WEL) 
    写使能锁存器(WEL)是状态寄存器(S1)中的只读位,在执行写使能指令后该位被置1。当设备被禁止写时,WEL状态位被清除为0。写禁用状态发生在上电时或以下任何指令之后:写入禁用,页面编程、扇区擦除、块擦除、芯片擦除和写入状态寄存器。

    所以当要写入数据时都要调用一下该指令,但是当写完后不需要调用写失能,会默认发生写失能

    指令集

    看一下指令集表

    根据这个表的内容就可以实现具体的操作了,比如读取厂商的ID,对W25Q64写入数据,读出数据。都可以按照上面的格式进行操作。

    1、Write Enable(06h写使能

    写使能指令(图4)将状态寄存器中的写使能锁位(WEL)位设置为1. WEL位必须在每个页面程序、扇区擦除、块擦除、芯片擦除和写入状态寄存器指令之前设置。写入使能指令是通过 /CS低,移动指令代码06h进入CLK上升沿上的数据输入(DI)引脚,然后驱动 /CS。

    2、Read Status Register-1 (05h)读状态寄存器1

    读状态寄存器指令允许读取8位状态寄存器。通过驱动/CS低电平并将状态寄存器-1的指令码“05h”和状态寄存器-2的指令码“35h”移到CLK上升沿的D引脚上进入指令。然后,状态寄存器位在CLK下降沿的DO引脚上首先以最高有效位(MSB)移出,如图6所示。状态寄存器位如图3a和3b所示,包括BUSY,WELBP2-BPO.TB.SEC.SRPO.SRP1和QE位(参见本数据表前面对状态寄存器的描述)
    读状态寄存器指令可以在任何时候使用,即使在程序、擦除或写状态寄存器周期进行时也是如此。这允许检查BUSY状态位,以确定周期何时完成以及设备是否可以接受另一条指令。可以连续读取状态寄存器,如图6所示。指令由驱动/CS高完成。

    使用该指时,首先发送05H指令码告诉W25Q64让他执行对应的操作,随后交换一个字节,得到的数据就是状态寄存器的值了。

    3、Page Program (02h) 页编程

    Page Program指令允许在先前擦除(FFh)内存位置对1到256字节(一页)的数据进行编程。在设备接受页程序指令(状态寄存器位WEL= 1)之前,必须执行写使能指令。该指令是通过驱动/CS引脚低,然后将指令代码“02h”后面跟着一个24位地址(A23-A0)和至少一个数据字节移动到DI脚来启动的。当数据被发送到设备时,/CS引脚必须在指令的整个长度内保持低电平。Page Program指令序列如图15所示。
    如果要对整个256字节的页面进行编程,则最后一个地址字节(8个最低有效地址位)应设置为0如果最后一个地址字节不为零,并且时钟的数量超过了剩余的页面长度,则寻址将封装到页面的开头。在某些情况下,可以编程少于256字节(部分页),而不会对同一页中的其他字节立生任何影响。执行部分页程序的一个条件是时钟的数量不能超过剩余的页长度。如果发送到设备的字节超过256个,寻址将封装到页的开头,并覆盖以前发送的数据。
    与写和擦除指令一样,在最后一个字节的第8位锁存之后,,/CS引脚必须被驱动到高位。如果不这样做,页面程序指令将不会被执行。当/CS被调高后,自定时的页面程序指令将在tpp的时间段内开始(参见AC特性)。当页程序周期正在进行时,读取状态寄存器指令仍然可以被访问以检查BUSY位的状态。BUSY位在页程序周期中为1,当周期结束并且设备准备好再次接受其他指令时变为0。在页面程序周期完成后,状态寄存器中的写使能锁存(WEL)位被清除为0.如果寻址页被块保护(BP2,BP1和BPO)位保护,则页面程序指令将不会被执行。

    页编程顾名思义是对W25Q64存储器的其中一页进行编程的,那么什么是页?

    W25Q64存储空间有8M,在内部将8M的存储空间进行划分分为块、扇区、页。

    8M分为128块每块64k。

    每块分为16扇区,每个扇区4k。

    每扇区分为16页,每页256位。

    看内部框图

    在进行页编程的时候是对页操作,将数据写入页。

    先输入指令码02h随后输入页地址,建议输入时页地址的首位,随后输入数据,最大可以输入256位

    4、Sector Erase (20h)扇区擦除

    扇区擦除指令将指定扇区内的所有内存(4k字节)设置为所有15 (FFh)的擦除状态。在设备接受扇区擦除指令(状态寄存器位WEL必须等于1)之前,必须执行Write Enable指令。该指令通过将/CS引脚驱动为低电平并将指令码“20h”移到24位扇区地址
    在最后一个字节的第8位锁存之后,/CS引脚必须被驱动到高位。如果没有这样做,扇区擦除指令将不会被执行。在/CS被驱动到高电平后,自定时扇区擦除指令将开始tSE的持续时间(参见AC特性)。当扇区擦除周期正在进行时,读取状态寄存器指令仍然可以被访问以检查BUSY位的状态。在扇区擦除周期中,BUSY位为1,当周期结束,设备准备好再次接受其他指令时,BUSY位变为O。在扇区擦除周期结束后,状态寄存器中的写使能锁存(WEL)位被清除为0。如果寻址页被块保护(SEC、TB、BP2、BP1和BPO)位保护(参见状态寄存器内存保护表),则扇区擦除指令将不会执行。

    扇区擦除先输入指令码,随后输入地址码即可擦除4kb的扇区,擦除后数据默认为0xFF,4k扇区内全部是FF。

    5、Read JEDEC ID (9Fh)读设备号

    出于兼容性原因,W25Q64BV提供了几个指令,以电子方式确定设备的身份。读取JEDEC ID指令与2003年采用的SPI兼容串行存储器的JEDEC标准兼容。指令通过驱动/CS引脚低电平并移动指令码“9Fh”来启动。JEDEC为Winbond分配的制造商ID字节(EFh)和两人设备ID字节,内存类型(D15-ID8)和容量(ID7-IDO)然后在CLK的下隆沿上以最高有效位(MSB)先移出,如图29所示。有关内存类型和容量值,请参阅制造商和设备标识表。

    首先输入指令码,随后交换三个字节。读出的三个字节就是厂商ID和设备ID

    6、Read Data (03h)读数据

    读取数据指令允许从内存中依次读取一个数据字节。该指令是通过驱动/CS引脚低,然后将指令代码“03h”和24位地址(A23-AO)转移到DI引脚来启动的。代码和地址位锁存在CLK引脚的上升沿上。接收到地址后,寻址内存位置的数据字节将首先以最高有效位(MSB)在CLK下降沿的DO引脚上移出。在每个字节的数据被移出后,地址自动增加到下个更高的地址,从而允许连续的数据流。这意味着只要时钟继续,就可以用一条指令访问整个内存。指令由驱动/CS高完成。
    Read Data指令序列如图8所示。如果在擦除、编程或写周期(BUSY=1)进行中发出读数据指令,则该指令将被忽略,并且不会对当前周期产生任何影响。读取数据指令允许时钟速率从直流到最大fR(见交流电气特性)。

    发送指令码,发送24位地址,交换三个字节,从W25Q64的DO线上输出所在地址的数据内容。

    电气特性

    软件模拟SPI

    W25Q64是通过操作指令集来实现各种功能的,如写使能,读数据等。

    我们必须需要用软件来模拟出他发送字节的时序,这样就能实现发送字节的时序。

    写使能指令只有一个字节,发送字节06h,进行写使能,再来看写使能的时序图

    使用SPI模式0

    CPOL=0:空闲状态时,SCK保持低电平;

    CPHA=0:数据采样从第一个时钟边沿开始,时钟上升沿进行数据采样。

    由时序图可以看到发送一个字节时

    1、单片机的MOSI输出一个低电平发送1位信号0,

    2、将时钟信号线从低电平拉高到电平,产生一个上升沿。W25Q64从第一个时钟边沿进行数据采样也就是上升沿

    3、W25Q64读取到移位数据的同时会在DO引脚移除1位数据,单片机的MISO接收这位数据并判断是否为1

    4、拉低时钟信号线产生一个时钟周期。

    这是发送1位数据的步骤,发送8位时循环即可。

    1. uint8_t MySPI_SwapByte(uint8_t ByteSend)
    2. {
    3. uint8_t i, ByteReceive = 0x00;
    4. for (i = 0; i < 8; i ++)
    5. {
    6. MySPI_W_MOSI(ByteSend & (0x80 >> i)); //单片机发送数据,W25Q64读入
    7. MySPI_W_SCK(1); //产生上升沿
    8. if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //单片机读入数据,W25Q64发送
    9. MySPI_W_SCK(0); //产生下降沿
    10. }
    11. return ByteReceive;
    12. }

    上面代码是江科大的源码

    他在教程上对时序是另一种解释,在SCK第一个边沿移入数据,第二个边沿移出数据

    在循环里实现在SCK第一个边沿上升沿移入数据,第二个边沿下降沿移出数据,循环8次后就完成了一个字节的发送。

    在发送字节的基础上可以实现指令集的所用功能。

    main.c

    1. #include "stm32f10x.h" // Device header
    2. #include "Delay.h"
    3. #include "OLED.h"
    4. #include "W25Q64.h"
    5. #include "usart.h"
    6. uint8_t MID;
    7. uint16_t DID;
    8. uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
    9. uint8_t ArrayRead[4];
    10. int main(void)
    11. {
    12. OLED_Init();
    13. W25Q64_Init();
    14. uart_init(9600);
    15. printf("数字:1\r\n");
    16. OLED_ShowString(1, 1, "MID: DID:");
    17. OLED_ShowString(2, 1, "W:");
    18. OLED_ShowString(3, 1, "R:");
    19. W25Q64_ReadID(&MID, &DID);
    20. OLED_ShowHexNum(1, 5, MID, 2);
    21. OLED_ShowHexNum(1, 12, DID, 4);
    22. printf("MID:%x,DID:%x",MID,DID);
    23. W25Q64_SectorErase(0x000000);
    24. W25Q64_PageProgram(0x000000, ArrayWrite, 4);
    25. W25Q64_ReadData(0x000000, ArrayRead, 4);
    26. OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);
    27. OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
    28. OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
    29. OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);
    30. OLED_ShowHexNum(3, 3, ArrayRead[0], 2);
    31. OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
    32. OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
    33. OLED_ShowHexNum(3, 12, ArrayRead[3], 2);
    34. while (1)
    35. {
    36. }
    37. }

    myspi.c

    1. #include "stm32f10x.h" // Device header
    2. void MySPI_W_SS(uint8_t BitValue)
    3. {
    4. GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
    5. }
    6. void MySPI_W_SCK(uint8_t BitValue)
    7. {
    8. GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
    9. }
    10. void MySPI_W_MOSI(uint8_t BitValue)
    11. {
    12. GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
    13. }
    14. uint8_t MySPI_R_MISO(void)
    15. {
    16. return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
    17. }
    18. void MySPI_Init(void)
    19. {
    20. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    21. GPIO_InitTypeDef GPIO_InitStructure;
    22. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    23. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
    24. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    25. GPIO_Init(GPIOA, &GPIO_InitStructure);
    26. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    27. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    28. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    29. GPIO_Init(GPIOA, &GPIO_InitStructure);
    30. MySPI_W_SS(1); //模式0 片选信号为高电平,不选中
    31. MySPI_W_SCK(0); //模式0 时钟信号空闲状态为低电平
    32. }
    33. void MySPI_Start(void)
    34. {
    35. MySPI_W_SS(0);
    36. }
    37. void MySPI_Stop(void)
    38. {
    39. MySPI_W_SS(1);
    40. }
    41. uint8_t MySPI_SwapByte(uint8_t ByteSend)
    42. {
    43. uint8_t i, ByteReceive = 0x00;
    44. for (i = 0; i < 8; i ++)
    45. {
    46. MySPI_W_MOSI(ByteSend & (0x80 >> i)); //单片机发送数据,W25Q64读入
    47. MySPI_W_SCK(1); //产生上升沿
    48. if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //单片机读入数据,W25Q64发送
    49. MySPI_W_SCK(0); //产生下降沿
    50. }
    51. return ByteReceive;
    52. }

    myspi.h

    1. #ifndef __MYSPI_H
    2. #define __MYSPI_H
    3. void MySPI_Init(void);
    4. void MySPI_Start(void);
    5. void MySPI_Stop(void);
    6. uint8_t MySPI_SwapByte(uint8_t ByteSend);
    7. #endif

    w25q64.c

    1. #include "stm32f10x.h" // Device header
    2. #include "MySPI.h"
    3. #include "W25Q64_Ins.h"
    4. void W25Q64_Init(void)
    5. {
    6. MySPI_Init();
    7. }
    8. void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
    9. {
    10. MySPI_Start();
    11. MySPI_SwapByte(W25Q64_JEDEC_ID);
    12. *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    13. *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    14. *DID <<= 8;
    15. *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    16. MySPI_Stop();
    17. }
    18. void W25Q64_WriteEnable(void)
    19. {
    20. MySPI_Start();
    21. MySPI_SwapByte(W25Q64_WRITE_ENABLE);
    22. MySPI_Stop();
    23. }
    24. void W25Q64_WaitBusy(void)
    25. {
    26. uint32_t Timeout;
    27. MySPI_Start();
    28. MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
    29. Timeout = 100000;
    30. while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)
    31. {
    32. Timeout --;
    33. if (Timeout == 0)
    34. {
    35. break;
    36. }
    37. }
    38. MySPI_Stop();
    39. }
    40. void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
    41. {
    42. uint16_t i;
    43. W25Q64_WriteEnable();
    44. MySPI_Start();
    45. MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
    46. MySPI_SwapByte(Address >> 16);
    47. MySPI_SwapByte(Address >> 8);
    48. MySPI_SwapByte(Address);
    49. for (i = 0; i < Count; i ++)
    50. {
    51. MySPI_SwapByte(DataArray[i]);
    52. }
    53. MySPI_Stop();
    54. W25Q64_WaitBusy();
    55. }
    56. void W25Q64_SectorErase(uint32_t Address)
    57. {
    58. W25Q64_WriteEnable();
    59. MySPI_Start();
    60. MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
    61. MySPI_SwapByte(Address >> 16);
    62. MySPI_SwapByte(Address >> 8);
    63. MySPI_SwapByte(Address);
    64. MySPI_Stop();
    65. W25Q64_WaitBusy();
    66. }
    67. void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
    68. {
    69. uint32_t i;
    70. MySPI_Start();
    71. MySPI_SwapByte(W25Q64_READ_DATA);
    72. MySPI_SwapByte(Address >> 16);
    73. MySPI_SwapByte(Address >> 8);
    74. MySPI_SwapByte(Address);
    75. for (i = 0; i < Count; i ++)
    76. {
    77. DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
    78. }
    79. MySPI_Stop();
    80. }

    w25q64.h

    1. #ifndef __W25Q64_H
    2. #define __W25Q64_H
    3. void W25Q64_Init(void);
    4. void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
    5. void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
    6. void W25Q64_SectorErase(uint32_t Address);
    7. void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);
    8. #endif

    w25q64_ins.h

    1. #ifndef __W25Q64_INS_H
    2. #define __W25Q64_INS_H
    3. #define W25Q64_WRITE_ENABLE 0x06
    4. #define W25Q64_WRITE_DISABLE 0x04
    5. #define W25Q64_READ_STATUS_REGISTER_1 0x05
    6. #define W25Q64_READ_STATUS_REGISTER_2 0x35
    7. #define W25Q64_WRITE_STATUS_REGISTER 0x01
    8. #define W25Q64_PAGE_PROGRAM 0x02
    9. #define W25Q64_QUAD_PAGE_PROGRAM 0x32
    10. #define W25Q64_BLOCK_ERASE_64KB 0xD8
    11. #define W25Q64_BLOCK_ERASE_32KB 0x52
    12. #define W25Q64_SECTOR_ERASE_4KB 0x20
    13. #define W25Q64_CHIP_ERASE 0xC7
    14. #define W25Q64_ERASE_SUSPEND 0x75
    15. #define W25Q64_ERASE_RESUME 0x7A
    16. #define W25Q64_POWER_DOWN 0xB9
    17. #define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
    18. #define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
    19. #define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
    20. #define W25Q64_MANUFACTURER_DEVICE_ID 0x90
    21. #define W25Q64_READ_UNIQUE_ID 0x4B
    22. #define W25Q64_JEDEC_ID 0x9F
    23. #define W25Q64_READ_DATA 0x03
    24. #define W25Q64_FAST_READ 0x0B
    25. #define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
    26. #define W25Q64_FAST_READ_DUAL_IO 0xBB
    27. #define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
    28. #define W25Q64_FAST_READ_QUAD_IO 0xEB
    29. #define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3
    30. #define W25Q64_DUMMY_BYTE 0xFF
    31. #endif

    硬件实现SPI

    硬件实现利用了STM32F103C8T6内部的SPI电路,通过初始化配置对应的IO口,完成SPI操作。

    硬件SPI将字节发送字节上更加简单易懂。所以只需要修改一下SPI内的函数就可以实现。实现了字节的交换,其他指令就不在话下了。

    myspi.c

    1. #include "stm32f10x.h" // Device header
    2. void MySPI_W_SS(uint8_t BitValue)
    3. {
    4. GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
    5. }
    6. void MySPI_Init(void)
    7. {
    8. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    9. RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
    10. GPIO_InitTypeDef GPIO_InitStructure;
    11. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    12. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    13. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    14. GPIO_Init(GPIOA, &GPIO_InitStructure);
    15. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    16. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    17. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    18. GPIO_Init(GPIOA, &GPIO_InitStructure);
    19. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    20. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    21. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    22. GPIO_Init(GPIOA, &GPIO_InitStructure);
    23. SPI_InitTypeDef SPI_InitStructure;
    24. SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置为主SPI
    25. SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //SPI 设置为双线双向全双工
    26. SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //SPI 发送接收 8 位帧结构
    27. SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //数据传输从 MSB 位开始
    28. SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//波特率预分频值为 128
    29. SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //时钟悬空低
    30. SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //数据捕获于第一个时钟沿
    31. SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //内部 NSS 信号有 SSI 位控制
    32. SPI_InitStructure.SPI_CRCPolynomial = 7; //SPI_CRCPolynomial 定义了用于 CRC 值计算的多项式。(不使用)
    33. SPI_Init(SPI1, &SPI_InitStructure);
    34. SPI_Cmd(SPI1, ENABLE);
    35. MySPI_W_SS(1);
    36. }
    37. void MySPI_Start(void)
    38. {
    39. MySPI_W_SS(0);
    40. }
    41. void MySPI_Stop(void)
    42. {
    43. MySPI_W_SS(1);
    44. }
    45. uint8_t MySPI_SwapByte(uint8_t ByteSend)
    46. {
    47. while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);
    48. SPI_I2S_SendData(SPI1, ByteSend);
    49. while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);
    50. return SPI_I2S_ReceiveData(SPI1);
    51. }

    到此对W25Q64的学习到一段落。

    VTI7604

    VTI7604是一款串行SRAM。支持1.8V和3.0V SPI/QPI接口的64Mbit串行SRAM器件。该器件可以配置1位输入/输出接口或4位I/O通用接口。所有必要的刷新操作由器件本身处理。

    8M*8bit=8Mbyte。对8M字节进行寻址需要2^23,共23位,所以寻址时用A[22:0]。

    这次我将学习VTI7064的串行SPI操作

    接口电路

    读芯片手册

    从重置设备初始化的命令序列时序图可以分出VTI7064采用SPI模式0

    接口说明

    4.4介绍到任何操作指令前需要将CE拉高。这里我是有点不理解的,CE#是片选信号,低电平有效,为什么在操作前要拉高,我觉得应该是拉低选中VTI7604。

    4.6命令终止

    所有所有的读取和写都必须由CE#低到高完成。这个CK#上升边可以终止读写激活的字行,并将设备设置为待机状态。不这样做将阻止内部刷新权限,直到设备看到读/写字行终止。命令终止操作不仅需要读写操作和任何命令操作,如输入四元模式命令和重置命令。
    起始条件和终止条件同W25Q64是一样的,后面的时序图可以更好的看出来。

    指令集

    设备上电后默认是SPI模式,但是也可以切换到QPI模式

    5.1 SPI Read Operations(读指令)

    0x03: Serial CMD, Serial IO, slow frequency

    读指令有三种模式,介绍0x03指令。

    1、拉低CE#

    2、发送指令0x03

    3、发送地址

    4、接收数据(在时钟下降沿接收数据)

    快速读0x0B相对于0x03多了8个等待周期,但是最大频率由33M上升到104M

    0xEB是四线SPI,不做讨论

    5.2 SPI Write Operations(写指令)

    5.4 SPI Read ID Operation(读设备ID)

    7.Reset Operation

    上电后默认操作

    8.Input / Output Timing

    10.Code Information:

    VTI7604相对W25Q64来说指令少了很多很多。

    程序代码

    VTI7604.h

    1. #ifndef __VTI7604_H
    2. #define __VTI7604_H
    3. #include "sys.h"
    4. #define VTI7604_CS PBout(12) //VTI7604的片选信号
    5. //指令表
    6. #define VTI_Reset_Enable 0x66
    7. #define VTI_Reset 0x99
    8. #define VTI_Read_ID 0x9F
    9. #define VTI_Write 0x02
    10. #define VTI_Read 0x03
    11. #define VTI_Fast_Read 0x0B
    12. #define VTI_Useless 0xFF
    13. //VTI_CE
    14. #define VTI_CE_GPIO GPIOB
    15. #define VTI_CE_PIN GPIO_Pin_12
    16. //VTI_SO
    17. #define VTI_SO_GPIO GPIOB
    18. #define VTI_SO_PIN GPIO_Pin_14
    19. //VTI_SI
    20. #define VTI_SI_GPIO GPIOB
    21. #define VTI_SI_PIN GPIO_Pin_15
    22. //VTI_SCLK
    23. #define VTI_SCLK_GPIO GPIOB
    24. #define VTI_SCLK_PIN GPIO_Pin_13
    25. #define VTI_CE_SET GPIO_SetBits(VTI_CE_GPIO,VTI_CE_PIN) //片选
    26. #define VTI_CE_Reset GPIO_ResetBits(VTI_CE_GPIO,VTI_CE_PIN)
    27. void SPI2_Init(void);
    28. void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler);
    29. u8 SPI2_ReadWriteByte(u8 TxData);
    30. void VTI7604_Init(void);
    31. u16 VTI7604_ReadID(void);
    32. void VTI7604_Write(u8 vti_da);
    33. u8 VTI7604_Read(void);
    34. void VTI7604_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
    35. void VTI7604_Read_Page(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);
    36. void VTI7604_Fast_Read_Page(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);
    37. #endif

    VTI7604.c

    1. #include "VTI7604.h"
    2. #include "delay.h"
    3. #include "usart.h"
    4. //以下是SPI模块的初始化代码,配置成主机模式
    5. //SPI口初始化
    6. //这里针是对SPI1的初始化
    7. void SPI2_Init(void)
    8. {
    9. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
    10. RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//使能SPI2时钟
    11. //GPIOFB13,14,15初始化设置
    12. GPIO_InitTypeDef GPIO_InitStructure;
    13. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;//PB13~15复用功能输出
    14. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
    15. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
    16. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
    17. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
    18. GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
    19. GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_SPI2); //PB3复用为 SPI2_SCK
    20. GPIO_PinAFConfig(GPIOB,GPIO_PinSource14,GPIO_AF_SPI2); //PB4复用为 SPI2_MISO
    21. GPIO_PinAFConfig(GPIOB,GPIO_PinSource15,GPIO_AF_SPI2); //PB5复用为 SPI2_MOSI
    22. //这里只针对SPI口初始化
    23. // RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,ENABLE);//复位SPI2
    24. // RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,DISABLE);//停止复位SPI2
    25. SPI_InitTypeDef SPI_InitStructure;
    26. SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
    27. SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI工作模式:设置为主SPI
    28. SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //设置SPI的数据大小:SPI发送接收8位帧结构
    29. SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行同步时钟的空闲状态为高电平
    30. SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //串行同步时钟的第二个跳变沿(上升或下降)数据被采样
    31. SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
    32. SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; //定义波特率预分频的值:波特率预分频值为256
    33. SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
    34. SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式
    35. SPI_Init(SPI2, &SPI_InitStructure); //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
    36. SPI_Cmd(SPI2, ENABLE); //使能SPI外设
    37. }
    38. //SPI1速度设置函数
    39. //SPI速度=fAPB2/分频系数
    40. //@ref SPI_BaudRate_Prescaler:SPI_BaudRatePrescaler_2~SPI_BaudRatePrescaler_256
    41. //fAPB2时钟一般为84Mhz:
    42. void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
    43. {
    44. assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性
    45. SPI2->CR1&=0XFFC7;//位3-5清零,用来设置波特率
    46. SPI2->CR1|=SPI_BaudRatePrescaler; //设置SPI1速度
    47. SPI_Cmd(SPI2,ENABLE); //使能SPI1
    48. }
    49. //SPI1 读写一个字节
    50. //TxData:要写入的字节
    51. //返回值:读取到的字节
    52. u8 SPI2_ReadWriteByte(u8 TxData)
    53. {
    54. while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET){}//等待发送区空
    55. SPI_I2S_SendData(SPI2, TxData); //通过外设SPIx发送一个byte 数据
    56. while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET){} //等待接收完一个byte
    57. return SPI_I2S_ReceiveData(SPI2); //返回通过SPIx最近接收的数据
    58. }
    59. //初始化SPI SRAM的IO口
    60. void VTI7604_Init(void)
    61. {
    62. GPIO_InitTypeDef GPIO_InitStructure;
    63. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
    64. //GPIOB12
    65. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;//PB14
    66. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//输出
    67. GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
    68. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
    69. GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
    70. GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
    71. SPI2_Init(); //初始化SPI
    72. SPI2_SetSpeed(SPI_BaudRatePrescaler_2); //设置为42M时钟,高速模式
    73. GPIO_ResetBits(GPIOB,GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15); //设备初始化
    74. delay_ms(1); //150us
    75. SPI2_ReadWriteByte(VTI_Reset_Enable);
    76. delay_ms(1); //tcph
    77. SPI2_ReadWriteByte(VTI_Reset);
    78. VTI_CE_SET; //取消片选,初始化完成
    79. }
    80. //读取设备ID
    81. u16 VTI7604_ReadID(void)
    82. {
    83. u16 Temp = 0;
    84. VTI_CE_Reset;
    85. SPI2_ReadWriteByte(VTI_Read_ID);
    86. SPI2_ReadWriteByte(VTI_Useless);
    87. SPI2_ReadWriteByte(VTI_Useless);
    88. SPI2_ReadWriteByte(VTI_Useless);
    89. Temp|=SPI2_ReadWriteByte(VTI_Useless)<<8; //MF ID
    90. Temp|=SPI2_ReadWriteByte(VTI_Useless); //KGD
    91. VTI_CE_SET; //取消片选,读取完成设备ID
    92. return Temp;
    93. }
    94. void VTI7604_Write(u8 vti_da)
    95. {
    96. VTI_CE_Reset;
    97. SPI2_ReadWriteByte(VTI_Write); //写指令
    98. SPI2_ReadWriteByte(0x00); //写地址
    99. SPI2_ReadWriteByte(0xFF); //写地址
    100. SPI2_ReadWriteByte(0x00); //写地址
    101. SPI2_ReadWriteByte(vti_da); //写入的数据
    102. VTI_CE_SET; //取消片选,读取完成设备ID
    103. }
    104. u8 VTI7604_Read(void)
    105. {
    106. u8 DATA_VT = 0;
    107. VTI_CE_Reset; //片选
    108. SPI2_ReadWriteByte(VTI_Read); //读指令
    109. SPI2_ReadWriteByte(0x00); //读地址
    110. SPI2_ReadWriteByte(0xFF); //读地址
    111. SPI2_ReadWriteByte(0x00); //读地址
    112. DATA_VT=SPI2_ReadWriteByte(VTI_Useless); //读入的数据
    113. VTI_CE_SET; //取消片选,读取完成设备ID
    114. return DATA_VT;
    115. }
    116. void VTI7604_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
    117. {
    118. u16 i;
    119. VTI_CE_Reset; //片选
    120. SPI2_ReadWriteByte(VTI_Write); //写指令
    121. SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); //写地址
    122. SPI2_ReadWriteByte((u8)((WriteAddr)>>8)); //写地址
    123. SPI2_ReadWriteByte((u8)WriteAddr); //写地址
    124. for(i=0;i//循环写数
    125. VTI_CE_SET; //取消片选
    126. }
    127. void VTI7604_Read_Page(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
    128. {
    129. u16 i;
    130. VTI_CE_Reset; //片选
    131. SPI2_ReadWriteByte(VTI_Read); //读指令
    132. SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //读地址
    133. SPI2_ReadWriteByte((u8)((ReadAddr)>>8)); //读地址
    134. SPI2_ReadWriteByte((u8)ReadAddr); //读地址
    135. for(i=0;i
    136. {
    137. pBuffer[i]=SPI2_ReadWriteByte(VTI_Useless); //循环读数
    138. }
    139. VTI_CE_SET; //取消片选
    140. }
    141. void VTI7604_Fast_Read_Page(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
    142. {
    143. u16 i;
    144. VTI_CE_Reset; //片选
    145. SPI2_ReadWriteByte(VTI_Fast_Read); //读指令
    146. SPI2_ReadWriteByte((u8)((ReadAddr)>>16)); //读地址
    147. SPI2_ReadWriteByte((u8)((ReadAddr)>>8)); //读地址
    148. SPI2_ReadWriteByte((u8)ReadAddr); //读地址
    149. for(i=0;i
    150. {
    151. pBuffer[i]=SPI2_ReadWriteByte(VTI_Useless); //循环读数
    152. }
    153. VTI_CE_SET; //取消片选
    154. }

    main.c

    1. #include "stm32f4xx.h" // Device header
    2. #include "delay.h"
    3. #include "usart.h"
    4. #include "VTI7604.h"
    5. const u8 TEXT_Buffer[]={"HK-lyysxt"};
    6. #define SIZE sizeof(TEXT_Buffer)
    7. int main(void)
    8. {
    9. //SRAM
    10. u16 MID = 0;
    11. u8 data_vti =0;
    12. uint32_t VTI_flashsize;
    13. VTI_flashsize = 8 * 1024 * 1024-0*4*1024; /* FLASH 大小为16M字节 */
    14. u8 datatemp[SIZE];
    15. delay_init(168); //初始化延时函数
    16. delay_ms (168);
    17. uart_init(9600);
    18. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    19. uart_init(9600); //初始化串口波特率为9600
    20. VTI7604_Init(); //SRAM
    21. printf("LYYXST\r\n");
    22. MID=VTI7604_ReadID();
    23. printf("MID:%x\r\n",MID);
    24. VTI7604_Write(20);
    25. data_vti=VTI7604_Read();
    26. printf("DATA:%d\r\n",data_vti);
    27. VTI7604_Write_Page((u8*)TEXT_Buffer,VTI_flashsize-100,SIZE); //写数据
    28. VTI7604_Fast_Read_Page(datatemp,VTI_flashsize-100,SIZE); //读数据
    29. printf("DATA:%s\r\n",datatemp);
    30. while(1)
    31. {
    32. }
    33. }

  • 相关阅读:
    《ClickHouse原理解析与应用实践》知识梳理
    Docker-宿主机与容器之间的文件拷贝
    三、NLP中的句子关系判断
    关于一篇ElementUI之CUD+表单验证
    MobaXterm连接报错Network error: Connection timed out
    【Python数据科学快速入门系列 | 07】Matplotlib数据可视化基础入门(二)
    磁盘管理:磁盘调度算法
    小程序如何设置余额充值
    文本标注工具doccano 中上传dataset无法成功
    CFdiv2-The Number of Imposters-(两种点集图上染色问题总结)
  • 原文地址:https://blog.csdn.net/m0_69752994/article/details/133933422