STM32学习开发记录:W25Q16(FLASH)——SPI

选用硬件:STM32F103 Nano开发板 板载有W25Q16


一、SPI协议简介

1、SPI的接线与数据

SPI:Serial Peripheral interface 串行外围设备接口
在这里插入图片描述
上图是SPI主机与从机设备间连线的关系,可以看出,主机主导了主机和从机的时序(SCLK),数据发送的流程是:
主机移位寄存器发送数据→MOSI→从机移位寄存器接收数据
从机移位寄存器发送数据→MISO→主机移位寄存器接收数据
两条线路发送数据是同步的。于是主机和从机的移位寄存器数据被交换

只进行主机写入数据到从机时,主机忽视从机返回的数据;
只进行主机读取从机数据时,主机可发送空数据。

通常规定主机与从机间的数据格式均为MSB(Most Significant Bit 高位数据先行)

此外SPI设备通常还有SS或NSS接口用于片选。每一个从设备都要接到主机的一个SS引脚上。
SS/NSS:低电平有效
通常不会使用硬件SS接口,而是直接软件控制引脚电平实现片选。
通讯开始时,SS端电平拉低,通讯结束时,SS端电平拉高。

2、时序:时钟极性(CPOL)和时钟相位(CPHA)

时钟极性(CPOL)和时钟相位(CPHA)对传输协议的影响如下:

CPOL=0:闲置时时钟信号(SCK)低电位
CPOL=1:闲置时时钟信号(SCK)高电位

CPHA=0:在时钟信号的第一个上升沿/下降沿采样
CPHA=1:在时钟信号的第二个上升沿/下降沿采样在这里插入图片描述
不同模式下SPI的采样与时钟电平关系如下表所示
在这里插入图片描述
对于W25Q16,只支持SPI模式0和模式3。这也是大多数SPI硬件支持的模式。


二、W25Q16简介

1、部分参数

数据内容大小
存储空间2M Byte
页数8192
每页大小256 Byte(0.25KB)
最小扇区4KB
电源供电2.7V~3.6V

W25Q16支持4倍传输模式,在本次学习中暂时不会用到,详情见数据手册。

2、硬件连接

引脚功能:

在这里插入图片描述
CS:片选端输入,发送或接受数据前应先拉低片选端为低电平0,完成后拉高片选端电平为1,读取BUSY位(见下文寄存器)直至BUSY位为0,可继续发送或接受下一条指令。
WP:低电平有效,用于保护状态寄存器。
HOLD:低电平有效,允许芯片暂停工作,保持输出。

开发板的硬件连接:
在这里插入图片描述
W25Q16支持软件Reset(有30μs的延迟),软件启动Dual SPI和Quad SPI 。使用QUAD SPI通信时,需要使用WP引脚和HOLD引脚。在此只学习有关标准SPI通信的方法。

W25Q16内部结构与存储地址示意图如下,W25Q16共2M字节的存储空间被分为32个Block,每个Block(64KB)下有16个Sector(4KB),每个Sector(4KB)下有16个Page(256Byte),在图中的每一行的左侧地址到右侧地址表示一个Page。
在这里插入图片描述
在这里插入图片描述

3、状态寄存器

在此特别说明两个状态寄存器:BUSY和WEL(Write Enable Only)
在这里插入图片描述
BUSY寄存器是只读寄存器,在对存储空间操作(包括页写入,块擦除等)时,BUSY寄存器保持在高电平位,此时芯片无法执行新的指令。完成操作后,BUSY寄存器重置为低电平,芯片可继续执行指令。
实际使用中可以用BUSY寄存器来判断指令是否执行完毕。

WEL寄存器同样是只读寄存器。当寄存器至为高电平时,可以执行对存储内容进行更改(包括页写入,块擦除等)的命令。置为低电平时,不能更改存储内容。

其它寄存器用于对数据的保护,通常不需要使用。需要使用时可查看数据手册的说明。

4、指令API

W25Q16数据传输为MSB模式,传入数据在上升沿采样。因此应使用SPI MODE3

The first byte of data clocked into the DI input provides the instruction code. Data on the DI input is sampled on the rising edge of clock with most significant bit (MSB) first.

芯片支持volatile数据写入,很多指令出于保护目的而设计,但在开发板上不需要。具体有待进一步学习。
预设的操作指令如下图所示:
在这里插入图片描述
在这里插入图片描述
对指令的一些说明:

(1) 芯片一次写入数据最多支持256Byte。超出256Byte的数据将覆盖前面已经写入的数据。

(2) 标准SPI模式下输入可基本参照上表,双路SPI或四路SPI模式下需要参考数据手册的说明。上表中带括号“()”的指令在双路SPI和四路SPI模式下有不同的输入方式,具体参照数据手册。

(3) 上表中【ID15-ID0】或【ID7-ID0】以及【MF7-MF0】对应第一个表中的设备地址,A表示地址,D表示数据,S表示寄存器,L表示Locked Value(具体见数据手册)。

(4) Write Enable/Disable 写使能/禁止
在页写入,扇区擦除,块擦除,全片擦除命令前必须使用的命令。使WEL位置1。

(5) Read/Write Status Register-1/2/3 读取/写入寄存器1/2/3的状态
上文中只介绍了Status Register-1的内容,实际使用中基本也只需要用到其中的BUSY和WEL两个状态寄存器,主要是读取寄存器的状态用于判断指令是否完成,或改变写入权限(通常通过写使能命令)。
对于读取状态寄存器的命令,当CS(片选端)未置高电平时将一直返回寄存器的状态。CS置1后命令结束。
在这里插入图片描述

(6) Read Data 读取数据
读取数据命令能够从发送的地址开始不断地读取数据直至CS置高电平1,能够读取至整个芯片。
此外还有作用相近的Fast Read 命令,在3个地址字节后加dummy字节,用于调整时钟速度,再进行数据的发送。具体没有进行实验,暂时只使用Read Data命令。
在这里插入图片描述

(7) Page Program 页写入
页写入命令必须在写使能(WEL=1)的条件下才有效。
页写入命令中的地址数据末八位(A7-A0)应当为00h,此时的地址是页的首地址,能够完整地将数据写入页中。
若地址末八位不是00h,则当输入的数据地址自增至页尾时,地址将回到页首继续写入。
写入数据的位置必须是擦出状态(0xFF),否则写入失败。
若写入的数据超出256字节,则超出的字节将覆盖写入数据的首部分字节。
完成数据写入后,应将CS置1才能使命令有效。此后芯片需要等待一段时间t才能完成数据的写入。写入过程中可用Read Status Register 读取BUSY寄存器查看命令是否完成。写入命令完成后,写使能自动被禁止(WEL=0)。

(8)
Sector Erase 扇区擦除(4KB)
32KB Block Erase 半块擦除
64KB Block Erase 整块擦除
Chip Erase 全片擦除

这几个命令与页写入命令相似,同样需要在WEL=1的条件下才可使用。命令执行过程中可通过查看BUSY标志位确认命令是否完成,命令完成后写使能自动被禁止,即WEL=0。
被擦除的数据将被全部置为1,即FFh。


三、STM32 HAL库 有关SPI的API

1.SPI初始化结构体

初始化结构体的可控参数如下所示。

typedef struct
{
	uint32_t Mode; //模式:主(SPI_MODE_MASTER),从(SPI_MODE_SLAVE)
	uint32_t Direction; //方式: 只接收模式,单线双向通信数据模式,全双工
	uint32_t DataSize; //8 位还是 16 位帧格式选择项
	uint32_t CLKPolarity; //时钟极性
	uint32_t CLKPhase;//时钟相位
	uint32_t NSS; //SS 信号由硬件(NSS 管脚)还是软件控制
	uint32_t BaudRatePrescaler;//设置 SPI 波特率预分频值
	uint32_t FirstBit;//起始位是 MSB 还是 LSB
	uint32_t TIMode; //帧格式 SPI motorola 模式还是 TI 模式
	uint32_t CRCCalculation; //硬件 CRC 是否使能
	uint32_t CRCPolynomial; //CRC 多项式
}SPI_InitTypeDef;

各个参数具体的预设值可参考官方数据手册。以下是根据W25Q16数据手册进行的初始化预设

	SPI2_Handler.Instance=SPI2; //SPI2
	SPI2_Handler.Init.Mode=SPI_MODE_MASTER; //设置为主模式
	SPI2_Handler.Init.Direction=SPI_DIRECTION_2LINES; //SPI 设置为双线模式
	SPI2_Handler.Init.DataSize=SPI_DATASIZE_8BIT; //SPI 发送接收 8 位帧结构
	SPI2_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //串行同步时钟的空闲状态为高电平
	SPI2_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; //第二个跳变沿数据被采样
	SPI2_Handler.Init.NSS=SPI_NSS_SOFT;//NSS 信号由硬件管理
	SPI2_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256
	;//定义波特率预分频的值:波特率预分频值为 256
	SPI2_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //数据传输从 MSB 位开始
	SPI2_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //关闭 TI 模式
	SPI2_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;//关闭硬件 CRC
	SPI2_Handler.Init.CRCPolynomial=7; //CRC 值计算的多项式
	HAL_SPI_Init(&SPI2_Handler);//初始化

注:
SPI模式中还支持TI模式,TI模式又称SSP模式,是由TI公司定义的通讯标准,关闭TI模式下的SPI是由Motorola公司定义的通讯标准。通常使用的是Motorola公司定义的SPI通讯模式。
CRC:CRC校验
用STM32CubeMX配置引脚时,要设置SYS的Debug模式为Serial Wire,以免下载程序一次后无法读取调试器。

  • 9
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
stm32f103标准库是一种在STM32F103系列微控制器上进行开发的库文件。该库文件提供了丰富的函数和接口,可以方便地进行外设的控制与操作。在使用stm32f103标准库进行开发时,可以利用该库来实现对W25Q128闪存芯片的读写操作。 下面是一个示例的程序源码,演示了如何使用stm32f103标准库来实现W25Q128的读写功能: #include "stm32f10x.h" #include "stm32f10x_spi.h" #include "stm32f10x_gpio.h" #define SPI1_CS_PIN GPIO_Pin_4 #define SPI1_CS_PORT GPIOA #define SPI1_MOSI_PIN GPIO_Pin_5 #define SPI1_MOSI_PORT GPIOA #define SPI1_MISO_PIN GPIO_Pin_6 #define SPI1_MISO_PORT GPIOA #define SPI1_SCK_PIN GPIO_Pin_7 #define SPI1_SCK_PORT GPIOA void SPI1_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; // 配置SPI引脚 GPIO_InitStructure.GPIO_Pin = SPI1_CS_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(SPI1_CS_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = SPI1_MISO_PIN | SPI1_SCK_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(SPI1_MISO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = SPI1_MOSI_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(SPI1_MOSI_PORT, &GPIO_InitStructure); // 配置SPI SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); } void W25Q128_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead) { GPIO_ResetBits(SPI1_CS_PORT, SPI1_CS_PIN); // 使能闪存芯片 SPI1->DR = 0x03; // 发送读命令 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI1->DR = (ReadAddr & 0xFF0000) >> 16; // 发送地址 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI1->DR = (ReadAddr & 0xFF00) >> 8; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI1->DR = ReadAddr & 0xFF; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); while (NumByteToRead--) { SPI1->DR = 0xFF; // 发送读数据命令 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); *pBuffer = SPI1->DR; pBuffer++; } GPIO_SetBits(SPI1_CS_PORT, SPI1_CS_PIN); // 禁能闪存芯片 } void W25Q128_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite) { GPIO_ResetBits(SPI1_CS_PORT, SPI1_CS_PIN); // 使能闪存芯片 SPI1->DR = 0x06; // 发送写使能命令 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); GPIO_SetBits(SPI1_CS_PORT, SPI1_CS_PIN); // 禁能闪存芯片 GPIO_ResetBits(SPI1_CS_PORT, SPI1_CS_PIN); // 使能闪存芯片 SPI1->DR = 0x02; // 发送写命令 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI1->DR = (WriteAddr & 0xFF0000) >> 16; // 发送地址 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI1->DR = (WriteAddr & 0xFF00) >> 8; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI1->DR = WriteAddr & 0xFF; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); while (NumByteToWrite--) { SPI1->DR = *pBuffer; // 发送写数据命令 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); pBuffer++; } GPIO_SetBits(SPI1_CS_PORT, SPI1_CS_PIN); // 禁能闪存芯片 } int main(void) { // 初始化SPI SPI1_Configuration(); // 读取数据 uint32_t addr = 0x000000; uint8_t buffer[60]; W25Q128_Read(buffer, addr, sizeof(buffer)); // 写入数据 uint8_t data_to_write[] = "Hello, World!"; uint16_t size = sizeof(data_to_write) - 1; W25Q128_Write(data_to_write, addr, size); while(1); return 0; } 上述程序实现了对W25Q128闪存芯片的读写功能。通过配置SPI1来连接STM32F103与W25Q128,在读取数据时使用了读命令和地址,读取完数据后将其存储在指定的缓冲区中。在写入数据时同样使用了写命令和地址,然后逐个字节地将数据写入闪存芯片。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值