news 2026/4/21 21:12:08

手把手教你用STM32的SPI驱动国产SM25QH128M Flash(附完整工程代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你用STM32的SPI驱动国产SM25QH128M Flash(附完整工程代码)

STM32实战:高效驱动国产SM25QH128M Flash全攻略

在嵌入式系统开发中,外部存储扩展是提升设备数据存储能力的常见需求。国产芯片SM25QH128M作为一款128Mbit容量的NOR Flash存储器,凭借其稳定的性能和兼容SPI接口的特性,正逐渐成为进口芯片的优质替代方案。本文将深入解析如何基于STM32平台实现对该芯片的完整驱动控制。

1. 硬件准备与环境搭建

1.1 芯片选型与硬件连接

SM25QH128M采用标准的8引脚SOIC封装,引脚定义如下:

引脚号名称功能描述
1CS#片选信号(低电平有效)
2SO(IO1)数据输出/IO1
3WP#(IO2)写保护/IO2
4GND
5SI(IO0)数据输入/IO0
6SCK时钟输入
7HOLD#(IO3)保持/IO3
8VCC电源(2.7V-3.6V)

典型连接电路建议:

  • 在VCC和GND之间并联0.1μF去耦电容
  • WP#和HOLD#引脚上拉至VCC(若不使用Quad模式)
  • SPI总线长度尽量缩短,必要时串联22Ω电阻匹配阻抗

1.2 STM32硬件SPI配置

以STM32F407为例,硬件SPI初始化代码如下:

void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; SPI_InitTypeDef SPI_InitStruct = {0}; // 时钟使能 __HAL_RCC_SPI1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // SCK=PA5, MISO=PA6, MOSI=PA7 GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // SPI参数配置 SPI_InitStruct.Mode = SPI_MODE_MASTER; SPI_InitStruct.Direction = SPI_DIRECTION_2LINES; SPI_InitStruct.DataSize = SPI_DATASIZE_8BIT; SPI_InitStruct.CLKPolarity = SPI_POLARITY_LOW; // Mode0 SPI_InitStruct.CLKPhase = SPI_PHASE_1EDGE; SPI_InitStruct.NSS = SPI_NSS_SOFT; SPI_InitStruct.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 21MHz @84MHz PCLK SPI_InitStruct.FirstBit = SPI_FIRSTBIT_MSB; SPI_InitStruct.TIMode = SPI_TIMODE_DISABLE; SPI_InitStruct.CRCCalculation = SPI_CRCCALCULATION_DISABLE; HAL_SPI_Init(&hspi1); }

注意:SM25QH128M支持SPI Mode0和Mode3,实际项目中建议优先使用Mode0以获得更好的兼容性。

2. 基础驱动函数实现

2.1 芯片识别与初始化

可靠的设备识别是驱动开发的第一步:

#define SM25QH128M_MANUFACTURER_ID 0x20 #define SM25QH128M_DEVICE_ID 0x7017 bool SM25QH128M_Init(void) { uint8_t id_buffer[3] = {0}; // 发送复位序列 SM25QH128M_WriteEnable(); SM25QH128M_Reset(); HAL_Delay(10); // 等待复位完成 // 读取JEDEC ID SM25QH128M_ReadID(id_buffer); // 验证制造商和器件ID if(id_buffer[0] != SM25QH128M_MANUFACTURER_ID || (id_buffer[1]<<8 | id_buffer[2]) != SM25QH128M_DEVICE_ID) { return false; } // 检查写保护状态 uint8_t status = SM25QH128M_ReadStatus(); if(status & 0x3C) { // 检查保护位 SM25QH128M_WriteStatus(0x00); // 解除保护 } return true; } void SM25QH128M_ReadID(uint8_t *id_buffer) { uint8_t cmd = 0x9F; // JEDEC ID指令 CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&hspi1, id_buffer, 3, HAL_MAX_DELAY); CS_HIGH(); }

2.2 状态管理与写使能

NOR Flash的写操作需要严格的状态管理:

void SM25QH128M_WriteEnable(void) { uint8_t cmd = 0x06; CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); CS_HIGH(); } bool SM25QH128M_WaitReady(uint32_t timeout_ms) { uint32_t start = HAL_GetTick(); uint8_t status; do { status = SM25QH128M_ReadStatus(); if((status & 0x01) == 0) { // 检查WIP位 return true; } } while(HAL_GetTick() - start < timeout_ms); return false; } uint8_t SM25QH128M_ReadStatus(void) { uint8_t cmd = 0x05; uint8_t status; CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&hspi1, &status, 1, HAL_MAX_DELAY); CS_HIGH(); return status; }

3. 存储操作高级实现

3.1 数据读写优化策略

页编程操作实现:

#define PAGE_SIZE 256 int SM25QH128M_PageProgram(uint32_t addr, const uint8_t *data, uint16_t len) { uint8_t cmd[4] = { 0x02, // 页编程指令 (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF }; // 等待设备就绪 if(!SM25QH128M_WaitReady(100)) return -1; // 启用写操作 SM25QH128M_WriteEnable(); // 发送编程指令和数据 CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY); HAL_SPI_Transmit(&hspi1, (uint8_t*)data, len > PAGE_SIZE ? PAGE_SIZE : len, HAL_MAX_DELAY); CS_HIGH(); return len > PAGE_SIZE ? PAGE_SIZE : len; } int SM25QH128M_Write(uint32_t addr, const uint8_t *data, uint32_t len) { uint32_t written = 0; while(written < len) { uint32_t remaining = len - written; uint32_t page_offset = addr % PAGE_SIZE; uint32_t chunk_size = PAGE_SIZE - page_offset; if(chunk_size > remaining) chunk_size = remaining; int ret = SM25QH128M_PageProgram(addr, data + written, chunk_size); if(ret <= 0) return -1; written += ret; addr += ret; if(!SM25QH128M_WaitReady(100)) return -1; } return written; }

快速读取实现:

int SM25QH128M_FastRead(uint32_t addr, uint8_t *buffer, uint32_t len) { uint8_t cmd[5] = { 0x0B, // 快速读取指令 (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF, 0xFF // dummy byte }; if(!SM25QH128M_WaitReady(100)) return -1; CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 5, HAL_MAX_DELAY); HAL_SPI_Receive(&hspi1, buffer, len, HAL_MAX_DELAY); CS_HIGH(); return len; }

3.2 擦除操作实现

SM25QH128M支持三种擦除粒度:

擦除类型指令大小典型耗时
扇区擦除0x204KB50-200ms
32KB块擦除0x5232KB150-600ms
64KB块擦除0xD864KB300-1200ms
整片擦除0xC7/0x6016MB20-60s

扇区擦除实现示例:

bool SM25QH128M_SectorErase(uint32_t addr) { uint8_t cmd[4] = { 0x20, // 扇区擦除指令 (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF }; if(!SM25QH128M_WaitReady(100)) return false; SM25QH128M_WriteEnable(); CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY); CS_HIGH(); return true; }

4. 性能优化与实战技巧

4.1 SPI时钟优化策略

SM25QH128M支持最高104MHz的快速读取时钟,但在实际应用中需要考虑:

  1. 信号完整性:高频SPI需要良好的PCB布局

    • 保持信号线等长
    • 避免过孔和锐角走线
    • 必要时添加端接电阻
  2. 分频策略表(基于STM32F407 84MHz SPI时钟):

预分频值实际频率适用场景
242MHz短距离、优质PCB布局
421MHz一般应用推荐
810.5MHz长线缆或噪声环境
void SPI_SetSpeed(uint32_t prescaler) { hspi1.Instance->CR1 &= ~SPI_BAUDRATEPRESCALER_256; hspi1.Instance->CR1 |= prescaler; }

4.2 双缓冲读写技术

实现高效连续读写的双缓冲方案:

#define BUFFER_SIZE 512 typedef struct { uint8_t buffer[2][BUFFER_SIZE]; uint8_t active_buf; uint32_t write_addr; } FlashWriter; void FlashWriter_Init(FlashWriter *writer, uint32_t start_addr) { writer->active_buf = 0; writer->write_addr = start_addr; memset(writer->buffer, 0, sizeof(writer->buffer)); } int FlashWriter_Commit(FlashWriter *writer) { uint8_t buf_idx = writer->active_buf; int ret = SM25QH128M_Write(writer->write_addr, writer->buffer[buf_idx], BUFFER_SIZE); if(ret > 0) { writer->write_addr += ret; writer->active_buf ^= 1; // 切换缓冲 memset(writer->buffer[buf_idx], 0, BUFFER_SIZE); } return ret; }

4.3 电源管理与低功耗

SM25QH128M提供多种省电模式:

  1. 深度掉电模式(电流<1μA):

    void Enter_DeepPowerDown(void) { uint8_t cmd = 0xB9; CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); CS_HIGH(); } void Release_DeepPowerDown(void) { uint8_t cmd = 0xAB; CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); CS_HIGH(); HAL_Delay(3); // 唤醒延迟 }
  2. 待机模式(电流约15μA):

    • 通过CS#引脚高电平进入
    • 访问时自动唤醒
  3. 动态时钟调整

    • 非关键操作时降低SPI时钟
    • 批量写入时使用最高效时钟

在实际项目中,将上述驱动代码整合到RT-Thread或FreeRTOS等实时操作系统中时,需要注意:

  • 添加互斥锁保护SPI总线
  • 使用信号量协调读写操作
  • 考虑使用DMA传输减轻CPU负担
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/21 21:12:04

Delphi JSON 助手:告别冗长代码,拥抱简洁操作

1. 为什么我们需要JSON助手类 如果你用过Delphi原生的JSON操作库&#xff0c;一定会被它繁琐的API折磨得够呛。每次操作JSON都要写一堆重复的代码&#xff0c;比如创建一个简单的JSON对象&#xff1a; varjo: TJSONObject; beginjo : TJSONObject.Create;tryjo.AddPair(name, T…

作者头像 李华
网站建设 2026/4/21 21:10:35

XUnity.AutoTranslator:架构深度解析与多语言游戏本地化实践

XUnity.AutoTranslator&#xff1a;架构深度解析与多语言游戏本地化实践 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 在游戏全球化浪潮中&#xff0c;语言本地化已成为决定产品成败的关键因素。XUnity…

作者头像 李华
网站建设 2026/4/21 21:06:45

算法实战笔记:LeetCode 169 多数元素 75 颜色分类

目录 一、169. 多数元素&#xff08;摩尔投票法&#xff0c;O (n) 时间 O (1) 空间&#xff09; 题目描述 核心思路 Java 完整代码 复杂度分析 二、75. 颜色分类&#xff08;三指针&#xff0c;原地排序&#xff09; 题目描述 核心思路 Java 完整代码 复杂度分析 三…

作者头像 李华