STM32外设SPIFLASH应用实例
- 互联网
- 2025-09-03 19:39:01

STM32外设SPI FLASH应用实例 1. 前言1.1 硬件准备1.2 软件准备 2. 硬件连接3. 软件实现3.1 SPI 初始化3.2 QW128 SPI FLASH 驱动3.3 乒乓存储实现 4. 测试与验证4.1 数据备份测试4.2 数据恢复测试 5 实例5.1 参数结构体定义5.2 存储参数到 SPI FLASH5.3 从 SPI FLASH 读取参数5.4 示例:存储和读取参数5.6 注意事项 6. 总结 1. 前言
在嵌入式系统中,数据的存储和备份是一个非常重要的功能。SPI FLASH 是一种常见的非易失性存储器,具有容量大、速度快、接口简单等优点。本文将介绍如何在 STM32F103 上使用 SPI 接口操作 QW128 SPI FLASH,并通过乒乓存储的方式实现数据备份。
1.1 硬件准备 STM32F103 开发板QW128 SPI FLASH 模块杜邦线若干 1.2 软件准备 Keil MDK 或 STM32CubeIDESTM32 HAL 库 2. 硬件连接将 QW128 SPI FLASH 模块与 STM32F103 开发板连接,具体连接方式如下:
QW128 引脚STM32F103 引脚CSPA4SCKPA5MISOPA6MOSIPA7GNDGNDVCC3.3V 3. 软件实现使用STM32CUBE配置SPI通信
3.1 SPI 初始化首先,我们需要初始化 SPI 接口。使用 STM32CubeMX 配置 SPI1 外设,并生成初始化代码。
void MX_SPI1_Init(void) { hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 10; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } } 3.2 QW128 SPI FLASH 驱动接下来,我们编写 QW128 SPI FLASH 的驱动代码,包括读写操作。
#define QW128_CMD_WRITE_ENABLE 0x06 #define QW128_CMD_WRITE_DISABLE 0x04 #define QW128_CMD_READ_STATUS_REG 0x05 #define QW128_CMD_WRITE_STATUS_REG 0x01 #define QW128_CMD_READ_DATA 0x03 #define QW128_CMD_PAGE_PROGRAM 0x02 #define QW128_CMD_SECTOR_ERASE 0x20 #define QW128_CMD_CHIP_ERASE 0xC7 void QW128_WriteEnable(void) { uint8_t cmd = QW128_CMD_WRITE_ENABLE; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } void QW128_WriteDisable(void) { uint8_t cmd = QW128_CMD_WRITE_DISABLE; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } uint8_t QW128_ReadStatusReg(void) { uint8_t cmd = QW128_CMD_READ_STATUS_REG; uint8_t status; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&hspi1, &status, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); return status; } void QW128_WriteStatusReg(uint8_t status) { uint8_t cmd[2] = {QW128_CMD_WRITE_STATUS_REG, status}; QW128_WriteEnable(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, cmd, 2, HAL_MAX_DELAY); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } void QW128_ReadData(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4] = {QW128_CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF}; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY); HAL_SPI_Receive(&hspi1, data, len, HAL_MAX_DELAY); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } void QW128_PageProgram(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4] = {QW128_CMD_PAGE_PROGRAM, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF}; QW128_WriteEnable(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY); HAL_SPI_Transmit(&hspi1, data, len, HAL_MAX_DELAY); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } void QW128_SectorErase(uint32_t addr) { uint8_t cmd[4] = {QW128_CMD_SECTOR_ERASE, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF}; QW128_WriteEnable(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } void QW128_ChipErase(void) { uint8_t cmd = QW128_CMD_CHIP_ERASE; QW128_WriteEnable(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } 3.3 乒乓存储实现乒乓存储是一种常用的数据备份策略,通过交替使用两个存储区域来确保数据的完整性和可靠性。
#define PAGE_SIZE 256 #define SECTOR_SIZE 4096 #define BUFFER_SIZE 1024 uint8_t buffer[BUFFER_SIZE]; uint32_t current_sector = 0; void PingPong_Backup(uint8_t *data, uint16_t len) { // 擦除当前扇区 QW128_SectorErase(current_sector * SECTOR_SIZE); // 写入数据 for (uint16_t i = 0; i < len; i += PAGE_SIZE) { QW128_PageProgram(current_sector * SECTOR_SIZE + i, data + i, PAGE_SIZE); } // 切换到下一个扇区 current_sector = (current_sector + 1) % 2; } void PingPong_Restore(uint8_t *data, uint16_t len) { // 读取数据 QW128_ReadData(current_sector * SECTOR_SIZE, data, len); } 4. 测试与验证 4.1 数据备份测试 uint8_t test_data[BUFFER_SIZE]; for (uint16_t i = 0; i < BUFFER_SIZE; i++) { test_data[i] = i % 256; } PingPong_Backup(test_data, BUFFER_SIZE); 4.2 数据恢复测试 uint8_t restore_data[BUFFER_SIZE]; PingPong_Restore(restore_data, BUFFER_SIZE); // 验证数据 for (uint16_t i = 0; i < BUFFER_SIZE; i++) { if (restore_data[i] != test_data[i]) { // 数据不一致,处理错误 Error_Handler(); } } 5 实例 5.1 参数结构体定义以下是参数结构体的定义,基于你提供的代码:
typedef enum { BAUD_9600, BAUD_19200, BAUD_115200 } BAUD_ENUM; typedef struct { BAUD_ENUM CommBaud; // 通信波特率 uint8_t OnOffCtrl; // 启停操作方式(0-本地;1-远程485;2-模拟量) uint8_t ModeCtrl; // 模式修改方式(0-本地;1-远程485;2-模拟量) uint8_t SetValCtrl; // 设定修改方式(0-本地;1-远程485;2-模拟量) uint8_t MasterSlaver; // 主副机设置(0-主机;1-副机;2-单机) uint8_t TestMode; // 测试模式 uint8_t DebugMode; // 调试模式 uint8_t DeviceModel; // 设备型号(0-3KW;2-20KW风冷) uint8_t DeviceSer[32]; // 设备序列号 uint8_t AlarmEnable; // 告警使能(0-关闭;1-使能) uint8_t CommProto; // 通信协议(0-Modbus;1-Profibus) uint16_t UdcLimit; // Udc调节限定值 uint16_t IdcLimit; // Idc调节限定值 uint16_t PdcLimit; // Pdc调节限定值 uint8_t ModeSlect; // 调节模式选择(0-Udc;1-Idc;2-Pdc) uint8_t PWM1Freq; // PWM1频率(40~80表示40KHz~80KHz) } DeviceParams;5.2 存储参数到 SPI FLASH
我们可以将参数结构体存储到 SPI FLASH 的指定地址。以下是存储函数的实现:
#include "stm32f1xx_hal.h" #include "spi_flash.h" // 假设这是 QW128 SPI FLASH 的驱动头文件 #define PARAMS_FLASH_ADDR 0x00000000 // 参数存储的起始地址 void SaveParamsToFlash(DeviceParams *params) { // 擦除 SPI FLASH 的指定扇区 QW128_SectorErase(PARAMS_FLASH_ADDR); // 将参数结构体写入 SPI FLASH QW128_PageProgram(PARAMS_FLASH_ADDR, (uint8_t *)params, sizeof(DeviceParams)); }5.3 从 SPI FLASH 读取参数
从 SPI FLASH 中读取参数结构体的实现如下:
void LoadParamsFromFlash(DeviceParams *params) { // 从 SPI FLASH 读取参数结构体 QW128_ReadData(PARAMS_FLASH_ADDR, (uint8_t *)params, sizeof(DeviceParams)); }5.4 示例:存储和读取参数
以下是一个完整的示例,展示如何初始化参数、存储到 SPI FLASH 以及从 SPI FLASH 读取参数:
int main(void) { HAL_Init(); SystemClock_Config(); MX_SPI1_Init(); // 初始化 SPI MX_GPIO_Init(); // 初始化 GPIO // 初始化参数结构体 DeviceParams params = { .CommBaud = BAUD_115200, .OnOffCtrl = 1, .ModeCtrl = 1, .SetValCtrl = 1, .MasterSlaver = 0, .TestMode = 0, .DebugMode = 1, .DeviceModel = 2, .DeviceSer = "1234567890ABCDEF1234567890ABCDEF", .AlarmEnable = 1, .CommProto = 0, .UdcLimit = 1000, .IdcLimit = 500, .PdcLimit = 2000, .ModeSlect = 1, .PWM1Freq = 60 }; // 存储参数到 SPI FLASH SaveParamsToFlash(¶ms); // 从 SPI FLASH 读取参数 DeviceParams loadedParams; LoadParamsFromFlash(&loadedParams); // 验证读取的参数是否正确 if (memcmp(¶ms, &loadedParams, sizeof(DeviceParams)) == 0) { printf("Parameters loaded successfully!\n"); } else { printf("Parameter load failed!\n"); } while (1) { // 主循环 } }5.6 注意事项
SPI FLASH 的寿命:
SPI FLASH 的擦写次数有限(通常为 10 万次左右),频繁擦写可能导致损坏。建议在设计中尽量减少擦写操作。数据对齐:
确保参数结构体的数据对齐与 SPI FLASH 的页大小(通常为 256 字节)匹配,避免跨页写入。数据校验:
在存储和读取参数时,可以添加 CRC 校验或校验和,确保数据的完整性。备份机制:
可以使用乒乓存储策略,将参数存储在两个不同的扇区中,确保在一个扇区损坏时可以从另一个扇区恢复数据。6. 总结
本文介绍了如何在 STM32F103 上使用 SPI 接口操作 QW128 SPI FLASH,并通过乒乓存储的方式实现数据备份。通过这种方式,可以有效地提高数据的可靠性和系统的稳定性。希望本文对大家有所帮助,欢迎在评论区留言讨论。
STM32外设SPIFLASH应用实例由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“STM32外设SPIFLASH应用实例”