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 FLASH
- 5.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 或 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 0xC7void 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 1024uint8_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 FLASHQW128_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(); // 初始化 SPIMX_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 FLASHSaveParamsToFlash(¶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,并通過乒乓存儲的方式實現數據備份。通過這種方式,可以有效地提高數據的可靠性和系統的穩定性。希望本文對大家有所幫助,歡迎在評論區留言討論。