从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位时循环即可。

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

上面代码是江科大的源码

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

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

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

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
#include "usart.h"uint8_t MID;
uint16_t DID;uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04};
uint8_t ArrayRead[4];int main(void)
{OLED_Init();W25Q64_Init();uart_init(9600);printf("数字:1\r\n");OLED_ShowString(1, 1, "MID:   DID:");OLED_ShowString(2, 1, "W:");OLED_ShowString(3, 1, "R:");W25Q64_ReadID(&MID, &DID);OLED_ShowHexNum(1, 5, MID, 2);OLED_ShowHexNum(1, 12, DID, 4);printf("MID:%x,DID:%x",MID,DID);W25Q64_SectorErase(0x000000);W25Q64_PageProgram(0x000000, ArrayWrite, 4);W25Q64_ReadData(0x000000, ArrayRead, 4);OLED_ShowHexNum(2, 3, ArrayWrite[0], 2);OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);OLED_ShowHexNum(3, 3, ArrayRead[0], 2);OLED_ShowHexNum(3, 6, ArrayRead[1], 2);OLED_ShowHexNum(3, 9, ArrayRead[2], 2);OLED_ShowHexNum(3, 12, ArrayRead[3], 2);while (1){}
}

myspi.c

#include "stm32f10x.h"                  // Device headervoid MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}void MySPI_W_SCK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}void MySPI_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}uint8_t MySPI_R_MISO(void)
{return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}void MySPI_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);MySPI_W_SS(1);		//模式0		片选信号为高电平,不选中MySPI_W_SCK(0);		//模式0		时钟信号空闲状态为低电平
}void MySPI_Start(void)
{MySPI_W_SS(0);
}void MySPI_Stop(void)
{MySPI_W_SS(1);
}uint8_t MySPI_SwapByte(uint8_t ByteSend)
{uint8_t i, ByteReceive = 0x00;for (i = 0; i < 8; i ++){MySPI_W_MOSI(ByteSend & (0x80 >> i));					//单片机发送数据,W25Q64读入MySPI_W_SCK(1);											//产生上升沿if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	//单片机读入数据,W25Q64发送MySPI_W_SCK(0);											//产生下降沿}return ByteReceive;
}

myspi.h

#ifndef __MYSPI_H
#define __MYSPI_Hvoid MySPI_Init(void);
void MySPI_Start(void);
void MySPI_Stop(void);
uint8_t MySPI_SwapByte(uint8_t ByteSend);#endif

w25q64.c

#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"void W25Q64_Init(void)
{MySPI_Init();
}void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{MySPI_Start();MySPI_SwapByte(W25Q64_JEDEC_ID);*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);*DID <<= 8;*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);MySPI_Stop();
}void W25Q64_WriteEnable(void)
{MySPI_Start();MySPI_SwapByte(W25Q64_WRITE_ENABLE);MySPI_Stop();
}void W25Q64_WaitBusy(void)
{uint32_t Timeout;MySPI_Start();MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);Timeout = 100000;while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01){Timeout --;if (Timeout == 0){break;}}MySPI_Stop();
}void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{uint16_t i;W25Q64_WriteEnable();MySPI_Start();MySPI_SwapByte(W25Q64_PAGE_PROGRAM);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);for (i = 0; i < Count; i ++){MySPI_SwapByte(DataArray[i]);}MySPI_Stop();W25Q64_WaitBusy();
}void W25Q64_SectorErase(uint32_t Address)
{W25Q64_WriteEnable();MySPI_Start();MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);MySPI_Stop();W25Q64_WaitBusy();
}void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{uint32_t i;MySPI_Start();MySPI_SwapByte(W25Q64_READ_DATA);MySPI_SwapByte(Address >> 16);MySPI_SwapByte(Address >> 8);MySPI_SwapByte(Address);for (i = 0; i < Count; i ++){DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);}MySPI_Stop();
}

w25q64.h

#ifndef __W25Q64_H
#define __W25Q64_Hvoid W25Q64_Init(void);
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);
void W25Q64_SectorErase(uint32_t Address);
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);#endif

w25q64_ins.h

#ifndef __W25Q64_INS_H
#define __W25Q64_INS_H#define W25Q64_WRITE_ENABLE							0x06
#define W25Q64_WRITE_DISABLE						0x04
#define W25Q64_READ_STATUS_REGISTER_1				0x05
#define W25Q64_READ_STATUS_REGISTER_2				0x35
#define W25Q64_WRITE_STATUS_REGISTER				0x01
#define W25Q64_PAGE_PROGRAM							0x02
#define W25Q64_QUAD_PAGE_PROGRAM					0x32
#define W25Q64_BLOCK_ERASE_64KB						0xD8
#define W25Q64_BLOCK_ERASE_32KB						0x52
#define W25Q64_SECTOR_ERASE_4KB						0x20
#define W25Q64_CHIP_ERASE							0xC7
#define W25Q64_ERASE_SUSPEND						0x75
#define W25Q64_ERASE_RESUME							0x7A
#define W25Q64_POWER_DOWN							0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE				0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET			0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID		0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID				0x90
#define W25Q64_READ_UNIQUE_ID						0x4B
#define W25Q64_JEDEC_ID								0x9F
#define W25Q64_READ_DATA							0x03
#define W25Q64_FAST_READ							0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT				0x3B
#define W25Q64_FAST_READ_DUAL_IO					0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT				0x6B
#define W25Q64_FAST_READ_QUAD_IO					0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO				0xE3#define W25Q64_DUMMY_BYTE							0xFF#endif

硬件实现SPI

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

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

myspi.c

#include "stm32f10x.h"                  // Device headervoid MySPI_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}void MySPI_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);SPI_InitTypeDef SPI_InitStructure;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;						//设置为主SPISPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//SPI 设置为双线双向全双工SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;					//SPI 发送接收 8 位帧结构SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;					//数据传输从 MSB 位开始SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;//波特率预分频值为 128SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;							//时钟悬空低SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;						//数据捕获于第一个时钟沿SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;							//内部 NSS 信号有 SSI 位控制SPI_InitStructure.SPI_CRCPolynomial = 7;							//SPI_CRCPolynomial 定义了用于 CRC 值计算的多项式。(不使用)SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);MySPI_W_SS(1);
}void MySPI_Start(void)
{MySPI_W_SS(0);
}void MySPI_Stop(void)
{MySPI_W_SS(1);
}uint8_t MySPI_SwapByte(uint8_t ByteSend)
{while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);SPI_I2S_SendData(SPI1, ByteSend);while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);return SPI_I2S_ReceiveData(SPI1);
}

到此对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来说指令少了很多很多。

程序代码

目前pcb没有到手电路没焊接,代码没写。写完测试好后会发布。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/116288.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【STM32】时钟设置函数(寄存器版)

一、STM32时钟设置函数移植 1.时钟模块回顾 一个疑问 前面代码并没有设置时钟为什么可以直接使用。 2.时钟树 3.时钟树分析 1.内部晶振&#xff08;HSI&#xff09; 内部晶振不稳定&#xff0c;当我们上电后&#xff0c;会自动产生振动&#xff0c;自动产生时钟&#xff0c;…

【java爬虫】使用selenium获取某交易所公司半年报数据

引言 上市公司的财报数据一般都会进行公开&#xff0c;我们可以在某交易所的官方网站上查看这些数据&#xff0c;由于数据很多&#xff0c;如果只是手动收集的话可能会比较耗时耗力&#xff0c;我们可以采用爬虫的方法进行数据的获取。 本文就介绍采用selenium框架进行公司财…

2023年10月24日程序员节

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

基于Tucker分解的时序知识图谱补全10.23

基于Tucker分解的时序知识图谱补全 摘要引言相关工作静态知识图谱补全时序知识图谱补全 背景提出的模型学习时间复杂度和参数增长表达能力分析 实验 摘要 知识图谱已被证明是众多智能应用的有效工具。然而&#xff0c;大量有价值的知识仍然隐含在知识图谱中。为了丰富现有的知…

[python 刷题] 19 Remove Nth Node From End of List

[python 刷题] 19 Remove Nth Node From End of List 题目&#xff1a; Given the head of a linked list, remove the nth node from the end of the list and return its head. 题目说的是就是移除倒数第 n 个结点&#xff0c;如官方给的案例&#xff1a; 这里提供的 n 就是…

实时配送跟踪功能的实现:外卖跑腿小程序的技术挑战

在当今数字化时代&#xff0c;外卖和跑腿服务已经成为了生活中不可或缺的一部分。为了提供更好的用户体验&#xff0c;外卖跑腿小程序越来越注重实时配送跟踪功能的实现。这项技术挑战旨在确保顾客可以方便地跟踪他们的订单&#xff0c;以及配送员可以高效地完成送货任务。本文…

经典卷积神经网络 - VGG

使用块的网络 - VGG。 使用多个 3 3 3\times 3 33的要比使用少个 5 5 5\times 5 55的效果要好。 VGG全称是Visual Geometry Group&#xff0c;因为是由Oxford的Visual Geometry Group提出的。AlexNet问世之后&#xff0c;很多学者通过改进AlexNet的网络结构来提高自己的准确…

TDengine小知识-数据文件命名规则

TDengine 时序数据库对数据文件有自己的命名规则&#xff0c;文件名中包含了vnodeID、时间范围、版本、文件类型等多种信息。了解数据文件命名规则&#xff0c;可以让运维工作更简单。 废话不多说&#xff0c;直接上图&#xff1a; v4&#xff1a;文件所属 Vgroup 组&#xf…

leetcode:2347. 最好的扑克手牌(python3解法)

难度&#xff1a;简单 给你一个整数数组 ranks 和一个字符数组 suit 。你有 5 张扑克牌&#xff0c;第 i 张牌大小为 ranks[i] &#xff0c;花色为 suits[i] 。 下述是从好到坏你可能持有的 手牌类型 &#xff1a; "Flush"&#xff1a;同花&#xff0c;五张相同花色的…

安装visual studio报错“无法安装msodbcsql“

在安装visual studio2022时安装完成后提示无法安装msodbcsql, 查看日志文件详细信息提示&#xff1a;指定账户已存在。 未能安装包“msodbcsql,version17.2.30929.1,chipx64,languagezh-CN”。 搜索 URL https://aka.ms/VSSetupErrorReports?qPackageIdmsodbcsql;PackageActi…

用matlab求解线性规划

文章目录 1、用单纯形表求解线性规划绘制单纯形表求解&#xff1a; 2、用matlab求解线性规划——linprog()函数问题&#xff1a;补充代码&#xff1a;显示出完整的影子价格向量 1、用单纯形表求解线性规划 求解线性规划 m i n − 3 x 1 − 4 x 2 x 3 min -3x_1-4x_2x_3 min−…

【ArcGIS模型构建器】04:根据矢量范围批量裁剪影像栅格数据

本文以中国2000-2010-2020年3期GLC30土地覆盖数据为例,演示用模型构建器批量裁剪出四川省3年的数据。 文章目录 一、结果预览二、模型构建三、运行模型四、注意事项一、结果预览 用四川省行政区数据裁剪出的3年Globeland30(配套实验数据data04.rar中有三年中国区域成品数据)…

Java编写图片转base64

图片转成base64 url &#xff0c; 在我们的工作中也会经常用到&#xff0c;比如说导出 word,pdf 等功能&#xff0c;今天我们尝试写一下。 File file new File("");byte[] data null;InputStream in null;ByteArrayOutputStream out null;try{URL url new URL(&…

NAS搭建指南三——私人云盘

一、私人云盘选择 我选择的是可道云进行私人云盘的搭建可道云官网地址可道云下载地址&#xff0c;下载服务器端和 Windows 客户端可道云官方文档 二、环境配置 PHP 与 MySQL 环境安装&#xff1a;XAMPP 官网地址 下载最新的 windows 版本 安装时只勾选 MySQL 与 PHP相关即可…

信号继电器驱动芯片(led驱动芯片)

驱动继电器需要配合BAV99&#xff08;防止反向脉冲&#xff09;使用 具体应用参考开源项目 电阻箱 sbstnh/programmable_precision_resistor: A SCPI programmable precision resistor (github.com) 这个是芯片的输出电流设置 对应到上面的实际开源项目其设置电阻为1.5K&…

侯捷C++面向对象程序设计笔记(上)-Object Based(基于对象)部分

基于对象就是对于单一class的设计。 对于有指针的&#xff1a;complex.h complex-test.cpp 对于没有指针的&#xff1a; string.h string-test.cpp https://blog.csdn.net/ncepu_Chen/article/details/113843775?spm1001.2014.3001.5501#commentBox 没有指针成员——以复数co…

力扣第55题 跳跃游戏 c++ 贪心 + 覆盖 加暴力超时参考

题目 55. 跳跃游戏 中等 相关标签 贪心 数组 动态规划 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &…

【RocketMQ系列十二】RocketMQ集群核心概念之主从复制生产者负载均衡策略消费者负载均衡策略

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精…

自动驾驶之—车道线感知

零、前言 &#xff1a; 最近在学习自动驾驶方向的东西&#xff0c;简单整理一些学习笔记&#xff0c;学习过程中发现宝藏up 手写AI 一、视觉系统坐标系 视觉系统一共有四个坐标系&#xff1a;像素平面坐标系&#xff08;u,v&#xff09;、图像坐标系&#xff08;x,y&#xff09…

开源WAF--Safeline(雷池)测试手册

长亭科技—雷池(SafeLine)社区版 官方网站:长亭雷池 WAF 社区版 (chaitin.cn) WAF 工作在应用层&#xff0c;对基于 HTTP/HTTPS 协议的 Web 系统有着更好的防护效果&#xff0c;使其免于受到黑客的攻击 1.1 雷池的搭建 1.1.1 配置需求 操作系统&#xff1a;Linux 指令架构&am…