STM32外设SPI FLASH应用实例
1. 前言
在嵌入式系统中,数据的存储和备份是一个非常重要的功能。SPI FLASH 是一种常见的非易失性存储器,具有容量大、速度快、接口简单等优点。本文将介绍如何在 STM32F103 上使用 SPI 接口操作 QW128 SPI FLASH,并通过乒乓存储的方式实现数据备份。
1.1 硬件准备
- STM32F103 开发板
- QW128 SPI FLASH 模块
- 杜邦线若干
1.2 软件准备
- Keil MDK 或 STM32CubeIDE
- STM32 HAL 库
2. 硬件连接
将 QW128 SPI FLASH 模块与 STM32F103 开发板连接,具体连接方式如下:
QW128 引脚 | STM32F103 引脚 |
---|---|
CS | PA4 |
SCK | PA5 |
MISO | PA6 |
MOSI | PA7 |
GND | GND |
VCC | 3.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,并通过乒乓存储的方式实现数据备份。通过这种方式,可以有效地提高数据的可靠性和系统的稳定性。希望本文对大家有所帮助,欢迎在评论区留言讨论。