参考资料
- STM32F103参考手册:rm0008-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx
- STM32103ZE数据手册:STM32F103xC, STM32F103xD, STM32F103xE
- W25Q32JV Datasheet
SPI概述
物理层
协议层
主机和从机之间的数据交换
时钟的极性和相位
案例-W25Q32
W25Q32介绍
W25Q32是一种使用SPI通讯协议的NOR FLASH存储器,它的CLK/DI/DO引脚分别连接到了STM32对应的SPI引脚SCK/MOSI/MISO上,其中STM32的NSS引脚虽然是其片上SPI外设的硬件引脚,但实际上后面的程序只是把它当成一个普通的GPIO,使用软件的方式控制NSS信号,所以在SPI的硬件设计中,NSS可以随便选择普通的GPIO,不必纠结于选择硬件NSS信号。
FLASH 芯片中还有WP和HOLD引脚。WP引脚可控制写保护功能,当该引脚为低电平时,禁止写入数据。我们直接接电源,不使用写保护功能。HOLD引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。我们直接接电源,不使用通讯暂停功能。
(1)这个flash芯片只支持模式0和模式3。
(2)写的时候必须是先擦除,擦除后再写入。
(3)移位是高位优先。
W25Q32框图
4M-byte存储空间,所需寻址bit为22,为了8bit对齐,采用24bit编址
- [23:16]:其中高2位没有用,低6位用于Block寻址,对应64个Block
- [15:12]:4个bit用于Sector寻址,对应16个Sector
- [11:8]:4个bit用于Page寻址,对应一个Sector中的16个Page
- [7:0]:8个bit用于页内字节寻址,对应一个Page中的256个字节
写入操作注意事项
(1)写入操作前,必须先进行写使能。
(2)每个数据位只能由1改写为0,不能由0改写为1。
(3)写入数据前必须先檫除,檫除后,所有数据位变为1。擦除必须按最小擦除单元进行。
(4)连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入。
(5)写入操作结束后,芯片进入忙状态,不响应新的读写操作。
写使能
页写
忙状态
读取操作注意事项
(1)直接调用读取时序,无需读使能,无需额外操作,没有页的限制。
(2)读取操作结束后不会进入忙状态,但不能在忙状态时读取。
电气特性&时序要求分析
如图3.3V工作电压条件下,SCK时钟频率最高可达133MHz(非03h指令,03h指令则为50MHz)。对于STM32F103而言,GPIO输出的最大频率为50MHz(如下图)。
其他时序要求参数大都在10ns以内,而STM32F103的主频为72MHz,时钟周期约为13.8ns,因此大多数情况下都不需要刻意延时。
对于擦除和页写相关操作,则需要增加一定的延时来等待芯片内部完成相应的操作。
实验-读取厂商ID和设备ID
spi.h
#ifndef __SPI_H__
#define __SPI_H__
#include "stm32f10x.h"
#include "delay.h"
// CS PC13
#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
// SCK PA5
#define SCK_HIGH (GPIOA->ODR |= GPIO_ODR_ODR5)
#define SCK_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR5)
// MOSI PA7
#define MOSI_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR7)
#define MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)
// MISO PA6
#define MISO_READ ((GPIOA->IDR & GPIO_IDR_IDR6) ? 1 : 0)
void SPI_Init(void);
void SPI_Start(void);
void SPI_Stop(void);
uint8_t SPI_SwapByte(uint8_t dataByte);
#endif /* __SPI_H__ */
spi.c
#include "spi.h"
void SPI_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
// CS-PC13 和 SCK-PA5,MOSI-PA7 通用推挽 MODE=11 CNF=00
GPIOC->CRH |= GPIO_CRH_MODE13;
GPIOC->CRH &= ~GPIO_CRH_CNF13;
GPIOA->CRL |= GPIO_CRL_MODE5;
GPIOA->CRL &= ~GPIO_CRL_CNF5;
GPIOA->CRL |= GPIO_CRL_MODE7;
GPIOA->CRL &= ~GPIO_CRL_CNF7;
// MISO-PA6 浮空输入 MODE=00, CNF=01
GPIOA->CRL &= ~GPIO_CRL_MODE6;
GPIOA->CRL |= GPIO_CRL_CNF6_0;
GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
// SPI模式0(极性=0,相位=0) 空闲状态 CS拉高(低电平使能),SCK拉低
CS_HIGH;
SCK_LOW;
}
void SPI_Start(void) { CS_LOW; }
void SPI_Stop(void) { CS_HIGH; }
uint8_t SPI_SwapByte(uint8_t dataByte) {
uint8_t readByte = 0;
for (uint8_t i = 0; i < 8; i++) {
// 主设备准备数据到输出线MOSI上
if (dataByte & 0x80) {
MOSI_HIGH;
} else {
MOSI_LOW;
}
dataByte <<= 1;
// 主设备拉高SCK(双方发送数据阶段),让从设备读取数据
SCK_HIGH;
// 主设备读取从设备的数据
readByte <<= 1;
if (MISO_READ) {
readByte |= 0x01;
}
// 主设备拉低SCK(双方准备数据阶段),让从设备准备下一个数据
SCK_LOW;
}
return readByte;
}
w25q32.h
#ifndef __W25Q32_H__
#define __W25Q32_H__
#include "spi.h"
void W25Q32_Init(void);
void W25Q32_ReadID(uint8_t *manufactureID, uint16_t *deviceID);
#endif /* __W25Q32_H__ */
w25q32.c
#include "w25q32.h"
void W25Q32_Init(void) {
SPI_Init();
}
void W25Q32_ReadID(uint8_t *manufactureId, uint16_t *deviceId) {
// 片选使能
SPI_Start();
// 发送Read JEDEC ID指令9Fh,读取生产厂商和设备ID
SPI_SwapByte(0x9F);
// 读取从设备响应数据
*manufactureId = SPI_SwapByte(0);
*deviceId |= (SPI_SwapByte(0) << 8) & 0xFF00;
*deviceId |= SPI_SwapByte(0) & 0x00FF;
// 片选失能
SPI_Stop();
}
logger
逻辑分析
实验-擦除&页写&连续读
等待BUSY状态
void W25Q32_WaitNotBusy(void) {
SPI_Start();
// 发送读取状态寄存器指令
SPI_SwapByte(0x05);
// 轮询接收从设备响应的状态字节,直到其中的最低位为0
while (SPI_SwapByte(0) & 0x01) {
}
SPI_Stop();
}
擦除扇区
void W25Q32_WriteEnable(void) {
SPI_Start();
SPI_SwapByte(0x06);
SPI_Stop();
}
void W25Q32_WriteDisable(void) {
SPI_Start();
SPI_SwapByte(0x04);
SPI_Stop();
}
void W25Q32_SectorErase(uint8_t block, uint8_t sector) {
// 执行擦除前必须等待BUSY状态和开启写使能
W25Q32_WaitNotBusy();
W25Q32_WriteEnable();
SPI_Start();
SPI_SwapByte(0x20);
// 发送24bit地址数据,高8位[23:16]为block地址,[15:12]为sector地址
uint32_t addr = (block << 16) | ((sector & 0x0F) << 12);
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
SPI_Stop();
W25Q32_WriteDisable();
}
页写
void W25Q32_WritePage(uint8_t block, uint8_t sector, uint8_t page,
uint8_t innerAddr, uint8_t *data, uint16_t size) {
// 执行页写前必须等待BUSY状态和开启写使能
W25Q32_WaitNotBusy();
W25Q32_WriteEnable();
SPI_Start();
SPI_SwapByte(0x02);
// 发送24bit地址数据,高8位[23:16]为block地址,[15:12]为sector地址
uint32_t addr = (block << 16) | ((sector & 0x0F) << 12) |
((page & 0x0F) << 8) | innerAddr;
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
// 发送待写入数据
for (uint8_t i = 0; i < size; i++) {
SPI_SwapByte(data[i]);
}
SPI_Stop();
W25Q32_WriteDisable();
}
连续读
void W25Q32_ReadBytes(uint8_t block, uint8_t sector, uint8_t page,
uint8_t innerAddr, uint8_t *buf, uint16_t size) {
// 读取数据前等待BUSY状态
W25Q32_WaitNotBusy();
SPI_Start();
SPI_SwapByte(0x03);
// 发送24bit地址数据,高8位[23:16]为block地址,[15:12]为sector地址
uint32_t addr = (block << 16) | ((sector & 0x0F) << 12) |
((page & 0x0F) << 8) | innerAddr;
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
// 发送待写入数据
for (uint16_t i = 0; i < size; i++) {
buf[i] = SPI_SwapByte(0);
}
SPI_Stop();
}
完整代码
w25q32.h
#ifndef __W25Q32_H__
#define __W25Q32_H__
#include "spi.h"
void W25Q32_Init(void);
void W25Q32_ReadID(uint8_t *manufactureID, uint16_t *deviceID);
void W25Q32_WaitNotBusy(void);
void W25Q32_WriteEnable(void);
void W25Q32_WriteDisable(void);
void W25Q32_SectorErase(uint8_t block, uint8_t sector);
void W25Q32_WritePage(uint8_t block, uint8_t sector, uint8_t page, uint8_t innerAddr, uint8_t *data, uint16_t size);
void W25Q32_ReadBytes(uint8_t block, uint8_t sector, uint8_t page, uint8_t innerAddr, uint8_t *buf, uint16_t size);
#endif /* __W25Q32_H__ */
w25q32.h
#include "w25q32.h"
void W25Q32_Init(void) { SPI_Init(); }
void W25Q32_ReadID(uint8_t *manufactureId, uint16_t *deviceId) {
// 片选使能
SPI_Start();
// 发送Read JEDEC ID指令9Fh,读取生产厂商和设备ID
SPI_SwapByte(0x9F);
// 读取从设备响应数据
*manufactureId = SPI_SwapByte(0);
*deviceId |= (SPI_SwapByte(0) << 8) & 0xFF00;
*deviceId |= SPI_SwapByte(0) & 0x00FF;
// 片选失能
SPI_Stop();
}
void W25Q32_WaitNotBusy(void) {
SPI_Start();
// 发送读取状态寄存器指令
SPI_SwapByte(0x05);
// 轮询接收从设备响应的状态字节,直到其中的最低位为0
while (SPI_SwapByte(0) & 0x01) {
}
SPI_Stop();
}
void W25Q32_WriteEnable(void) {
SPI_Start();
SPI_SwapByte(0x06);
SPI_Stop();
}
void W25Q32_WriteDisable(void) {
SPI_Start();
SPI_SwapByte(0x04);
SPI_Stop();
}
void W25Q32_SectorErase(uint8_t block, uint8_t sector) {
// 执行擦除前必须等待BUSY状态和开启写使能
W25Q32_WaitNotBusy();
W25Q32_WriteEnable();
SPI_Start();
SPI_SwapByte(0x20);
// 发送24bit地址数据,高8位[23:16]为block地址,[15:12]为sector地址
uint32_t addr = (block << 16) | ((sector & 0x0F) << 12);
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
SPI_Stop();
W25Q32_WriteDisable();
}
void W25Q32_WritePage(uint8_t block, uint8_t sector, uint8_t page,
uint8_t innerAddr, uint8_t *data, uint16_t size) {
// 执行页写前必须等待BUSY状态和开启写使能
W25Q32_WaitNotBusy();
W25Q32_WriteEnable();
SPI_Start();
SPI_SwapByte(0x02);
// 发送24bit地址数据,高8位[23:16]为block地址,[15:12]为sector地址
uint32_t addr = (block << 16) | ((sector & 0x0F) << 12) |
((page & 0x0F) << 8) | innerAddr;
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
// 发送待写入数据
for (uint8_t i = 0; i < size; i++) {
SPI_SwapByte(data[i]);
}
SPI_Stop();
W25Q32_WriteDisable();
}
void W25Q32_ReadBytes(uint8_t block, uint8_t sector, uint8_t page,
uint8_t innerAddr, uint8_t *buf, uint16_t size) {
// 读取数据前等待BUSY状态
W25Q32_WaitNotBusy();
SPI_Start();
SPI_SwapByte(0x03);
// 发送24bit地址数据,高8位[23:16]为block地址,[15:12]为sector地址
uint32_t addr = (block << 16) | ((sector & 0x0F) << 12) |
((page & 0x0F) << 8) | innerAddr;
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
// 发送待写入数据
for (uint16_t i = 0; i < size; i++) {
buf[i] = SPI_SwapByte(0);
}
SPI_Stop();
}
main.c
#include "stm32f10x.h"
#include "uart.h"
#include "logger.h"
#include "w25q32.h"
void uart1_received_callback(uint8_t buf[], uint8_t size) {
}
int main() {
uart_init();
W25Q32_Init();
LOG_DEBUG("main start")
uint8_t manufactureId = 0;
uint16_t deviceId = 0;
W25Q32_ReadID(&manufactureId, &deviceId);
// LOG_DEBUG("manufacture = %#x, deviceId = %#x", manufactureId, deviceId)
// 擦除扇区(第63个block,第2个sector)
W25Q32_SectorErase(63, 2);
// 向 block-63 sector-2 page-10 addr-0写入数据
W25Q32_WritePage(63, 2, 10, 0, (uint8_t *)"12345678", 8);
uint8_t buf[10] = {0};
W25Q32_ReadBytes(63, 2, 10, 0, (uint8_t *)buf, 8);
LOG_DEBUG("buf = %s", buf);
// uint8_t buf[10] = {0};
// // 从 block-63 sector-2 page-10 addr-2 读取数据
// W25Q32_ReadBytes(63, 2, 10, 2, (uint8_t *)buf, 6);
// LOG_DEBUG("buf = %s", buf);
while (1) {}
}
逻辑分析
STM32 SPI外设
简介
STM32 的 SPI 外设可用作通讯的主机及从机,支持最高的 SCK 时钟频率为 fpclk/2 (STM32F103 型号的芯片默认fpclk1为36MHz,fpclk2为72MHz。),完全支持 SPI 协议的 4 种模式,数据帧长度可设置为 8 位或 16 位,可设置数据 MSB 先行或 LSB 先行。它还支持双线全双工、单线双向以及单线模式。
STM32F103系列提供了3个SPI,SPI1挂在APB2总线,SPI2/3挂在APB1总线。
用的比较多还是双线全双工模式。
框图
NSS功能说明
主设备模式配置流程(寄存器版本)
spi.h
#ifndef __SPI_H__
#define __SPI_H__
#include "stm32f10x.h"
#include "delay.h"
// CS PC13
#define CS_HIGH (GPIOC->ODR |= GPIO_ODR_ODR13)
#define CS_LOW (GPIOC->ODR &= ~GPIO_ODR_ODR13)
void SPI_Init(void);
void SPI_Start(void);
void SPI_Stop(void);
uint8_t SPI_SwapByte(uint8_t dataByte);
#endif /* __SPI_H__ */
spi.c
#include "spi.h"
void SPI_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
// CS-PC13 通用推挽 MODE=11 CNF=00
GPIOC->CRH |= GPIO_CRH_MODE13;
GPIOC->CRH &= ~GPIO_CRH_CNF13;
// SCK-PA5,MOSI-PA7 复用推挽 MODE=11 CNF=10
GPIOA->CRL |= GPIO_CRL_MODE5;
GPIOA->CRL |= GPIO_CRL_CNF5_1;
GPIOA->CRL &= ~GPIO_CRL_CNF5_0;
GPIOA->CRL |= GPIO_CRL_MODE7;
GPIOA->CRL |= GPIO_CRL_CNF7_1;
GPIOA->CRL &= ~GPIO_CRL_CNF7_0;
// MISO-PA6 浮空输入 MODE=00, CNF=01
GPIOA->CRL &= ~GPIO_CRL_MODE6;
GPIOA->CRL |= GPIO_CRL_CNF6_0;
GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
// 波特率配置为PCLK/4 即 72M/4=18M => 001
SPI1->CR1 &= ~SPI_CR1_BR;
SPI1->CR1 |= SPI_CR1_BR_0;
// SPI模式0(时钟极性=0,相位=0)
SPI1->CR1 &= ~SPI_CR1_CPOL;
SPI1->CR1 &= ~SPI_CR1_CPHA;
// 数据帧格式 8bit
SPI1->CR1 &= ~SPI_CR1_DFF;
// 高位优先
SPI1->CR1 &= ~SPI_CR1_LSBFIRST;
// NSS为当前MCU作为从设备时的片选信号输入,主模式下,要么硬件上将该引脚拉高,要么通过SSM和SSI将其强制置1
SPI1->CR1 |= SPI_CR1_SSM; // 将NSS输入配置为软件控制
SPI1->CR1 |= SPI_CR1_SSI; // 通过SSI=1将NSS强制拉高
// 设置主模式
SPI1->CR1 |= SPI_CR1_MSTR;
// 使能SPI外设
SPI1->CR1 |= SPI_CR1_SPE;
// SPI模式0(极性=0,相位=0) 空闲状态 CS拉高(低电平使能),SCK拉低
CS_HIGH;
}
void SPI_Start(void) { CS_LOW; }
void SPI_Stop(void) { CS_HIGH; }
uint8_t SPI_SwapByte(uint8_t dataByte) {
// 等待发送缓冲区为空
while ((SPI1->SR & SPI_SR_TXE) == 0) {
}
// 发送数据
SPI1->DR = dataByte;
// 等待接收缓冲区非空
while ((SPI1->SR & SPI_SR_RXNE) == 0) {
}
// 接收数据
return (uint8_t)SPI1->DR;
}
main.c
#include "stm32f10x.h"
#include "uart.h"
#include "logger.h"
#include "w25q32.h"
void uart1_received_callback(uint8_t buf[], uint8_t size) {
}
int main() {
uart_init();
W25Q32_Init();
LOG_DEBUG("main start")
uint8_t manufactureId = 0;
uint16_t deviceId = 0;
W25Q32_ReadID(&manufactureId, &deviceId);
LOG_DEBUG("manufacture = %#x, deviceId = %#x", manufactureId, deviceId)
// 擦除扇区(第63个block,第2个sector)
W25Q32_SectorErase(63, 2);
// 向 block-63 sector-2 page-10 addr-0写入数据
W25Q32_WritePage(63, 2, 10, 0, (uint8_t *)"12345678", 8);
uint8_t buf[10] = {0};
W25Q32_ReadBytes(63, 2, 10, 1, (uint8_t *)buf, 7);
LOG_DEBUG("buf = %s", buf);
while (1) {}
}
HAL库实现
SPI配置
片选引脚配置
示例代码
spi.h
/* USER CODE BEGIN Prototypes */
void SPI_Init(void);
void SPI_Start(void);
void SPI_Stop(void);
uint8_t SPI_SwapByte(uint8_t dataByte);
/* USER CODE END Prototypes */
spi.c
void SPI_Start(void) {
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
}
void SPI_Stop(void){
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
}
uint8_t SPI_SwapByte(uint8_t dataByte) {
uint8_t buf = 0;
HAL_SPI_TransmitReceive(&hspi1, &dataByte, &buf, 1, 10);
return buf;
}
main.c
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_SPI1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
uint8_t manufactureId = 0;
uint16_t deviceId = 0;
W25Q32_ReadID(&manufactureId, &deviceId);
printf("manufacture = %#x, deviceId = %#x\n", manufactureId, deviceId);
// 擦除扇区(第63个block,第2个sector)
W25Q32_SectorErase(63, 2);
// 向 block-63 sector-2 page-10 addr-0写入数据
W25Q32_WritePage(63, 2, 10, 0, (uint8_t *)"12345678", 8);
uint8_t buf[10] = {0};
W25Q32_ReadBytes(63, 2, 10, 1, (uint8_t *)buf, 7);
printf("buf = %s\n", buf);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
案例-跨页写入
spi.c
#include "w25q32.h"
void W25Q32_Init(void) { SPI_Init(); }
void W25Q32_ReadID(uint8_t *manufactureId, uint16_t *deviceId) {
// 片选使能
SPI_Start();
// 发送Read JEDEC ID指令9Fh,读取生产厂商和设备ID
SPI_SwapByte(0x9F);
// 读取从设备响应数据
*manufactureId = SPI_SwapByte(0);
*deviceId |= (SPI_SwapByte(0) << 8) & 0xFF00;
*deviceId |= SPI_SwapByte(0) & 0x00FF;
// 片选失能
SPI_Stop();
}
void W25Q32_WaitNotBusy(void) {
SPI_Start();
// 发送读取状态寄存器指令
SPI_SwapByte(0x05);
// 轮询接收从设备响应的状态字节,直到其中的最低位为0
while (SPI_SwapByte(0) & 0x01) {
}
SPI_Stop();
}
void W25Q32_WriteEnable(void) {
SPI_Start();
SPI_SwapByte(0x06);
SPI_Stop();
}
void W25Q32_WriteDisable(void) {
SPI_Start();
SPI_SwapByte(0x04);
SPI_Stop();
}
void W25Q32_SectorErase(uint8_t block, uint8_t sector) {
// 执行擦除前必须等待BUSY状态和开启写使能
W25Q32_WaitNotBusy();
W25Q32_WriteEnable();
SPI_Start();
SPI_SwapByte(0x20);
// 发送24bit地址数据,高8位[23:16]为block地址,[15:12]为sector地址
uint32_t addr = (block << 16) | ((sector & 0x0F) << 12);
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
SPI_Stop();
W25Q32_WriteDisable();
}
void W25Q32_WritePage(uint8_t block, uint8_t sector, uint8_t page,
uint8_t innerAddr, uint8_t *data, uint16_t size) {
// 执行页写前必须等待BUSY状态和开启写使能
W25Q32_WaitNotBusy();
W25Q32_WriteEnable();
SPI_Start();
SPI_SwapByte(0x02);
// 发送24bit地址数据,高8位[23:16]为block地址,[15:12]为sector地址
uint32_t addr = (block << 16) | ((sector & 0x0F) << 12) |
((page & 0x0F) << 8) | innerAddr;
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
// 发送待写入数据
for (uint8_t i = 0; i < size; i++) {
SPI_SwapByte(data[i]);
}
SPI_Stop();
W25Q32_WriteDisable();
}
void W25Q32_WriteAddr(uint32_t addr, uint8_t *data, uint16_t size) {
// 执行页写前必须等待BUSY状态和开启写使能
W25Q32_WaitNotBusy();
W25Q32_WriteEnable();
SPI_Start();
SPI_SwapByte(0x02);
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
// 发送待写入数据
for (uint8_t i = 0; i < size; i++) {
SPI_SwapByte(data[i]);
}
SPI_Stop();
W25Q32_WriteDisable();
}
void W25Q32_ReadBytes(uint8_t block, uint8_t sector, uint8_t page,
uint8_t innerAddr, uint8_t *buf, uint16_t size) {
// 读取数据前等待BUSY状态
W25Q32_WaitNotBusy();
SPI_Start();
SPI_SwapByte(0x03);
// 发送24bit地址数据,高8位[23:16]为block地址,[15:12]为sector地址
uint32_t addr = (block << 16) | ((sector & 0x0F) << 12) |
((page & 0x0F) << 8) | innerAddr;
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
// 接收读取的数据
for (uint16_t i = 0; i < size; i++) {
buf[i] = SPI_SwapByte(0);
}
SPI_Stop();
}
void W25Q32_ReadFromAddr(uint32_t addr, uint8_t *buf, uint16_t size) {
// 读取数据前等待BUSY状态
W25Q32_WaitNotBusy();
SPI_Start();
SPI_SwapByte(0x03);
// 高位优先,逐字节发送24bit
SPI_SwapByte((addr >> 16) & 0xFF);
SPI_SwapByte((addr >> 8) & 0xFF);
SPI_SwapByte(addr & 0xFF);
// 接收读取的数据
for (uint16_t i = 0; i < size; i++) {
buf[i] = SPI_SwapByte(0);
}
SPI_Stop();
}
main.c
#include "logger.h"
#include "stm32f10x.h"
#include "uart.h"
#include "w25q32.h"
void uart1_received_callback(uint8_t buf[], uint8_t size) {}
int main() {
uart_init();
W25Q32_Init();
LOG_DEBUG("main start")
uint8_t manufactureId = 0;
uint16_t deviceId = 0;
W25Q32_ReadID(&manufactureId, &deviceId);
LOG_DEBUG("manufacture = %#x, deviceId = %#x", manufactureId, deviceId)
// // 擦除扇区(第63个block,第2个sector)
// W25Q32_SectorErase(63, 2);
// // 向 block-63 sector-2 page-10 addr-0写入数据
// W25Q32_WritePage(63, 2, 10, 0, (uint8_t *)"12345678", 8);
// uint8_t buf[10] = {0};
// W25Q32_ReadBytes(63, 2, 10, 1, (uint8_t *)buf, 7);
// LOG_DEBUG("buf = %s", buf);
uint8_t data[261] = {0};
for (uint16_t i = 0; i < 260; i++) {
data[i] = 'a' + (i % 26);
}
LOG_DEBUG("data = %s", data)
uint32_t addr = 0x32f680;
W25Q32_SectorErase((addr >> 16) & 0xFF, (addr >> 12) & 0x0F);
uint16_t size1 = 0xFF + 1 - (addr & 0xFF);
W25Q32_WriteAddr(addr, data, size1);
uint16_t size2 = 260 - size1;
W25Q32_WritePage((addr >> 16) & 0xFF, (addr >> 12) & 0x0F, ((addr >> 8) & 0x0F) + 1, 0, data + size1, size2);
uint8_t buf[300] = {0};
W25Q32_ReadFromAddr(addr, buf, 260);
LOG_DEBUG("buf = %s", buf);
while (1) {
}
}