当前位置: 首页 > news >正文

STM32 SPI实战:5分钟搞定W25X16 Flash读写(附完整代码)

STM32 SPI实战:5分钟搞定W25X16 Flash读写(附完整代码)

在嵌入式开发中,SPI(Serial Peripheral Interface)是一种常见的高速全双工通信协议,广泛应用于Flash存储、传感器、显示屏等外设的连接。本文将带你快速实现STM32与W25X16 Flash芯片的SPI通信,从硬件连接到代码实现,手把手教你完成芯片ID读取操作。

1. 硬件连接与SPI基础

W25X16是一款16M-bit的串行Flash存储器,采用SPI接口进行通信。在开始编码前,我们需要确保硬件连接正确:

  • SCK(PA5): 时钟信号线
  • MISO(PA6): 主设备输入,从设备输出
  • MOSI(PA7): 主设备输出,从设备输入
  • CS(PA2): 片选信号(低电平有效)

SPI通信有四种工作模式,由CPOL(时钟极性)和CPHA(时钟相位)决定:

模式CPOLCPHA空闲时钟数据采样边沿
000低电平上升沿
101低电平下降沿
210高电平下降沿
311高电平上升沿

W25X16支持模式0和模式3,本文示例将使用模式3(CPOL=1,CPHA=1)。

2. STM32 SPI初始化配置

以下是使用STM32标准外设库配置SPI1为主设备的代码:

void SPI_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // 配置SPI引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; // SCK, MOSI GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // MISO GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置CS引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_2); // CS高电平 // SPI参数配置 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // 模式3 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }

提示:SPI时钟预分频器(BaudRatePrescaler)应根据实际需求设置,对于W25X16,最高支持80MHz时钟频率。

3. SPI数据收发函数实现

SPI通信是全双工的,发送和接收同时进行。以下是基本的字节收发函数:

uint8_t SPI_ReadWriteByte(uint8_t byte) { // 等待发送缓冲区空 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); // 发送数据 SPI_I2S_SendData(SPI1, byte); // 等待接收完成 while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); // 返回接收到的数据 return SPI_I2S_ReceiveData(SPI1); }

4. W25X16 Flash操作实战

4.1 读取设备ID

W25X16提供了几个重要的识别指令,其中读取设备ID(0x90)是最常用的:

uint16_t W25X16_ReadID(void) { uint16_t id = 0; // 拉低CS GPIO_ResetBits(GPIOA, GPIO_Pin_2); // 发送读取ID指令 SPI_ReadWriteByte(0x90); // 发送3字节空地址 SPI_ReadWriteByte(0x00); SPI_ReadWriteByte(0x00); SPI_ReadWriteByte(0x00); // 读取2字节ID id = SPI_ReadWriteByte(0xFF) << 8; id |= SPI_ReadWriteByte(0xFF); // 拉高CS GPIO_SetBits(GPIOA, GPIO_Pin_2); return id; }

W25X16的设备ID应为0xEF14,其中0xEF表示厂商(Winbond),0x14表示设备型号。

4.2 常用Flash操作指令

W25X16支持多种操作指令,以下是几个常用的:

指令名称指令代码描述
写使能0x06允许写入操作
写禁止0x04禁止写入操作
读数据0x03读取Flash数据
页编程0x02写入一页数据(256字节)
扇区擦除0x20擦除4KB扇区
芯片擦除0xC7擦除整个芯片

注意:在执行写入或擦除操作前,必须先发送写使能指令(0x06),并且需要检查忙状态位。

4.3 状态寄存器读取

W25X16有一个状态寄存器,可以通过以下代码读取:

uint8_t W25X16_ReadSR(void) { uint8_t sr; GPIO_ResetBits(GPIOA, GPIO_Pin_2); SPI_ReadWriteByte(0x05); // 读状态寄存器指令 sr = SPI_ReadWriteByte(0xFF); GPIO_SetBits(GPIOA, GPIO_Pin_2); return sr; }

状态寄存器中最重要的位是BUSY位(bit0),当芯片执行写入或擦除操作时,该位为1,操作完成后自动清零。

5. 完整示例:读取ID并打印

结合USART输出,我们可以创建一个完整的示例来验证SPI通信:

#include "stm32f10x.h" #include <stdio.h> // 初始化USART1 void USART1_Init(void) { // USART初始化代码... } // 发送一个字符 void USART1_SendChar(char ch) { // 字符发送代码... } // 发送字符串 void USART1_SendString(char *str) { while(*str) { USART1_SendChar(*str++); } } int main(void) { uint16_t flash_id; char buf[32]; // 初始化外设 USART1_Init(); SPI_Init(); // 读取Flash ID flash_id = W25X16_ReadID(); // 打印结果 sprintf(buf, "Flash ID: 0x%04X\r\n", flash_id); USART1_SendString(buf); while(1) { // 主循环 } }

在实际项目中遇到最多的问题是SPI时钟相位和极性的配置错误,导致通信失败。通过逻辑分析仪抓取波形可以快速定位这类问题。

http://www.jsqmd.com/news/493158/

相关文章:

  • 如何轻松管理Windows右键菜单?ContextMenuManager终极指南
  • SiameseUIE与LangGraph技术结合:知识图谱自动构建
  • 费曼学习法
  • 从崩溃到重生:VScode+Espressif IDF开发环境修复全记录
  • SpringBoot项目集成数据脱敏全攻略:从注解到AOP的优雅实现
  • Cosmos-Reason1-7B在微信小程序开发中的应用:智能生成页面逻辑与云函数
  • AgentCPM深度研报助手:流式输出研究报告,实时观看AI思考过程
  • EcomGPT电商领域大模型效果展示:从模糊描述到精准标签体系构建
  • Phi-3 Forest Laboratory作品集:支持思维链(CoT)显式展开的推理全过程
  • 《从频谱到滤镜:MATLAB频域滤波实战全解析》
  • 无需编程!在CSDN在线环境快速体验清音听真1.7B高精度语音识别
  • Windows安全事件ID全解析:从4624到5159,这些日志你读懂了吗?
  • Kafka性能调优全攻略:从分区数到压缩算法的实战经验分享
  • Visual Studio 2017中Eigen库的配置与矩阵运算实战
  • MedGemma-X效果展示:多维度影像描述报告生成真实输出示例
  • Burp Suite拦截移动端请求全攻略(Android/iOS实测可用)
  • MogFace人脸检测模型效果展示:复杂场景下高精度人脸定位与关键点检测
  • SUNFLOWER MATCH LAB重装系统后快速恢复部署:环境备份与迁移指南
  • 基于 pdf-lib 的图片转PDF工具核心JS实现
  • 如何构建英雄联盟智能辅助工具:League Akari的技术架构与应用实践
  • HY-MT1.5-1.8B在企业文档翻译场景的应用:保持术语一致性
  • 零基础入门YOLOFuse:开箱即用的多模态检测框架,实测效果惊艳
  • DAMOYOLO-S在无人机视觉中的应用:基于嵌入式平台的实时避障
  • 从理论到产品:Coze-Loop优化学术论文算法实现
  • FRCRN单麦降噪实战教程:Gradio Web界面快速搭建与分享
  • ClearerVoice-Studio开源可部署:支持Kubernetes集群化语音处理微服务架构
  • AI超清画质增强镜像部署教程:3步搞定老照片高清修复
  • BGE-Reranker-v2-m3进阶演示:test2.py语义直观分析教程
  • EVA-01视觉系统应用:如何通过企业微信实现图片智能识别
  • 从CSS到Canvas:揭秘海报生成中文本排版的核心算法与实战