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

STM32CubeMX-H7-15-SPI通信协议读写W25Q64

前言

SPI(Serial Peripheral Interface)通信协议是一种高速、全双工、同步的串行通信协议

本篇文章就使用W25Q64模块来学习SPI,包括软件SPI代码的编写硬件SPI中断SPIDMA+SPI

SPI的应用场景和模块

        !这里是抄AI的,主要是也要了解学了之后有什么用

  • 存储设备:如闪存芯片(如 W25Q64 等)与微控制器的连接。SPI 接口可实现高速的数据读写,用于程序代码存储、数据存储等,支持频繁的擦除和写入操作,满足设备对数据存储和快速访问的需求。
  • 传感器:许多传感器如温度传感器、压力传感器、加速度传感器等,采用 SPI 接口与主控芯片通信。传感器将采集到的模拟信号转换为数字信号后,通过 SPI 协议快速传输给主控芯片进行处理,以实现对物理量的精确测量和实时监测。
  • 显示设备:SPI 常用于连接微控制器与液晶显示器(LCD)或有机发光二极管显示器(OLED)的驱动芯片。能够高效地传输图像数据和控制指令,实现显示屏的快速刷新和高分辨率显示,满足不同显示场景的需求。
  • 音频设备:在音频编解码器、数字音频放大器等音频设备中,SPI 用于传输音频数据和控制信息。可以实现高质量的音频数据传输,支持多种音频格式和采样率,确保音频信号的准确还原和播放。
  • 无线通信模块:如 Wi-Fi 模块、蓝牙模块等与主控芯片之间常通过 SPI 接口进行通信。SPI 协议能够满足无线通信模块与主控芯片之间高速数据传输的需求,实现设备间的短距离无线数据传输和通信。
  • 现场可编程门阵列(FPGA)配置:在一些系统中,通过 SPI 接口将配置数据加载到 FPGA 中,实现对 FPGA 的功能配置和初始化。这种方式简单可靠,能够在系统启动时快速完成 FPGA 的配置,确保其正常工作。

SPI信号线

  1. 主设备出、从设备入(MOSI):主设备通过这条线向从设备传输数据,也被称为从设备输入线(SI 或 SDI)。连接设备DI引脚
  2. 主设备入、从设备出(MISO):从设备利用这条线向主设备发送数据,也叫从设备输出线(SO 或 SDO)。连接设备DO引脚
  3. 串行时钟(SCLK):由主设备产生的时钟信号,用于同步主从设备之间的数据传输,从设备根据 SCLK 的节奏来接收或发送数据。
  4. 从设备选择(SS):也称为片选信号线,低电平有效。主设备通过控制这条线来选择要与之通信的从设备。当有多个从设备时,每个从设备都有独立的 SS 引脚与主设备相连,主设备通过拉低相应从设备的 SS 引脚来使能该从设备进行数据传输。

SPI时序

        spi的时序有四种,但是我们会一种就够了。

        我们学习IIC的时候知道,在

        CLK高电平的时候是设备读取数据的时间,此时SDA不能改变数据

        只有在CLK为低电平的时候,SDA才能改变数据电平

       

        那么SPI也是一样的道理

        这里是SPI的一种模式

        就是在CLK上升沿的时候,从设备和主设备读数据

        在下降沿的时候,从设备和主设备也修改数据

        然后都是从最高位开始发送

        因为是上升沿和下降沿的时候实现读写,而且时钟线又是独立的可快可慢,所以SPI的速度可以到几M甚至几十M每秒。

        

uint8_t SPI_read_write_byte(uint8_t byte)
{uint8_t i;uint8_t data = 0;// 发送一个字节数据for (i = 0; i < 8; i++){SPI_CLK_LOW();  // 拉低时钟if (byte & 0x80)  // 判断最高位是否为1{SPI_MOSI_HIGH();  // 发送1}else{SPI_MOSI_LOW();  // 发送0}byte <<= 1;  // 左移一位SPI_CLK_HIGH();  // 拉高时钟data <<= 1;  // 左移一位if (SPI_MISO_READ())  // 读取MISO信号{data |= 0x01;  // 读取到1} else{data &= 0xFE;  // 读取到0}}return data;  // 返回读取到的数据
}

        这个是SPI的核心时序代码,是执行一次读写八位的操作

        跟IIC不同

        IIC是一条8位指令,只能写或者读

        CLK高电平的时候,不是主设备写从设备读,就是从设备写主设备读

        

        但是SPI是可以同时写和读的

        一条八位指令,同时完成读和写

        CLK高电平的时候,主设备从设备

        CLK低电平的时候,从设备主设备

        可以有多总理解方法,像我这样理解也行

        SPI通信就是围绕上面的一条函数来执行

        基本上,后面我们就使用这一个函数,来实现对W25Q64的读写

W25Q64--EEPROM数据存储模块

       以下是跟AI搜集到的资料,稍微简单了解下模块

W25Q64 是华邦公司推出的一款大容量 SPI FLASH 产品,以下将从其基本参数、性能特点、应用场景等方面进行详细介绍:

  • 基本参数
    • 存储容量:容量为 64Mb,即 8M 字节。它将 8M 字节的容量分为 128 个块,每个块大小为 64K 字节,每个块又分为 16 个扇区,每个扇区 4K 字节2
    • 封装形式:常见的有 8 引脚 SOIC 208 mil、8 焊盘 XSON 4x4 mm、8 焊垫 USON 4x3 mm、8 垫 WSON 6x5 mm/8x6 mm、24 球 TFBGA 8x6 mm(5x5 球阵列)、12 球 WLCSP 等3
  • 性能特点
    • 多种 SPI 接口支持:支持标准的 SPI 接口,还支持双输出 / 四输出的 SPI 接口,可满足不同系统对数据传输速率的要求。在双输出 SPI 模式下,等效时钟频率可达 160MHz;在四输出 SPI 模式下,等效时钟频率可达 320MHz256
    • 高性能串行闪存:高达普通串行闪存性能的 6 倍,支持最高 133MHz 的 SPI 时钟频率,当使用 SPI 快速读取 Quad I/O 指令时,允许 Quad I/O 的等效时钟速率为 532MHz(133MHz x 4),数据连续传输速率可达 66MB/S23
    • 高效的连续读取模式:具有低指令开销,仅需 8 个时钟周期处理内存,允许 XIP(Execute In Place)操作,即可以直接从闪存中执行代码,性能优于 X16 并行闪存2
    • 低功耗与宽温度范围:单电源供电,电压范围为 1.7V 至 1.95V,断电时电流消耗低至 1µA。正常运行温度范围为 - 40°C 至 + 85°C,部分型号可支持到 + 105°C,适用于各种恶劣环境3
    • 灵活的擦写操作:扇区统一擦除大小为 4KB,支持 1 到 256 个字节编程,擦写周期多达 10 万次以上,数据可保存达 20 年之久23
    • 高级安全功能:支持 JEDEC 标准制造商和设备 ID、64 位唯一序列号和三个 256 字节的安全寄存器。提供软件和硬件写保护、特殊 OTP 保护、顶部 / 底部互补阵列保护、单个块 / 扇区阵列保护等多种数据保护功能3
  • 应用场景
    • 嵌入式系统:作为嵌入式系统中的非易失性存储解决方案,可存储固件、操作系统和应用程序代码,如智能家居设备、工业控制器、医疗设备等。
    • 消费电子:在智能手机、平板电脑、数码相机、智能手表、健康监测设备等消费电子产品中,用于存储用户数据、应用程序、媒体文件和用户设置等。
    • 汽车电子:适用于汽车电子系统,如存储发动机控制单元(ECU)的固件、导航系统的数据和其他关键信息。
    • 通信设备:可用于通信基站、路由器等设备中,存储网络配置和固件更新。
    • 计算机外设:在打印机、扫描仪等计算机外设中,存储设备固件和驱动程序。
    • 智能卡和安全令牌:因其具有安全性特性,可用于智能卡和安全令牌中存储敏感信息。
    • 数据记录器:在需要数据记录和回放的应用中,如安全监控系统、医疗设备等,可用于存储数据。

字节、扇区、块、页的区别

        这个是存储空间的概念,基本上后面都会涉及,还是要做一些了解吧,比如这个模块擦除是以扇区为单位,不知道扇区就不好理解

  • 字节:是存储容量的基本单位,1 字节等于 8 位二进制数。W25Q64 的存储容量为 64Mbit,换算成字节就是 8MByte,即该芯片可以存储 8×1024×1024 个字节的数据,每个字节都有唯一的地址,通过地址可以对芯片中的数据进行读写操作1
  • :是闪存芯片中最小的可擦除单元,在 W25Q64 中,每页大小为 256 字节。页适用于需要频繁读写且存储小量数据的场景,如缓存、寄存器、配置信息等。可以对页进行独立的读取、写入和擦除操作,但写入时一次最多只能写入 256 字节的数据,如果超过 256 字节,就需要分多次写入或者进行跨页操作45
  • 扇区:是存储器中的逻辑分区,由多个页组成。在 W25Q64 中,16 个页组成一个扇区,所以一个扇区的大小是 4096 字节(即 4KB)。扇区适用于中等大小的数据存储和操作,如文件系统、日志记录等。扇区也是闪存芯片中常用的擦除单位,通常不能单独擦除一个页,而是要以扇区为单位进行擦除,这意味着在擦除扇区内的数据时,会将整个扇区的内容都清除245
  • :是存储器中的逻辑分块,由多个扇区组成。W25Q64 中,16 个扇区组成一个块,所以每个块的大小为 64KB。块适用于大容量数据存储,如磁盘分区、应用程序和媒体文件等。块通常是最大可擦除单元,擦除一个块将清除该块内的所有数据4

        W25Q64 将 8M 的容量分为 128 个块,每个块又分为 16 个扇区,每个扇区包含 16 个页2。这种分级结构有助于更高效地管理和操作闪存芯片中的数据,根据不同的应用场景和数据量大小,可以选择不同的存储单元进行读写和擦除操作。

页地址、扇区地址、块地址

都是在驱动模块前需要了解的基本资料

1. 页(Page)

  • 每页大小:256 字节($2^8$ 字节)。
  • 页数量:总容量除以每页大小,即 $\frac{8\times1024\times1024}{256}= 32768$ 页。
  • 地址表示:页地址由高 16 位确定,低 8 位用于页内偏移。例如,地址 0x000000 到 0x0000FF 是第 0 页,0x000100 到 0x0001FF 是第 1 页,依此类推。

2. 扇区(Sector)

  • 每扇区大小:4 KB($4\times1024 = 2^{12}$ 字节)。
  • 扇区数量:总容量除以每扇区大小,即 $\frac{8\times1024\times1024}{4\times1024}= 2048$ 个扇区。
  • 地址表示:扇区地址由高 12 位确定,低 12 位用于扇区内偏移。例如,地址 0x000000 到 0x000FFF 是第 0 扇区,0x001000 到 0x001FFF 是第 1 扇区。

3. 块(Block)

  • 每块大小:64 KB($64\times1024 = 2^{16}$ 字节)。
  • 块数量:总容量除以每块大小,即 $\frac{8\times1024\times1024}{64\times1024}= 128$ 个块。
  • 地址表示:块地址由高 8 位确定,低 16 位用于块内偏移。例如,地址 0x000000 到 0x00FFFF 是第 0 块,0x010000 到 0x01FFFF 是第 1 块。

        

使用SPI获取W25Q64制造商 ID和设备 ID 

        首先初始化和确定好引脚

        在.h文件定义引脚和功能函数

这样子可以就是复制代码,然后直接在.h处修改函数就可以改引脚就可以使用了

其实也可以自己在Cubemx配置,只不过我是觉得这样子使用方便

hal_spi.h

#ifndef HAL_SPI_H
#define HAL_SPI_H#include "main.h"#define SPI_CS_PIN    GPIO_PIN_0   //连接模块cs
#define SPI_CS_PORT   GPIOA        //连接模块cs#define SPI_CLK_PIN   GPIO_PIN_2   //连接模块clk
#define SPI_CLK_PORT  GPIOA        //连接模块clk#define SPI_MOSI_PIN  GPIO_PIN_3   //连接模块mosi
#define SPI_MOSI_PORT GPIOA        //连接模块mosi#define SPI_MISO_PIN  GPIO_PIN_1   //连接模块miso
#define SPI_MISO_PORT GPIOA        //连接模块miso
// 片选操作宏定义
#define SPI_CS_LOW()    HAL_GPIO_WritePin(SPI_CS_PORT, SPI_CS_PIN, GPIO_PIN_RESET)
#define SPI_CS_HIGH()   HAL_GPIO_WritePin(SPI_CS_PORT, SPI_CS_PIN, GPIO_PIN_SET)// 时钟信号操作宏定义
#define SPI_CLK_LOW()   HAL_GPIO_WritePin(SPI_CLK_PORT, SPI_CLK_PIN, GPIO_PIN_RESET)
#define SPI_CLK_HIGH()  HAL_GPIO_WritePin(SPI_CLK_PORT, SPI_CLK_PIN, GPIO_PIN_SET)// MOSI信号操作宏定义
#define SPI_MOSI_LOW()  HAL_GPIO_WritePin(SPI_MOSI_PORT, SPI_MOSI_PIN, GPIO_PIN_RESET)
#define SPI_MOSI_HIGH() HAL_GPIO_WritePin(SPI_MOSI_PORT, SPI_MOSI_PIN, GPIO_PIN_SET)// MISO信号操作宏定义
#define SPI_MISO_READ() HAL_GPIO_ReadPin(SPI_MISO_PORT, SPI_MISO_PIN)// 初始化SPI
void HAL_SPI_Init(void);// 发送一个字节数据
uint8_t SPI_read_write_byte(uint8_t byte);#endif

在.c处添加初始化函数和SPI通信函数

第二个函数就是核心
 

hal_spi.c

#include "hal_spi.h"
#include "gpio.h"// 软件SPI初始化
void HAL_SPI_Init(void)
{// 初始化GPIOGPIO_InitTypeDef GPIO_InitStruct = {0};__HAL_RCC_GPIOA_CLK_ENABLE();  // 使能GPIOA时钟// 配置CS引脚为输出模式GPIO_InitStruct.Pin = SPI_CS_PIN ;  // 连接模块csGPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出模式GPIO_InitStruct.Pull = GPIO_NOPULL;  // 无上下拉GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;  // 高速HAL_GPIO_Init(SPI_CS_PORT, &GPIO_InitStruct);SPI_CS_HIGH();  // 初始状态为高电平// 配置CLK引脚为输出模式GPIO_InitStruct.Pin = SPI_CLK_PIN;  // 连接模块clkHAL_GPIO_Init(SPI_CLK_PORT, &GPIO_InitStruct);SPI_CLK_HIGH();  // 初始状态为高电平// 配置MOSI引脚为输出模式GPIO_InitStruct.Pin = SPI_MOSI_PIN;  // 连接模块mosiHAL_GPIO_Init(SPI_MOSI_PORT, &GPIO_InitStruct);// 配置MISO引脚为输入模式GPIO_InitStruct.Pin = SPI_MISO_PIN;  // 连接模块misoGPIO_InitStruct.Mode = GPIO_MODE_INPUT;  // 输入模式HAL_GPIO_Init(SPI_MISO_PORT, &GPIO_InitStruct);SPI_CLK_HIGH();  // 初始状态为高电平SPI_MOSI_HIGH();  // 初始状态为高电平
}// 软件SPI读写一个字节
uint8_t SPI_read_write_byte(uint8_t byte)
{uint8_t i;uint8_t data = 0;// 发送一个字节数据for (i = 0; i < 8; i++){SPI_CLK_LOW();  // 拉低时钟if (byte & 0x80)  // 判断最高位是否为1{SPI_MOSI_HIGH();  // 发送1}else{SPI_MOSI_LOW();  // 发送0}byte <<= 1;  // 左移一位SPI_CLK_HIGH();  // 拉高时钟data <<= 1;  // 左移一位if (SPI_MISO_READ())  // 读取MISO信号{data |= 0x01;  // 读取到1} else{data &= 0xFE;  // 读取到0}}return data;  // 返回读取到的数据
}

初始化函数

void W25Q64_Init(void)
{HAL_SPI_Init();  // 初始化SPISPI_CS_HIGH();HAL_Delay(10);  // 延时10ms
}

获取ID函数

void get_w25q64_id(uint8_t * ManufacturerID, uint16_t * DeviceID )
{SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0x9F);  // 发送读取Jedec ID指令*ManufacturerID = SPI_read_write_byte(0x00);  // 读取Manufacturer ID*DeviceID = SPI_read_write_byte(0x00) ;*DeviceID <<= 8;*DeviceID |= SPI_read_write_byte(0x00);  // 读取Device IDSPI_CS_HIGH();  // 片选拉高,结束通信
}

然后直接在主函数调用就OK了

利用串口助手,可以看见已经成功获取ID了

后面还有对页,快,扇区的擦除和写

然后直接给上完整的软件SPI驱动W25Q64

w25164.c

#include "w25q64.h"
#include "hal_spi.h"
#include <stdio.h>void W25Q64_Init(void)
{HAL_SPI_Init();  // 初始化SPISPI_CS_HIGH();HAL_Delay(10);  // 延时10ms
}void get_w25q64_id(uint8_t * ManufacturerID, uint16_t * DeviceID ){SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0x9F);  // 发送读取Jedec ID指令*ManufacturerID = SPI_read_write_byte(0x00);  // 读取Manufacturer ID*DeviceID = SPI_read_write_byte(0x00) ;*DeviceID <<= 8;*DeviceID |= SPI_read_write_byte(0x00);  // 读取Device IDSPI_CS_HIGH();  // 片选拉高,结束通信}void W25Q64_WriteEnable(void)
{SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0x06);  // 发送写使能指令SPI_CS_HIGH();  // 片选拉高,结束通信
}void W25Q64_WriteDisable(void)
{SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0x04);  // 发送写禁止指令SPI_CS_HIGH();  // 片选拉高,结束通信
}void W25Q64_ReadStatusReg(uint8_t * StatusReg)//读取状态寄存器
{SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0x05);  // 发送读取状态寄存器指令*StatusReg = SPI_read_write_byte(0x00);  // 读取状态寄存器SPI_CS_HIGH();  // 片选拉高,结束通信
}void W25Q64_WriteStatusReg(uint8_t StatusReg)//写状态寄存器
{SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0x01);  // 发送写状态寄存器指令SPI_read_write_byte(StatusReg);  // 写入状态寄存器SPI_CS_HIGH();  // 片选拉高,结束通信
}
void W25Q64_ReadData(uint8_t * Data, uint32_t Address, uint32_t Size)//读取数据
{uint8_t i;SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0x03);  // 发送读取数据指令SPI_read_write_byte((Address >> 16) & 0xFF);  // 发送地址高8位SPI_read_write_byte((Address >> 8) & 0xFF);  // 发送地址中8位SPI_read_write_byte(Address & 0xFF);  // 发送地址低8位for (i = 0; i < Size; i++)  // 读取数据{Data[i] = SPI_read_write_byte(0x00);  // 读取数据}SPI_CS_HIGH();  // 片选拉高,结束通信 
}void W25Q64_WriteData(uint8_t * Data, uint32_t Address, uint32_t Size)//写入数据
{uint8_t i;W25Q64_WriteEnable();  // 写使能SPI_CS_LOW();  // 片选拉低,开始通信 SPI_read_write_byte(0x02);  // 发送写数据指令SPI_read_write_byte((Address >> 16) & 0xFF);  // 发送地址高8位SPI_read_write_byte((Address >> 8) & 0xFF);  // 发送地址中8位SPI_read_write_byte(Address & 0xFF);  // 发送地址低8位for (i = 0; i < Size; i++)  // 写入数据{SPI_read_write_byte(Data[i]);  // 写入数据}SPI_CS_HIGH();  // 片选拉高,结束通信// 轮询状态寄存器,等待写操作完成uint8_t status;do {W25Q64_ReadStatusReg(&status);} while (status & 0x01); // 当状态寄存器的第0位为1时,表示芯片正在忙
}void W25Q64_EraseSector(uint32_t Address)//擦除扇区
{W25Q64_WriteEnable();  // 写使能SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0x20);  // 发送扇区擦除指令SPI_read_write_byte((Address >> 16) & 0xFF);  // 发送地址高8位SPI_read_write_byte((Address >> 8) & 0xFF);  // 发送地址中8位SPI_read_write_byte(Address & 0xFF);  // 发送地址低8位SPI_CS_HIGH();  // 片选拉高,结束通信// 轮询状态寄存器,等待擦除完成uint8_t status;do {W25Q64_ReadStatusReg(&status);} while (status & 0x01); // 当状态寄存器的第0位为1时,表示芯片正在忙
}void W25Q64_EraseBlock(uint32_t Address)//擦除块
{W25Q64_WriteEnable();  // 写使能SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0xD8);  // 发送块擦除指令SPI_read_write_byte((Address >> 16) & 0xFF);  // 发送地址高8位SPI_read_write_byte((Address >> 8) & 0xFF);  // 发送地址中8位SPI_read_write_byte(Address & 0xFF);  // 发送地址低8位SPI_CS_HIGH();  // 片选拉高,结束通信HAL_Delay(50);  // 等待擦除完成  
}
void W25Q64_EraseChip(void)//擦除芯片
{W25Q64_WriteEnable();  // 写使能SPI_CS_LOW();  // 片选拉低,开始通信SPI_read_write_byte(0xC7);  // 发送芯片擦除指令SPI_CS_HIGH();  // 片选拉高,结束通信HAL_Delay(50);  // 等待擦除完成   
}void W25Q64_Verify(void) {//这个是测试程序uint8_t ManufacturerID;uint16_t DeviceID;uint8_t writeData[] = {0x11, 0x22, 0x33, 0x44};uint8_t readData[4];uint32_t address = 0x000000;uint8_t status;int i;// 初始化 W25Q64W25Q64_Init();// 读取芯片 IDget_w25q64_id(&ManufacturerID, &DeviceID);printf("Manufacturer ID: 0x%02X, Device ID: 0x%04X\n", ManufacturerID, DeviceID);// 擦除扇区W25Q64_EraseSector(address);// 读取状态寄存器W25Q64_ReadStatusReg(&status);printf("Status Register after sector erase: 0x%02X\n", status);// 写入数据W25Q64_WriteData(writeData, address, sizeof(writeData));// 读取状态寄存器W25Q64_ReadStatusReg(&status);printf("Status Register after write data: 0x%02X\n", status);// 读取数据W25Q64_ReadData(readData, address, sizeof(readData));// 验证数据printf("Read data: ");for (i = 0; i < sizeof(readData); i++) {printf("0x%02X ", readData[i]);}printf("\n");if (memcmp(writeData, readData, sizeof(writeData)) == 0) {printf("Data verification passed!\n");} else {printf("Data verification failed!\n");}
}

w25164.h

#ifndef W25Q64_H
#define W25Q64_H#include "main.h"void W25Q64_Init(void);
void get_w25q64_id(uint8_t * ManufacturerID, uint16_t * DeviceID );//读取ID
void W25Q64_WriteEnable(void);//写使能
void W25Q64_WriteDisable(void);//写禁止
void W25Q64_ReadStatusReg(uint8_t * StatusReg);//读取状态寄存器
void W25Q64_WriteStatusReg(uint8_t StatusReg);//写状态寄存器
void W25Q64_ReadData(uint8_t * Data, uint32_t Address, uint32_t Size);//读取数据
void W25Q64_WriteData(uint8_t * Data, uint32_t Address, uint32_t Size);//写数据
void W25Q64_EraseSector(uint32_t Address);//擦除扇区
void W25Q64_EraseBlock(uint32_t Address);//擦除块
void W25Q64_Verify(void);
#endif

调用测试程序的结果

可以发现成功实现读写

    一般我们使用这个模块主要是存储变量数据,可以直接参考我 我之前的文章

    https://blog.csdn.net/m0_74211379/article/details/146343170?fromshare=blogdetail&sharetype=blogdetail&sharerId=146343170&sharerefer=PC&sharesource=m0_74211379&sharefrom=from_link

    使用模块常见问题

         1.如果扇区和块使用超了会怎么样

            

    • 数据覆盖:W25Q64 芯片的存储容量是固定的,当扇区和块被全部使用后,若继续写入新数据,会覆盖之前存储在芯片中的旧数据。例如,在对一个已经写满数据的扇区再次写入数据时,新数据会直接覆盖该扇区原有的数据,导致旧数据丢失。
    • 写入失败:部分系统可能会对存储操作进行检查,当检测到没有可用的扇区或块时,会拒绝新的数据写入请求,并返回错误信息,告知用户存储已满无法写入。

            2.如果给定的地址超了会怎么样

            W25Q64 芯片本身不会对超出有效地址范围的输入进行检查和提示。当你发送一个超出块数量的地址(如对应块号 129 的地址)进行擦除、写入等操作时,芯片可能会忽略这个非法地址,不执行相应操作,或者将操作映射到内部合法的地址空间,但这并非预期行为,可能导致数据混乱。

     硬件SPI驱动

    基本的方式

    软件模拟时序结束了,接下来我们来弄硬件SPI

    首先是在Cubemx这样配置

    注意那个速度要在几M,不要太快,之前我68M直接卡死

    然后编写框架代码,其实使用硬件的一个函数就够了,我们只需要进行简单封装,就能像软件那样调用了。

    HAL_SPI_TransmitReceive(&hspi1, &data, &rx_data, 1,100);  // 发送数据并接收 

    就是这一个函数

    uint8_t hal_SPI_txrx(uint8_t data) {uint8_t rx_data = 0;HAL_SPI_TransmitReceive(&hspi1, &data, &rx_data, 1,100);  // 发送数据并接收 return rx_data;  // 返回接收到的数据
    }

    封装成我们软件SPI的样子

    void GET_W25Q64_JEDEC_ID(uint8_t *manufacturer_id, uint16_t *device_id) 
    {CS_LOW();  // 拉低片选信号,选中 W25Q64hal_SPI_txrx(0x9F);  // 发送JEDEC ID命令*manufacturer_id = hal_SPI_txrx(0x00);  // 接收制造商ID*device_id = (hal_SPI_txrx(0x00) << 8) | hal_SPI_txrx(0x00);  // 接收设备ID// 组合设备ID的高8位和低8位CS_HIGH();  // 拉高片选信号,取消选中 W25Q64
    }

    获取ID函数

    后面也是成功获取

    完整代码

    hal_w25q64.c

    #include "hal_w25q64.h"
    #include "gpio.h"  // 假设GPIO操作头文件
    #include "spi.h"uint8_t hal_SPI_txrx(uint8_t data) {uint8_t rx_data = 0;HAL_SPI_TransmitReceive(&hspi1, &data, &rx_data, 1,100);  // 发送数据并接收 return rx_data;  // 返回接收到的数据
    }void GET_W25Q64_JEDEC_ID(uint8_t *manufacturer_id, uint16_t *device_id) 
    {CS_LOW();  // 拉低片选信号,选中 W25Q64hal_SPI_txrx(0x9F);  // 发送JEDEC ID命令*manufacturer_id = hal_SPI_txrx(0x00);  // 接收制造商ID*device_id = (hal_SPI_txrx(0x00) << 8) | hal_SPI_txrx(0x00);  // 接收设备ID// 组合设备ID的高8位和低8位CS_HIGH();  // 拉高片选信号,取消选中 W25Q64
    }// 其他函数的实现可以参考上面的代码

    hal_w25q64.h

    #ifndef HAL_W25Q64_H
    #define HAL_W25Q64_H#include "main.h"#define CS_PIN GPIO_PIN_4 // 片选引脚
    #define CS_PORT GPIOA // 片选引脚所在的 GPIO 端口
    // 片选引脚控制函数
    #define CS_LOW() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET) // 片选低电平
    #define CS_HIGH() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET) // 片选高电平// 函数声明
    uint8_t hal_SPI_txrx(uint8_t data); // SPI 数据发送和接收函数void GET_W25Q64_JEDEC_ID(uint8_t *manufacturer_id, uint16_t *device_id) ; // 获取 W25Q64 的 JEDEC ID
    #endif /* HAL_W25Q64_H */

    然后如果要使用中断和DMA的方式,其实也是一样的道理

    中断的方式

    // 中断服务函数
    void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
    {// 处理发送完成事件
    }void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
    {// 处理接收完成事件
    }// 启动传输
    HAL_SPI_Transmit_IT(&hspi1, tx_buffer, tx_length);
    HAL_SPI_Receive_IT(&hspi1, rx_buffer, rx_length);

    DMA的方式

    // 启动 DMA 传输
    HAL_SPI_Transmit_DMA(&hspi1, tx_buffer, tx_length);
    HAL_SPI_Receive_DMA(&hspi1, rx_buffer, rx_length);// DMA 传输完成回调函数
    void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
    {// 处理发送完成事件
    }void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
    {// 处理接收完成事件
    }

    这样子我们软件和硬件SPI就弄完了

    相关文章:

  • 第11篇:Linux程序访问控制FPGA端HEX<四>
  • C++23 新特性:行拼接前去除空白符 (P2223R2)
  • 相比其他缓存/内存数据库(如 Memcached, Ehcache 等),Redis 在微服务环境中的优势和劣势是什么?
  • 浪涌保护器:纳秒盾支持2/4/8线制,全面防护信号、电源及网络设备 智能防雷设备
  • 力扣每日打卡 2364. 统计坏数对的数目 (中等)
  • 【java 13天进阶Day06】Map集合,HashMapTreeMap,斗地主、图书管理系统,排序算法
  • 硬盘变废为宝!西部数据携微软等启动稀土回收 效率可达90%
  • 学习threejs,使用EffectComposer后期处理组合器(采用RenderPass、GlitchPass渲染通道)
  • 自动驾驶与机器人算法学习
  • 数据结构--并查集-高效处理连通性问题
  • 细节:如何制作高质量的VR全景图
  • Yocto项目实战教程 · 第4章:4.2小节-菜谱
  • 「GitHub热榜」AIGC系统源码:AI问答+绘画+PPT+音乐生成一站式
  • GreatSQL启动崩溃:jemalloc依赖缺失问题排查
  • ReAct、CoT 和 ToT:大模型提示词推理架构的对比分析
  • windwos脚本 | 基于scrcpy,只投声音、只投画面
  • DNS解析失败怎么解决?
  • 基于 S2SH 架构的企业车辆管理系统:设计、实现与应用
  • 深入理解synchronized
  • Linux和Ubuntu的驱动适配情况
  • 毕节两兄弟摘马蜂窝致路人被蜇去世,涉嫌过失致人死亡罪被公诉
  • 上海体育消费节将从5月持续至11月,推出运动装备商品促销活动
  • 过敏性鼻炎,不只是“打喷嚏”那么简单
  • 郑庆华任同济大学党委书记
  • 叶辛秦文君进校园推广阅读
  • 上海一小学百名学生齐聚图书馆:纸质书的浪漫AI无法取代