高速通信协议SPI(基于STM32F103)


参考资料

SPI概述

image-20241208085758199

物理层

image-20241208085826672

image-20241208085922892

image-20241208090013844

协议层

主机和从机之间的数据交换

image-20241208090106193

image-20241208090214243

image-20241208090240645

image-20241208090322157

image-20241208090347725

image-20241208090549989

时钟的极性和相位

image-20241208091011305

image-20241208091050562

STM32参考手册

STM32参考手册

image-20241208091524655

image-20241208114103130

image-20241208114140123

案例-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引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,数据输出引脚输出高阻抗状态,时钟和数据输入引脚无效。我们直接接电源,不使用通讯暂停功能。

image-20241208132401090

(1)这个flash芯片只支持模式0和模式3

(2)写的时候必须是先擦除,擦除后再写入。

(3)移位是高位优先。

image-20241208132526999

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个字节

image-20241208132710140

写入操作注意事项

(1)写入操作前,必须先进行写使能。

(2)每个数据位只能由1改写为0,不能由0改写为1。

(3)写入数据前必须先檫除,檫除后,所有数据位变为1。擦除必须按最小擦除单元进行。

(4)连续写入多字节时,最多写入一页的数据,超过页尾位置的数据,会回到页首覆盖写入。

(5)写入操作结束后,芯片进入忙状态,不响应新的读写操作。

写使能

image-20241208140636281

页写

image-20241208142927822

忙状态

image-20241208141022091

读取操作注意事项

(1)直接调用读取时序,无需读使能,无需额外操作,没有页的限制。

(2)读取操作结束后不会进入忙状态,但不能在忙状态时读取。

电气特性&时序要求分析

W25Q32

如图3.3V工作电压条件下,SCK时钟频率最高可达133MHz(非03h指令,03h指令则为50MHz)。对于STM32F103而言,GPIO输出的最大频率为50MHz(如下图)。

其他时序要求参数大都在10ns以内,而STM32F103的主频为72MHz,时钟周期约为13.8ns,因此大多数情况下都不需要刻意延时。

STM32F103数据手册

对于擦除和页写相关操作,则需要增加一定的延时来等待芯片内部完成相应的操作。

image-20241208145935374

实验-读取厂商ID和设备ID

image-20241208143740884image-20241208144153347

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

image-20241208163556410

逻辑分析

image-20241208150354261

实验-擦除&页写&连续读

等待BUSY状态

image-20241208155727798

image-20241208155924081

void W25Q32_WaitNotBusy(void) {
    SPI_Start();
    // 发送读取状态寄存器指令
    SPI_SwapByte(0x05);
    // 轮询接收从设备响应的状态字节,直到其中的最低位为0
    while (SPI_SwapByte(0) & 0x01) {
    }
    SPI_Stop();
}

擦除扇区

image-20241208164659395

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();
}

连续读

image-20241208164826767

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) {}
}

逻辑分析

image-20241208171056385

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总线。

用的比较多还是双线全双工模式

框图

image-20241208170545574

image-20241208170738650

NSS功能说明

image-20241208191830342

image-20241208191904550

主设备模式配置流程(寄存器版本)

image-20241208180028612

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配置

image-20241208201837487

片选引脚配置

image-20241208201927169

示例代码

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 */

案例-跨页写入

image-20241208221856087

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) {
    }
}

文章作者: 安文
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 安文 !
  目录