STM32基础驱动系列——SPI flash 简介以GD25Q32为例进行介绍。这个flash是一款32Mbit的存储芯片有4096K byte也就是相当于4M的容量。整个flash的层级结构如下整片 Flash └── Block 块 └── Sector 扇区 └── Page 页 └── Byte 字节也就是具有64 个 64KB Block1024 个 4KB Sector16384 个 256B Page。写入时按page最大256B擦除按sector最小4kb使用block擦除也是可以的。这种限制就要求了如果要“重新写入/修改”的区域跨越了两个 sector并且需要擦除那么这两个 sector 都要擦除。而不能保留前一个sector里面不擦除的区域和后一个sector里面不擦除的区域。到这里肯定会想到这个flash有多少次读写寿命数据手册里面给出Minimum 100,000 Program/Erase Cycles也就是10W次编程和擦写注意读取是不影响寿命的。那么按照上述擦除原理来算如果只清除了单个sector那么单个sector的P/E寿命就减少了一次如果按block清除那么整个block的所有sector的P/E寿命就减少了一次。看到这里我们可以想到如果我们一直从第一个page开始往后写那么前面sector的寿命肯定比后面的短因为前面的sector我们反复在进行写入和擦除。这里使用磨损均衡的方法就可以一定程度上避免这种问题。磨损均衡是指从第一个扇区一直写写到写满位置后擦除最前面的扇区。不过对于我当前的应用来说10W次寿命已经够了。因此暂时不做磨损均衡。PIN定义除了SPI通信需要的MOSI MISO SCLK CS以及电源VCC和GND以外剩下WP和HOLD两个pin。wp为低时具有写保护功能此时内部数据不能写入和擦除。hold为暂时挂起的应用。我这里wp直接拉高先不用写保护功能。hold主要是为了应对通信到一半就去执行其他代码打断 写入的情况。然而如果单个SPI比如SPI1只接入了spi flash没有接入别的外设其他中断的触发只会暂停spi通信sclk是不会动的也就不会给flash写入造成困境。驱动文件gd25q32.h#ifndef __GD25Q32_H #define __GD25Q32_H #ifdef __cplusplus extern C { #endif #include main.h #include stdint.h /* * GD25Q32ESIG / GD25Q32E * MCU: STM32F401RCT6 * SPI: SPI1 * CS : PD2 * * CubeMX SPI1 pins: * SCK - PB3 AF5 * MISO - PB4 AF5 * MOSI - PB5 AF5 * CS - PD2 GPIO_Output * * HOLD# and WP# are pulled high in hardware, so they are not controlled here. */ #define GD25Q32_FLASH_SIZE (4UL * 1024UL * 1024UL) /* 4 MByte */ #define GD25Q32_SECTOR_SIZE 4096UL /* 4 KByte */ #define GD25Q32_BLOCK32_SIZE (32UL * 1024UL) #define GD25Q32_BLOCK64_SIZE (64UL * 1024UL) #define GD25Q32_PAGE_SIZE 256UL #define GD25Q32_JEDEC_ID 0xC84016UL HAL_StatusTypeDef GD25Q32_Init(void); HAL_StatusTypeDef GD25Q32_ReadJEDEC_ID(uint32_t *id); HAL_StatusTypeDef GD25Q32_ReadData(uint32_t addr, uint8_t *buf, uint32_t len); HAL_StatusTypeDef GD25Q32_PageProgram(uint32_t addr, const uint8_t *buf, uint32_t len); HAL_StatusTypeDef GD25Q32_WriteData(uint32_t addr, const uint8_t *buf, uint32_t len); HAL_StatusTypeDef GD25Q32_EraseSector(uint32_t addr); HAL_StatusTypeDef GD25Q32_EraseBlock32K(uint32_t addr); HAL_StatusTypeDef GD25Q32_EraseBlock64K(uint32_t addr); HAL_StatusTypeDef GD25Q32_ChipErase(void); HAL_StatusTypeDef GD25Q32_ReadStatus1(uint8_t *status); HAL_StatusTypeDef GD25Q32_WaitBusy(uint32_t timeout_ms); #ifdef __cplusplus } #endif #endifgd25q32.c#include gd25q32.h #include spi.h #include gpio.h #include stddef.h /* ---------- User hardware binding ---------- */ extern SPI_HandleTypeDef hspi1; #define GD25Q32_SPI_HANDLE hspi1 #define GD25Q32_CS_GPIO_PORT GPIOD #define GD25Q32_CS_GPIO_PIN GPIO_PIN_2 /* ---------- SPI Flash commands ---------- */ #define GD25Q32_CMD_WRITE_ENABLE 0x06 #define GD25Q32_CMD_WRITE_DISABLE 0x04 #define GD25Q32_CMD_READ_STATUS1 0x05 #define GD25Q32_CMD_READ_DATA 0x03 #define GD25Q32_CMD_PAGE_PROGRAM 0x02 #define GD25Q32_CMD_SECTOR_ERASE 0x20 /* 4KB */ #define GD25Q32_CMD_BLOCK_ERASE32 0x52 /* 32KB */ #define GD25Q32_CMD_BLOCK_ERASE64 0xD8 /* 64KB */ #define GD25Q32_CMD_CHIP_ERASE 0xC7 #define GD25Q32_CMD_JEDEC_ID 0x9F #define GD25Q32_CMD_ENABLE_RESET 0x66 #define GD25Q32_CMD_RESET_DEVICE 0x99 #define GD25Q32_STATUS1_WIP 0x01 /* Write In Progress */ #define GD25Q32_STATUS1_WEL 0x02 /* Write Enable Latch */ #define GD25Q32_SPI_TIMEOUT_MS 100U #define GD25Q32_PAGE_TIMEOUT_MS 20U #define GD25Q32_SECTOR_TIMEOUT_MS 1000U #define GD25Q32_BLOCK_TIMEOUT_MS 3000U #define GD25Q32_CHIP_TIMEOUT_MS 60000U static void GD25Q32_CS_Low(void) { HAL_GPIO_WritePin(GD25Q32_CS_GPIO_PORT, GD25Q32_CS_GPIO_PIN, GPIO_PIN_RESET); } static void GD25Q32_CS_High(void) { HAL_GPIO_WritePin(GD25Q32_CS_GPIO_PORT, GD25Q32_CS_GPIO_PIN, GPIO_PIN_SET); } static HAL_StatusTypeDef GD25Q32_Transmit(const uint8_t *data, uint16_t len) { return HAL_SPI_Transmit(GD25Q32_SPI_HANDLE, (uint8_t *)data, len, GD25Q32_SPI_TIMEOUT_MS); } static HAL_StatusTypeDef GD25Q32_Receive(uint8_t *data, uint16_t len) { return HAL_SPI_Receive(GD25Q32_SPI_HANDLE, data, len, GD25Q32_SPI_TIMEOUT_MS); } static uint8_t GD25Q32_CheckAddressRange(uint32_t addr, uint32_t len) { if (len 0U) { return 1U; } if (addr GD25Q32_FLASH_SIZE) { return 0U; } if (len (GD25Q32_FLASH_SIZE - addr)) { return 0U; } return 1U; } static void GD25Q32_MakeAddrCmd(uint8_t cmd, uint32_t addr, uint8_t out[4]) { out[0] cmd; out[1] (uint8_t)((addr 16) 0xFFU); out[2] (uint8_t)((addr 8) 0xFFU); out[3] (uint8_t)(addr 0xFFU); } static HAL_StatusTypeDef GD25Q32_WriteEnable(void) { uint8_t cmd GD25Q32_CMD_WRITE_ENABLE; uint8_t status; uint32_t start; GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 1) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } GD25Q32_CS_High(); /* Wait until WEL bit becomes 1 */ start HAL_GetTick(); do { if (GD25Q32_ReadStatus1(status) ! HAL_OK) { return HAL_ERROR; } if ((status GD25Q32_STATUS1_WEL) ! 0U) { return HAL_OK; } } while ((HAL_GetTick() - start) GD25Q32_SPI_TIMEOUT_MS); return HAL_TIMEOUT; } HAL_StatusTypeDef GD25Q32_Init(void) { uint32_t id; /* Make sure CS is inactive before any SPI command */ GD25Q32_CS_High(); HAL_Delay(1); /* * Optional software reset. * Safe for normal boot initialization, but do not call it while another task is using the flash. */ uint8_t cmd; cmd GD25Q32_CMD_ENABLE_RESET; GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 1) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } GD25Q32_CS_High(); cmd GD25Q32_CMD_RESET_DEVICE; GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 1) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } GD25Q32_CS_High(); HAL_Delay(1); if (GD25Q32_ReadJEDEC_ID(id) ! HAL_OK) { return HAL_ERROR; } if (id ! GD25Q32_JEDEC_ID) { return HAL_ERROR; } return HAL_OK; } HAL_StatusTypeDef GD25Q32_ReadJEDEC_ID(uint32_t *id) { uint8_t cmd GD25Q32_CMD_JEDEC_ID; uint8_t rx[3]; if (id NULL) { return HAL_ERROR; } GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 1) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } if (GD25Q32_Receive(rx, 3) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } GD25Q32_CS_High(); *id ((uint32_t)rx[0] 16) | ((uint32_t)rx[1] 8) | rx[2]; return HAL_OK; } HAL_StatusTypeDef GD25Q32_ReadStatus1(uint8_t *status) { uint8_t cmd GD25Q32_CMD_READ_STATUS1; if (status NULL) { return HAL_ERROR; } GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 1) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } if (GD25Q32_Receive(status, 1) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } GD25Q32_CS_High(); return HAL_OK; } HAL_StatusTypeDef GD25Q32_WaitBusy(uint32_t timeout_ms) { uint8_t status; uint32_t start HAL_GetTick(); do { if (GD25Q32_ReadStatus1(status) ! HAL_OK) { return HAL_ERROR; } if ((status GD25Q32_STATUS1_WIP) 0U) { return HAL_OK; } /* * If you use FreeRTOS, you may replace this with osDelay(1) * after including cmsis_os2.h. */ HAL_Delay(1); } while ((HAL_GetTick() - start) timeout_ms); return HAL_TIMEOUT; } HAL_StatusTypeDef GD25Q32_ReadData(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; if ((buf NULL) || (GD25Q32_CheckAddressRange(addr, len) 0U)) { return HAL_ERROR; } if (len 0U) { return HAL_OK; } GD25Q32_MakeAddrCmd(GD25Q32_CMD_READ_DATA, addr, cmd); GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 4) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } while (len 0U) { uint16_t chunk (len 65535U) ? 65535U : (uint16_t)len; if (GD25Q32_Receive(buf, chunk) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } buf chunk; len - chunk; } GD25Q32_CS_High(); return HAL_OK; } /* * Program no more than one page. * This function must not cross a 256-byte page boundary. * For arbitrary-length write, use GD25Q32_WriteData(). */ HAL_StatusTypeDef GD25Q32_PageProgram(uint32_t addr, const uint8_t *buf, uint32_t len) { uint8_t cmd[4]; if ((buf NULL) || (len 0U) || (len GD25Q32_PAGE_SIZE)) { return HAL_ERROR; } if (GD25Q32_CheckAddressRange(addr, len) 0U) { return HAL_ERROR; } if (((addr (GD25Q32_PAGE_SIZE - 1U)) len) GD25Q32_PAGE_SIZE) { return HAL_ERROR; } if (GD25Q32_WriteEnable() ! HAL_OK) { return HAL_ERROR; } GD25Q32_MakeAddrCmd(GD25Q32_CMD_PAGE_PROGRAM, addr, cmd); GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 4) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } while (len 0U) { uint16_t chunk (len 65535U) ? 65535U : (uint16_t)len; if (GD25Q32_Transmit(buf, chunk) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } buf chunk; len - chunk; } GD25Q32_CS_High(); return GD25Q32_WaitBusy(GD25Q32_PAGE_TIMEOUT_MS); } /* * Write arbitrary length data. * It automatically splits data by 256-byte page boundary. * * Important: * SPI NOR Flash can program bits from 1 to 0 only. * Before changing 0 back to 1, erase the sector/block/chip first. */ HAL_StatusTypeDef GD25Q32_WriteData(uint32_t addr, const uint8_t *buf, uint32_t len) { HAL_StatusTypeDef ret; if ((buf NULL) || (GD25Q32_CheckAddressRange(addr, len) 0U)) { return HAL_ERROR; } while (len 0U) { uint32_t page_offset addr (GD25Q32_PAGE_SIZE - 1U); uint32_t page_left GD25Q32_PAGE_SIZE - page_offset; uint32_t write_len (len page_left) ? len : page_left; ret GD25Q32_PageProgram(addr, buf, write_len); if (ret ! HAL_OK) { return ret; } addr write_len; buf write_len; len - write_len; } return HAL_OK; } HAL_StatusTypeDef GD25Q32_EraseSector(uint32_t addr) { uint8_t cmd[4]; if (addr GD25Q32_FLASH_SIZE) { return HAL_ERROR; } addr ~(GD25Q32_SECTOR_SIZE - 1U); if (GD25Q32_WriteEnable() ! HAL_OK) { return HAL_ERROR; } GD25Q32_MakeAddrCmd(GD25Q32_CMD_SECTOR_ERASE, addr, cmd); GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 4) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } GD25Q32_CS_High(); return GD25Q32_WaitBusy(GD25Q32_SECTOR_TIMEOUT_MS); } HAL_StatusTypeDef GD25Q32_EraseBlock32K(uint32_t addr) { uint8_t cmd[4]; if (addr GD25Q32_FLASH_SIZE) { return HAL_ERROR; } addr ~(GD25Q32_BLOCK32_SIZE - 1U); if (GD25Q32_WriteEnable() ! HAL_OK) { return HAL_ERROR; } GD25Q32_MakeAddrCmd(GD25Q32_CMD_BLOCK_ERASE32, addr, cmd); GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 4) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } GD25Q32_CS_High(); return GD25Q32_WaitBusy(GD25Q32_BLOCK_TIMEOUT_MS); } HAL_StatusTypeDef GD25Q32_EraseBlock64K(uint32_t addr) { uint8_t cmd[4]; if (addr GD25Q32_FLASH_SIZE) { return HAL_ERROR; } addr ~(GD25Q32_BLOCK64_SIZE - 1U); if (GD25Q32_WriteEnable() ! HAL_OK) { return HAL_ERROR; } GD25Q32_MakeAddrCmd(GD25Q32_CMD_BLOCK_ERASE64, addr, cmd); GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 4) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } GD25Q32_CS_High(); return GD25Q32_WaitBusy(GD25Q32_BLOCK_TIMEOUT_MS); } HAL_StatusTypeDef GD25Q32_ChipErase(void) { uint8_t cmd GD25Q32_CMD_CHIP_ERASE; if (GD25Q32_WriteEnable() ! HAL_OK) { return HAL_ERROR; } GD25Q32_CS_Low(); if (GD25Q32_Transmit(cmd, 1) ! HAL_OK) { GD25Q32_CS_High(); return HAL_ERROR; } GD25Q32_CS_High(); return GD25Q32_WaitBusy(GD25Q32_CHIP_TIMEOUT_MS); }使用方法uint8_t tx_buf[16] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x12, 0x34 }; uint8_t rx_buf[16]; GD25Q32_EraseSector(0x000000); GD25Q32_WriteData(0x000000, tx_buf, sizeof(tx_buf)); GD25Q32_ReadData(0x000000, rx_buf, sizeof(rx_buf));