W25Qxx系列是Winbond公司推出的SPI接口NOR Flash存储器,采用串行外设接口(SPI)协议进行通信。这类芯片在嵌入式系统中广泛用于固件存储、配置参数保存、日志记录等场景。其典型特点包括:
注意:不同型号的电压范围可能不同,常见有3.3V和1.8V版本,混用会导致通信失败或硬件损坏。
通过UART控制SPI Flash的核心是在两个不同协议间建立转换桥梁。典型实现包含以下组件:
硬件连接示意图:
code复制[上位机] --UART--> [MCU] --SPI--> [W25Qxx]
协议转换的核心是命令解析与转发:
例如擦除命令:
code复制ERASE:SECTOR,0x1000\n
转换为:
基础命令集应包含以下功能:
| 命令格式 | 功能说明 | SPI对应操作 |
|---|---|---|
| READ:id,addr,len | 读取数据 | 0x03 + 地址 |
| WRITE:id,addr,data | 写入数据 | 0x02 + 地址 |
| ERASE:TYPE,addr | 擦除操作 | 0x20/0x52/0xD8 |
| ID:id | 读取芯片ID | 0x9F |
| STATUS:id | 读状态寄存器 | 0x05 |
提示:建议每条命令以\n结尾,方便行解析。数据部分可采用HEX编码。
以STM32 HAL库为例,关键配置参数:
c复制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;
关键操作函数示例:
c复制void Flash_WriteEnable(void) {
uint8_t cmd = 0x06;
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
}
uint32_t Flash_ReadID(void) {
uint8_t cmd[4] = {0x9F, 0, 0, 0};
uint8_t id[3];
HAL_SPI_TransmitReceive(&hspi1, cmd, id, 4, 100);
return (id[0]<<16)|(id[1]<<8)|id[2];
}
推荐采用ASCII可读格式:
code复制[命令]:[参数1],[参数2],...[参数N]\n
例如:
code复制READ:1,0xA000,256
WRITE:1,0xA000,48656C6C6F
ERASE:SECTOR,0xA000
响应格式:
code复制[状态]:[数据]\n
例如:
code复制OK:576F726C64
ERROR:ADDR_INVALID
典型处理流程:
关键代码结构:
c复制void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(rx_buffer[rx_index] == '\n') {
ProcessCommand(rx_buffer);
rx_index = 0;
} else {
rx_index++;
}
HAL_UART_Receive_IT(huart, &rx_buffer[rx_index], 1);
}
SPI无响应:
UART数据错误:
批量操作优化:
可靠性增强:
可在基础驱动上实现简易文件系统:
c复制typedef struct {
char name[16];
uint32_t size;
uint32_t checksum;
uint32_t timestamp;
} FileHeader;
c复制int FS_WriteFile(const char* name, void* data, uint32_t size) {
// 查找空闲区域
// 写入文件头
// 写入数据内容
// 更新分配表
}
写保护控制:
数据加密:
c复制void EncryptWrite(uint32_t addr, void* data, uint32_t len) {
AES_Encrypt(data, encrypted, key);
Flash_Write(addr, encrypted, ALIGN(len,16));
}
典型实现流程:
关键代码段:
c复制void OTA_Update(void) {
Flash_Erase(APP_ADDR, APP_SIZE);
for(int i=0; i<fw_size; i+=256) {
Flash_Write(APP_ADDR+i, fw_data+i, 256);
if(memcmp(Flash_Read(APP_ADDR+i,256),fw_data+i,256)) {
// 写入校验失败
break;
}
}
Flash_Write(BOOT_FLAG_ADDR, &NEW_FW_FLAG, 1);
NVIC_SystemReset();
}
环形缓冲区实现方案:
c复制typedef struct {
uint32_t timestamp;
uint16_t event_id;
uint8_t data[16];
} LogEntry;
c复制void Log_Write(uint16_t id, void* data) {
uint32_t addr = LOG_BASE + (log_index * sizeof(LogEntry));
if(addr >= LOG_END) {
log_index = 0;
addr = LOG_BASE;
}
Flash_Write(addr, &(LogEntry){HAL_GetTick(),id,data}, sizeof(LogEntry));
log_index++;
}
推荐配置:
典型问题诊断:
速度测试指标:
寿命测试方案:
信号完整性:
电源设计:
c复制uint8_t comm_buffer[256]; // 用于UART和SPI复用
void ProcessUART() {
// 使用comm_buffer接收UART数据
SPI_Transmit(comm_buffer); // 复用同一缓冲区
}
c复制// 配置SPI DMA
hdma_spi1_tx.Instance = DMA1_Channel3;
hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
HAL_DMA_Init(&hdma_spi1_tx);
__HAL_LINKDMA(&hspi1, hdmatx, hdma_spi1_tx);
// 使用DMA传输
HAL_SPI_Transmit_DMA(&hspi1, data, len);
实现示例:
c复制void EnterLowPower() {
Flash_PowerDown();
HAL_SPI_DeInit(&hspi1);
GPIO_InitTypeDef gpio = {0};
gpio.Pin = FLASH_CS_PIN;
gpio.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(FLASH_GPIO_PORT, &gpio);
}