4.STM32通信接口之SPI通信(含源码)---软件SPI与W25Q64存储模块通信实战《精讲》

经过研究SPI协议和W25Q64,逐步了解了SPI的通信过程,接下来,就要进行战场实战了!跟进Whappy步伐!

目标:主要实现基于软件的SPI的STM32对W25Q64存储写入和读取操作!

开胃介绍(代码基本实现过程)

  1. 初始化 SPI 接口:
    • 配置微控制器上必要的 GPIO 引脚,将 MOSI、SCK 和 CS 设置为输出模式,MISO 设置为输入模式。
    • 根据 SPI 时钟极性(CPOL)的配置,设置 SCK 引脚的初始状态。
    • 将 CS 引脚设置为高电平,取消对 W25Q64 芯片的选择。
  2. 选择 W25Q64 芯片:
    • 将 CS 引脚拉低,选择 W25Q64 芯片,开始通信。
  3. 发送命令:
    • 在 MOSI 线上移位发送命令字节,同时切换 SCK 线。
    • 命令指示要执行的操作类型,如读、写或擦除。
    • 在命令阶段,W25Q64 可能也会在 MISO 线上移出状态信息,微控制器需要读取。
  4. 发送地址(如果需要):
    • 对于需要指定内存地址的操作,在 MOSI 线上移位发送 24 位地址。
    • W25Q64 会接受地址,并为后续的数据传输做准备。
  5. 执行数据传输:
    • 根据操作类型,微控制器会在 MOSI 线上移位发送数据(写操作),或从 MISO 线上移位读取数据(读操作)。
    • 切换 SCK 线以同步数据传输。
    • 对于读操作,W25Q64 会在 MISO 线上移出请求的数据。
    • 对于写操作,W25Q64 会接受 MOSI 线上的数据,并将其存储到相应的内存位置。
  6. 取消选择 W25Q64 芯片:
    • 数据传输完成后,将 CS 引脚拉高,取消对 W25Q64 的选择,表示本次交互结束。

这就是与 W25Q64 闪存芯片进行软件 SPI 通信的主要步骤。

程序框架和上一节IIC差不多。

第一步:软件SPI协议层实现代码

时序框架:通过时序用C语言实现SPI

初始化相关的GPIO

(1)MySPI_Init(oid)

void MySPI_Init(void)
{// 开启GPIOA时钟/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟/*GPIO初始化*/	// 配置输出引脚(SCK, MOSI, NSS)GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置输入引脚(MISO)GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);MySPI_W_CS(1);MySPI_W_SCLK(0);}

SPI的模式0;实现交换数据代码(和下面的收发函数实现的功能一样)

uint8_t MySPI_SwapByte(uint8_t Byte)
{uint8_t ReceiveData = 0x00;uint16_t i;for(i=0; i<8; i++){// 发送最高位MySPI_W_MOSI((Byte & 0x80) ? 1 : 0);// 左移发送数据Byte <<= 1;// 拉高时钟线MySPI_W_SCLK(1);// 左移接收数据ReceiveData <<= 1;// 如果MISO为1,则在接收数据最低位置1if(MySPI_R_MISO() == 1){ReceiveData |= 0x01;}// 拉低时钟线MySPI_W_SCLK(0);}return ReceiveData;
}

这个版本:

  1. 完整发送8个bit
  2. 完整接收8个bit
  3. 返回接收到的完整字节
  4. 保留了原始的SPI通信时序

推荐使用这个版本,它更加准确地模拟了SPI通信的数据交换过程。

(2)SPI的起始和终止

/SPI模式0
/*** @brief 开始SPI通信,拉低片选信号* @note 通常在发送数据前调用,选中从设备*/
void MySPI_Start(void)
{MySPI_W_CS(0);  // 拉低CS(片选)信号,选中从设备
}
/*** @brief 结束SPI通信,拉高片选信号* @note 通常在数据传输完成后调用,取消从设备选择*/
void MySPI_Stop(void)
{MySPI_W_CS(1);  // 拉高CS(片选)信号,取消从设备选择
}

(3)SPI单字节数据交换(收发)函数

/*** @brief SPI单字节数据交换(收发)函数* @param Byte 要发送的字节* @return uint8_t 接收到的字节* @note 实现软件模拟SPI的数据交换*/
uint8_t MySPI_SwapByte(uint8_t Byte)
{uint16_t i, ReceiveData = 0x00;// 按位发送和接收数据for(i=0; i<8; i++){// 发送一个bit,从最高位开始// 使用移位操作判断当前bit是0还是1MySPI_W_MOSI(Byte & (0x80>>i));// 拉高时钟线,从设备在上升沿采样数据MySPI_W_SCLK(1);// 读取MISO上的数据if(MySPI_R_MISO()==1){// 如果接收到1,则在对应位置置1ReceiveData |= (0x80>>i);}// 拉低时钟线,准备下一个bitMySPI_W_SCLK(0);        }return ReceiveData;
}

/*** @brief SPI单字节数据交换(收发)函数* @param Byte 要发送的字节* @return uint8_t 接收到的字节* @note 实现软件模拟SPI的数据交换*/
uint8_t MySPI_SwapByte(uint8_t Byte)
{uint16_t i, ReceiveData = 0x00;// 按位发送和接收数据for(i=0; i<8; i++){MySPI_W_SCLK(0);// 发送一个bit,从最高位开始// 使用移位操作判断当前bit是0还是1MySPI_W_MOSI(Byte & (0x80>>i));// 拉高时钟线,从设备在上升沿采样数据MySPI_W_SCLK(1);// 读取MISO上的数据if(MySPI_R_MISO()==1){// 如果接收到1,则在对应位置置1ReceiveData |= (0x80>>i);}// 拉低时钟线,准备下一个bitMySPI_W_SCLK(0);        }return ReceiveData;
}

/*** @brief SPI单字节数据交换(收发)函数* @param Byte 要发送的字节* @return uint8_t 接收到的字节* @note 实现软件模拟SPI的数据交换*/
uint8_t MySPI_SwapByte(uint8_t Byte)
{uint16_t i, ReceiveData = 0x00;// 按位发送和接收数据for(i=0; i<8; i++){// 发送一个bit,从最高位开始// 使用移位操作判断当前bit是0还是1MySPI_W_MOSI(Byte & (0x80>>i));// 拉高时钟线,从设备在上升沿采样数据MySPI_W_SCLK(0);// 读取MISO上的数据if(MySPI_R_MISO()==1){// 如果接收到1,则在对应位置置1ReceiveData |= (0x80>>i);}// 拉低时钟线,准备下一个bitMySPI_W_SCLK(1);        }return ReceiveData;
}

/*** @brief SPI单字节数据交换(收发)函数* @param Byte 要发送的字节* @return uint8_t 接收到的字节* @note 实现软件模拟SPI的数据交换*/
uint8_t MySPI_SwapByte(uint8_t Byte)
{uint16_t i, ReceiveData = 0x00;// 按位发送和接收数据for(i=0; i<8; i++){MySPI_W_SCLK(1);// 发送一个bit,从最高位开始// 使用移位操作判断当前bit是0还是1MySPI_W_MOSI(Byte & (0x80>>i));// 拉高时钟线,从设备在上升沿采样数据MySPI_W_SCLK(0);// 读取MISO上的数据if(MySPI_R_MISO()==1){// 如果接收到1,则在对应位置置1ReceiveData |= (0x80>>i);}// 拉低时钟线,准备下一个bitMySPI_W_SCLK(1);        }return ReceiveData;
}

总结:

SPI总共就三个函数

  • MySPI_Init():初始化通信接口
  • MySPI_Start():开始通信
  • MySPI_Stop():结束通信
  • MySPI_SwapByte():进行实际的数据交换
/**
* @brief 初始化SPI通信接口
* @note 配置GPIO口,设置SPI通信相关引脚模式和时钟
*/
void MySPI_Init(void);/**
* @brief 开始SPI通信
* @note 拉低片选信号(CS),选中从设备,准备开始数据传输
*/
void MySPI_Start(void);/**
* @brief 结束SPI通信
* @note 拉高片选信号(CS),取消从设备选择,结束数据传输
*/
void MySPI_Stop(void);/**
* @brief SPI数据交换函数
* @param Byte 要发送的字节数据
* @return uint8_t 接收到的字节数据
* @note 模拟SPI通信的数据交换过程
* 
* 功能:
* 1. 逐位发送输入字节
* 2. 同时接收从设备返回的数据
* 3. 返回接收到的完整字节
*/
uint8_t MySPI_SwapByte(uint8_t Byte);

W25Q64的

验证SPI时序的正确 实验实例:读取设备W25Q64的MID 和DID

这段文字描述了 W25Q80/16/32 系列存储器芯片如何通过 JEDEC 标准指令读取设备的身份信息。它特别说明了通过 Read JEDEC ID 指令来读取设备的制造商ID、内存类型和容量。

解释:

  1. For compatibility reasons, the W25Q80/16/32 provides several instructions to electronically determine the identity of the device:
    为了兼容性,W25Q80/16/32 提供了几种指令,允许电子设备读取存储器芯片的身份信息。这有助于识别设备及其相关参数。

  2. The Read JEDEC ID instruction is compatible with the JEDEC standard for SPI compatible serial memories that was adopted in 2003:
    读取 JEDEC ID 指令遵循了 JEDEC 标准,这个标准在 2003 年被采纳,专门用于 SPI 兼容的串行存储器。

  3. The instruction is initiated by driving the /CS pin low and shifting the instruction code "9Fh":
    该指令通过将芯片选择信号(/CS)拉低来启动,并且发送指令代码 9Fh9Fh 是 JEDEC ID 读取指令的指令代码。

  4. The JEDEC assigned Manufacturer ID byte for Winbond (EFh) and two Device ID bytes, Memory Type (ID15-ID8) and Capacity (ID7-ID0) are then shifted out on the falling edge of CLK with most significant bit (MSB) first as shown in figure 28:
    启动指令后,设备会通过时钟信号(CLK)的下降沿依次将数据发送出来。具体数据包括:

    • 制造商 ID(Manufacturer ID):在 Winbond 芯片上是 EFh(十六进制)。
    • 内存类型(Memory Type):通过位域 ID15-ID8 表示。
    • 容量(Capacity):通过位域 ID7-ID0 表示。 数据是按 MSB(最重要位)优先的顺序发送的。
  5. For memory type and capacity values refer to Manufacturer and Device Identification table:
    内存类型和容量的具体值可以参考设备的“制造商与设备标识表”。这意味着根据不同的设备型号,内存类型和容量值会有所不同。

第二步:基于SPI的W25Q64驱动代码的实现

通过查找W25Q64指令集来对其操作!主要利用页面编程,擦除,读取数据等基础操作

(1)W25Q64的初始化

void W25Q64_Init(void)
{MySPI_Init();
}

(2)读取闪存的制造商 ID(MID)和设备 ID(DID)

指令集

void W25Q64_ReadID(uint8_t* MID, uint16_t* DID)
{// 启动 SPI 通信MySPI_Start();// 发送 JEDEC ID 命令以读取闪存芯片的 IDMySPI_SwapByte(W25Q64_JEDEC_ID); // 发送读取 JEDEC ID 命令// 读取 MID(制造商 ID),并将其存入 MID 指针指向的地址*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 发送占位符字节并读取 MID// 读取 DID(设备 ID),并将其存入 DID 指针指向的地址*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 发送占位符字节并读取 DID 的低字节// 将 DID 向左移 8 位,将 DID 的高字节移到高位*DID <<= 8; // 将 DID 低字节移到高字节的地方// 读取 DID 的高字节并合并到 DID 变量中*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取 DID 高字节并合并// 停止 SPI 通信MySPI_Stop();
}

代码功能概述:

W25Q64_ReadID 函数通过 SPI 协议与 W25Q64 闪存芯片进行通信,读取闪存的制造商 ID(MID)和设备 ID(DID)。这两个 ID 是通过 JEDEC ID 命令从闪存芯片中获取的。

代码注释与解释

 

c

复制代码

void W25Q64_ReadID(uint8_t* MID, uint16_t* DID) { // 启动 SPI 通信 MySPI_Start(); // 发送 JEDEC ID 命令以读取闪存芯片的 ID MySPI_SwapByte(W25Q64_JEDEC_ID); // 发送读取 JEDEC ID 命令 // 读取 MID(制造商 ID),并将其存入 MID 指针指向的地址 *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 发送占位符字节并读取 MID // 读取 DID(设备 ID),并将其存入 DID 指针指向的地址 *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 发送占位符字节并读取 DID 的低字节 // 将 DID 向左移 8 位,将 DID 的高字节移到高位 *DID <<= 8; // 将 DID 低字节移到高字节的地方 // 读取 DID 的高字节并合并到 DID 变量中 *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); // 读取 DID 高字节并合并 // 停止 SPI 通信 MySPI_Stop(); }

代码详解

  1. 启动 SPI 通信

     

    c

    复制代码

    MySPI_Start();

    • 调用 MySPI_Start 函数拉低 CS(片选)引脚,启动与 W25Q64 的 SPI 通信。
  2. 发送 JEDEC ID 命令

     

    c

    复制代码

    MySPI_SwapByte(W25Q64_JEDEC_ID);

    • W25Q64_JEDEC_ID 是一个常量,它代表 JEDEC ID 命令。调用 MySPI_SwapByte 发送该命令,通知 W25Q64 闪存芯片准备返回其 ID。
  3. 读取 MID(制造商 ID)

     

    c

    复制代码

    *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    • 发送一个占位符字节(通常是 0x00),然后通过 SPI 读取 MID(制造商 ID),将读取的值存入传入的指针 MID 指向的变量中。
  4. 读取 DID(设备 ID)

     

    c

    复制代码

    *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    • 再次发送占位符字节,通过 SPI 读取 DID(设备 ID)的低字节,并将其存入 DID 的低字节部分。
  5. 处理 DID 的高字节

     

    c

    复制代码

    *DID <<= 8;

    • DID 变量左移 8 位,将低字节腾出位置,为接下来的高字节准备空间。
  6. 读取 DID 的高字节并合并

     

    c

    复制代码

    *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    • 读取 DID 的高字节,并将其合并到 DID 变量中。|= 操作符将高字节与之前的低字节组合成完整的 16 位 DID。
  7. 停止 SPI 通信

     

    c

    复制代码

    MySPI_Stop();

    • 调用 MySPI_Stop 函数拉高 CS(片选)引脚,结束与 W25Q64 闪存的 SPI 通信。

注意细节:

  1. 占位符字节

    • 在读取 MID 和 DID 时,必须发送占位符字节(通常是 W25Q64_DUMMY_BYTE,如 0x00)。这些字节用于占据时钟周期,等待闪存芯片返回数据。
    • W25Q64_DUMMY_BYTE 是定义为常数的值,表示在不进行数据传输时发送的空字节,确保 SPI 时序正确。
  2. 字节顺序

    • 由于 MID 是 8 位,而 DID 是 16 位,因此在读取 DID 时需要先读取低字节,再读取高字节。高字节必须左移 8 位,低字节通过位或操作合并。
    • DID 高字节在低字节之后读取,并且需要将低字节的值“腾空”,这通常是通过左移操作(<<= 8)来实现。
  3. SPI 通信时序

    • MySPI_SwapByte() 函数用于执行 SPI 发送和接收操作。每次调用该函数都发送一个字节并接收一个字节,因此它能同时实现发送命令和接收数据。
    • 注意:MySPI_SwapByte() 不仅发送数据,还会返回接收到的字节数据。
  4. 传入指针

    • 函数通过传入指针 MIDDID 传递读取到的 MID 和 DID。这意味着在函数外部可以直接访问这两个 ID。
  5. SPI 配置

    • 假设 MySPI_Start()MySPI_Stop() 正确配置了 SPI 接口的片选信号,并确保 SPI 数据传输时序正确,确保每个字节都能正确传输。

总结

  • W25Q64_ReadID 函数通过 SPI 协议读取 W25Q64 闪存的制造商 ID (MID) 和设备 ID (DID)。
  • 通过发送命令和读取数据字节,并使用位操作处理 DID 的高低字节,将 MID 和 DID 存储在传入的指针所指向的变量中。
  • 函数使用占位符字节与闪存芯片同步,确保 SPI 通信的时序正确。

(3)向 W25Q64 闪存芯片发送写使能命令(Write Enable)

指令集

void W25Q64_WriteEnable(void)
{// 启动 SPI 通信MySPI_Start();// 发送写使能命令(0x06)以允许闪存写入MySPI_SwapByte(W25Q64_WRITE_ENABLE);// 停止 SPI 通信MySPI_Stop();
}

代码功能概述:

W25Q64_WriteEnable 函数通过 SPI 协议向 W25Q64 闪存芯片发送写使能命令(Write Enable)。此命令用于启用写操作,确保在进行闪存写操作(如写入数据、页编程等)之前,芯片允许进行写入。

代码注释与解释

 

c

复制代码

void W25Q64_WriteEnable(void) { // 启动 SPI 通信 MySPI_Start(); // 发送写使能命令(0x06)以允许闪存写入 MySPI_SwapByte(W25Q64_WRITE_ENABLE); // 停止 SPI 通信 MySPI_Stop(); }

代码详解

  1. 启动 SPI 通信

     

    c

    复制代码

    MySPI_Start();

    • 调用 MySPI_Start 函数,通过拉低 SPI 的 CS(片选)引脚来开始与 W25Q64 闪存的通信。此时芯片被选中,开始接收命令。
  2. 发送写使能命令

     

    c

    复制代码

    MySPI_SwapByte(W25Q64_WRITE_ENABLE);

    • W25Q64_WRITE_ENABLE 是定义的常量,其值为 0x06,这是 W25Q64 闪存的写使能命令。
    • 通过调用 MySPI_SwapByte(),向闪存发送该命令。在 SPI 总线上,MySPI_SwapByte 不仅发送数据,还接收从闪存芯片返回的数据。
    • 这里的 MySPI_SwapByte() 会发送一个字节 0x06(写使能命令)到 W25Q64,并确保 SPI 时序正确。
  3. 停止 SPI 通信

     

    c

    复制代码

    MySPI_Stop();

    • 调用 MySPI_Stop 函数,将 CS(片选)引脚拉高,停止与 W25Q64 闪存的 SPI 通信。此时,芯片被取消选中,通信结束。

注意细节

  1. 写使能命令的作用

    • 写使能命令 0x06 是 W25Q64 闪存芯片的一个基本命令。它使能闪存的写操作。如果不发送写使能命令,后续的写操作将被闪存芯片忽略,无法进行。
    • 在实际的闪存操作中(如擦除、写入数据等),每次都需要先发送写使能命令以允许写操作。
  2. 命令的发送顺序和时序

    • 在 SPI 通信中,必须遵循正确的时序和命令顺序。MySPI_SwapByte 函数确保发送的命令按照 SPI 协议正确发送,同时接收返回的数据。
    • 即使写使能命令没有返回数据,MySPI_SwapByte 也会等待 SPI 时钟周期,确保数据传输完成。
  3. SPI 总线上的 CS 管脚

    • 在该函数中,MySPI_StartMySPI_Stop 控制 CS(片选)引脚的状态。CS 必须在发送命令之前拉低,命令发送完成后再拉高,确保闪存芯片接收到整个命令。
    • CS 管脚的控制确保只有一个设备在某个时刻与 MCU 通信。这个操作必须非常精确,否则可能导致与其他 SPI 设备发生冲突。
  4. 写使能命令后的操作

    • 写使能命令发送成功后,W25Q64 闪存芯片就会允许进行后续的写入操作(如页面编程、数据写入等)。因此,执行此命令后可以安全地进行其他写入操作,如写页、擦除扇区等。
  5. 性能优化

    • W25Q64_WriteEnable 是一个非常常见的操作,每次写操作前都需要发送该命令。如果在实际应用中频繁调用该函数,可能会增加通信的延迟。如果希望优化性能,可以在写操作时减少多次调用 WriteEnable

总结

  • W25Q64_WriteEnable 函数通过 SPI 协议向 W25Q64 闪存芯片发送 0x06 的写使能命令,确保后续的写操作可以成功执行。
  • 通过 MySPI_StartMySPI_Stop 控制 SPI 通信的开始和结束。
  • 写使能命令是所有写操作的前置条件,必须在每次写操作之前执行


(4)等待 W25Q64 闪存芯片完成当前操作(如写入、擦除等)

Busy 位的作用

  • W25Q64 的状态寄存器 1 中的 Busy 位(第 0 位)表示芯片是否正在进行操作。忙碌时该位为 1,表示正在进行擦除、编程等操作;如果该位为 0,则表示闪存芯片的操作已经完成,可以进行后续操作。
void W25Q64_WaitBusy(void)
{// 定义一个超时计数器,防止死循环uint32_t Timeout = 100000;// 启动 SPI 通信MySPI_Start();// 发送读取状态寄存器 1 命令,W25Q64 的状态寄存器 1 用于获取芯片的忙碌状态MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);// 通过 SPI 读取状态寄存器 1 的数据,检查忙碌位while((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)  // 判断 Busy 位{Timeout--;  // 超时计数器递减if(Timeout == 0)  // 如果超时,跳出循环{break;}}// 停止 SPI 通信MySPI_Stop();
}

(5)通过 SPI 向 W25Q64 闪存芯片的指定地址进行页面编程

void W25Q64_PageProgram(uint32_t Address, uint8_t* Array, uint16_t Lenght)
{uint16_t i;// 启用写操作(发送写使能命令)W25Q64_WriteEnable();// 启动 SPI 通信MySPI_Start();// 发送页面编程命令(0x02)MySPI_SwapByte(W25Q64_PAGE_PROGRAM);// 发送目标地址(24 位地址:高 8 位、中 8 位、低 8 位)MySPI_SwapByte((Address >> 16) & 0xFF);  // 地址的高 8 位MySPI_SwapByte((Address >> 8) & 0xFF);   // 地址的中 8 位MySPI_SwapByte(Address & 0xFF);           // 地址的低 8 位// 发送数据(最多 256 字节,写入指定地址)for(i = 0; i < Lenght; i++){MySPI_SwapByte(Array[i]);  // 发送数据字节}// 停止 SPI 通信MySPI_Stop();// 等待闪存操作完成(等待闪存忙碌标志清除)W25Q64_WaitBusy();
}

代码功能概述:

W25Q64_PageProgram 函数用于通过 SPI 向 W25Q64 闪存芯片的指定地址进行页面编程。页面编程将数据写入指定地址的页中,每个页的大小通常为 256 字节。该函数首先发送写使能命令,然后发送页面编程命令,最后传输数据。

代码详解

  1. 写使能命令

     

    c

    复制代码

    W25Q64_WriteEnable();

    • 通过调用 W25Q64_WriteEnable 向 W25Q64 闪存发送写使能命令 0x06,使闪存允许执行写操作。
  2. 启动 SPI 通信

     

    c

    复制代码

    MySPI_Start();

    • 通过 MySPI_Start 函数拉低 CS(片选)引脚,开始与 W25Q64 闪存的 SPI 通信。
  3. 发送页面编程命令

     

    c

    复制代码

    MySPI_SwapByte(W25Q64_PAGE_PROGRAM);

    • 发送页面编程命令 0x02W25Q64_PAGE_PROGRAM),该命令用于启动页面写入操作。
  4. 发送地址

     

    c

    复制代码

    MySPI_SwapByte((Address >> 16) & 0xFF); MySPI_SwapByte((Address >> 8) & 0xFF); MySPI_SwapByte(Address & 0xFF);

    • 由于 W25Q64 闪存支持 24 位地址(3 字节),所以需要将传入的 32 位地址 Address 拆分成高 8 位、中 8 位和低 8 位,依次通过 SPI 发送。
    • 使用位移操作将地址拆分成 3 个字节。
  5. 发送数据

     

    c

    复制代码

    for(i = 0; i < Lenght; i++) { MySPI_SwapByte(Array[i]); }

    • 通过循环遍历 Array 中的数据,将每个字节依次发送到 W25Q64 闪存芯片。Lenght 参数表示要写入的字节数(最多 256 字节)。
    • 这里假设写入的数据不会超过闪存页面的大小,即最多 256 字节。如果需要写入更大的数据,必须分多次进行页面写入。
  6. 停止 SPI 通信

     

    c

    复制代码

    MySPI_Stop();

    • 通过 MySPI_Stop 函数将 CS(片选)引脚拉高,结束与 W25Q64 闪存的 SPI 通信。
  7. 等待闪存完成操作

     

    c

    复制代码

    W25Q64_WaitBusy();

    • 调用 W25Q64_WaitBusy 函数,等待闪存完成当前的编程操作。该函数会检查闪存的忙碌状态,直到写入操作完成。

注意细节

  1. 页面编程命令

    • W25Q64 的页面编程命令 0x02 是用于将数据写入指定地址的页面中。每个页面的大小通常为 256 字节,因此在调用该命令时,最大写入数据长度为 256 字节。
    • 在实际操作中,如果数据长度超过 256 字节,需要将数据分成多个页面进行写入。可以通过地址自增和多次调用此函数来实现。
  2. 地址范围

    • W25Q64 支持最大 24 位地址(3 字节地址),即最大地址为 0xFFFFFF,范围为 0 到 16MB。因此,函数中的地址参数 Address 应在 0 到 16MB 之间。
  3. 数据长度

    • Lenght 参数指定写入的数据字节数。由于每个页面的大小为 256 字节,所以该函数最多一次性支持写入 256 字节的数据。如果需要写入的数据超过 256 字节,需要拆分为多个页面写入。
  4. 闪存的写入周期

    • 每次页面编程后,W25Q64 闪存需要一段时间来完成写入操作。在此期间,芯片处于忙碌状态。W25Q64_WaitBusy 函数确保在闪存完成写入操作后才会进行下一步操作。
  5. 数据验证

    • 本函数没有实现数据验证。在实际应用中,可能需要在编程后通过读取该地址的数据并与原数据进行比较,来确保写入成功。
  6. 性能优化

    • 该函数每次写入最多 256 字节,如果数据长度较大,可能需要多次调用 W25Q64_PageProgram 函数进行数据写入。如果有较大的数据块需要写入,可以考虑优化为批量写入模式,减少重复的 SPI 命令传输。

总结

W25Q64_PageProgram 函数通过 SPI 协议将指定的数据写入 W25Q64 闪存的指定页面。它首先通过 W25Q64_WriteEnable 启用写操作,随后发送页面编程命令和地址,并将数据逐字节写入闪存。最后,通过 W25Q64_WaitBusy 函数等待闪存完成写入操作。

(6)W25Q64 闪存芯片进行 4KB 扇区的擦除操作

指令集

函数概述:

该函数 W25Q64_SectorErase 用于对 W25Q64 闪存芯片进行 4KB 扇区的擦除操作。擦除操作是通过 SPI 总线向 W25Q64 发送擦除命令和目标地址来实现的。函数内部包括启用写权限、发送擦除命令、传输地址以及等待擦除完成的步骤。

void W25Q64_SectorErase(uint32_t Address)
{// 1. 启用写操作:调用 W25Q64_WriteEnable 函数,设置闪存为可写状态//    这步是必须的,因为 W25Q64 的擦除、写入操作都需要先启用写权限。W25Q64_WriteEnable();// 2. 启动 SPI 通信:调用 MySPI_Start 启动 SPI 总线通信//    SPI 总线用于与 W25Q64 芯片进行数据交换。MySPI_Start();// 3. 发送扇区擦除命令:发送擦除命令 0x20,表示擦除 4KB 扇区//    该命令告知 W25Q64 执行一个 4KB 扇区擦除操作。MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);// 4. 发送地址:地址分成 3 个字节进行传输(从高字节到低字节)//    W25Q64 使用 24 位地址来定位擦除区域,按大端顺序发送地址的三个字节。MySPI_SwapByte((Address >> 16) & 0xFF);  // 发送地址的高字节MySPI_SwapByte((Address >> 8) & 0xFF);   // 发送地址的中间字节MySPI_SwapByte(Address & 0xFF);          // 发送地址的低字节// 5. 停止 SPI 通信:调用 MySPI_Stop 停止 SPI 总线通信//    在完成命令发送后,关闭 SPI 总线。MySPI_Stop();// 6. 等待擦除完成:调用 W25Q64_WaitBusy 函数,确保擦除操作完成//    擦除操作可能需要一定的时间,调用此函数等待闪存芯片的忙碌标志变为“未忙”状态,//    表示擦除操作已完成,可以进行其他操作。W25Q64_WaitBusy();
}

代码功能:

该函数的作用是擦除指定地址处的 4KB 扇区。整个过程包括以下步骤:

  1. 启用写操作:在擦除操作之前,必须先启用写操作,这通常是为了防止误操作对闪存内容进行修改。

  2. SPI 启动与数据传输:通过 SPI 协议与 W25Q64 芯片通信,发送擦除命令以及目标地址。擦除命令是 0x20,后面跟着的是 24 位地址数据。

  3. 等待操作完成:擦除操作是一个相对较长的过程,因此在擦除命令发出后,程序通过 W25Q64_WaitBusy 函数等待闪存芯片的忙碌状态变为未忙碌,确保擦除完成。

关键点总结:

  • W25Q64_WriteEnable:开启写操作权限,允许对 W25Q64 进行擦除操作。
  • W25Q64_SECTOR_ERASE_4KB:执行 4KB 扇区擦除命令(0x20)。
  • SPI 通信:通过 MySPI_StartMySPI_SwapByteMySPI_Stop 与 W25Q64 进行数据传输。
  • W25Q64_WaitBusy:等待擦除操作完成,确保擦除过程不被中断。

这段代码的目的就是精确控制 W25Q64 闪存的擦除操作,确保数据的完整性和操作的成功执行。

(7)从 W25Q64 闪存芯片读取指定地址的数据

函数概述:

该函数 W25Q64_ReadData 用于从 W25Q64 闪存芯片读取指定地址的数据。通过 SPI 协议发送读取命令、地址和所需读取的字节数,并将读取的数据存入 Array 数组中。函数支持任意长度的数据读取。

void W25Q64_ReadData(uint32_t Address, uint8_t* Array, uint32_t Length)
{uint32_t i;// 1. 启动 SPI 通信:通过 MySPI_Start 启动 SPI 总线MySPI_Start();// 2. 发送读取命令:发送 W25Q64 的读取数据命令(通常为 0x03)MySPI_SwapByte(W25Q64_READ_DATA);  // 发送读取命令(0x03)// 3. 发送 24 位地址:将目标地址分成三个字节并按大端格式发送MySPI_SwapByte((Address >> 16) & 0xFF);  // 发送地址的高字节MySPI_SwapByte((Address >> 8) & 0xFF);   // 发送地址的中间字节MySPI_SwapByte(Address & 0xFF);          // 发送地址的低字节// 4. 读取数据:读取指定长度的数据,并将读取的数据存入 Array 数组//    每次从 SPI 总线接收一个字节,并存储到 Array[i] 中for(i = 0; i < Length; i++){Array[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);  // 发送占位符字节并接收返回数据}// 5. 停止 SPI 通信:通过 MySPI_Stop 停止 SPI 总线MySPI_Stop();
}

代码功能:

该函数的主要功能是从指定的地址读取数据并将其存储到数组 Array 中。以下是具体步骤:

  1. SPI 启动:通过 MySPI_Start 启动与 W25Q64 闪存的 SPI 通信。

  2. 发送读取命令:通过 SPI 向闪存发送读取数据命令,通常为 0x03,表示读取操作。

  3. 发送地址:闪存地址是 24 位的,函数通过拆分地址的三个字节,并按大端格式(高字节先)发送。

  4. 读取数据:通过 MySPI_SwapByte 发送一个占位符字节(通常是 0xFF 或其他不重要的值),同时接收从闪存返回的数据并存储到 Array 数组中。每次读取一个字节,直到读取指定长度的所有数据。

  5. SPI 停止:通过 MySPI_Stop 停止 SPI 通信,完成数据读取操作。

关键点总结:

  • W25Q64_READ_DATA:读取数据命令,通常为 0x03
  • 地址格式:W25Q64 使用 24 位地址,发送时按高字节到低字节顺序。
  • 数据读取:通过 SPI 接收数据,将其存储到 Array 数组中,读取的字节数由 Length 参数指定。
  • 占位符字节MySPI_SwapByte(W25Q64_DUMMY_BYTE) 用于发送一个占位符字节(0xFF),同时接收返回的有效数据。W25Q64_DUMMY_BYTE 通常是一个不重要的字节,用于生成时钟并接收数据。

使用场景:

此函数适用于需要从 W25Q64 闪存芯片读取数据的场景,例如读取存储在闪存中的配置数据、文件数据、程序代码等。可以根据实际需要调整读取的长度,读取任意数量的字节。

第三步:STM32F10x 单片机控制 OLED 显示屏和 W25Q64 闪存芯片

代码概述:

该程序通过 STM32F10x 单片机控制 OLED 显示屏和 W25Q64 闪存芯片,实现了读取和写入闪存数据的操作,并通过 OLED 屏幕显示相关的 MID(制造商ID)和 DID(设备ID),以及闪存中的数据读取和写入值。

#include "stm32f10x.h"                  // 设备头文件,包含了STM32F10x系列的所有硬件抽象接口
#include "Delay.h"                       // 延时函数头文件
#include "OLED.h"                        // OLED显示模块的头文件
#include "W25Q64.h"                      // W25Q64闪存模块的头文件// 定义变量用于存储W25Q64芯片的制造商ID和设备ID
uint8_t MID;    // 存储制造商ID
uint16_t DID;   // 存储设备ID// 定义用于写入W25Q64的数组,模拟写入数据
uint8_t ArrayWrite[] = {0xAA, 0xBB, 0xCC, 0xDD};  // 写入的数据数组
uint8_t ArrayRead[4];  // 用于存储从W25Q64读取的数据int main(void)
{/* 模块初始化 */OLED_Init();  // 初始化OLED显示屏W25Q64_Init(); // 初始化W25Q64闪存模块// 在OLED屏幕上显示一些提示信息OLED_ShowString(1, 1, "MID:   DID:"); // 第1行显示 "MID: DID:"OLED_ShowString(2, 1, "W:");  // 第2行显示 "W:",用于显示写入数据OLED_ShowString(3, 1, "R:");  // 第3行显示 "R:",用于显示读取数据// 从W25Q64读取MID和DID并显示W25Q64_ReadID(&MID, &DID);  // 读取W25Q64的MID和DIDOLED_ShowHexNum(1, 5, MID, 2);  // 显示MID(制造商ID)到OLED屏幕的第1行,从第5列开始,显示2个十六进制数OLED_ShowHexNum(1, 12, DID, 4); // 显示DID(设备ID)到OLED屏幕的第1行,从第12列开始,显示4个十六进制数// 执行闪存操作:擦除扇区,写入数据并读取数据W25Q64_SetorErase(0x000000); // 擦除W25Q64芯片的第一个4KB扇区W25Q64_PageProgram(0x000000, ArrayWrite, 4); // 写入数据ArrayWrite到闪存地址0x000000,写入4个字节W25Q64_ReadData(0x000000, ArrayRead, 4); // 从闪存地址0x000000读取4个字节数据到ArrayRead数组// 在OLED屏幕上显示写入的数据OLED_ShowHexNum(2, 4, ArrayWrite[0], 2);  // 显示写入数据的第1个字节OLED_ShowHexNum(2, 7, ArrayWrite[1], 2);  // 显示写入数据的第2个字节OLED_ShowHexNum(2, 10, ArrayWrite[2], 2); // 显示写入数据的第3个字节OLED_ShowHexNum(2, 13, ArrayWrite[3], 2); // 显示写入数据的第4个字节// 在OLED屏幕上显示读取的数据OLED_ShowHexNum(3, 4, ArrayRead[0], 2);  // 显示读取数据的第1个字节OLED_ShowHexNum(3, 7, ArrayRead[1], 2);  // 显示读取数据的第2个字节OLED_ShowHexNum(3, 10, ArrayRead[2], 2); // 显示读取数据的第3个字节OLED_ShowHexNum(3, 13, ArrayRead[3], 2); // 显示读取数据的第4个字节// 主循环:程序将在此循环中不断运行while (1){// 主循环为空,程序在此运行时不会执行任何其他操作}
}

代码功能说明:

  1. 初始化模块

    • OLED_Init():初始化 OLED 显示模块,为显示做准备。
    • W25Q64_Init():初始化 W25Q64 闪存模块,设置 SPI 接口并准备与闪存进行通信。
  2. 显示MID和DID

    • 使用 W25Q64_ReadID(&MID, &DID) 从 W25Q64 获取制造商 ID(MID)和设备 ID(DID)。
    • 然后通过 OLED_ShowHexNum() 函数将这些 ID 显示在 OLED 屏幕上,显示格式为十六进制。
  3. 闪存操作

    • W25Q64_SetorErase(0x000000):擦除 W25Q64 闪存芯片的第一个 4KB 扇区,地址为 0x000000。
    • W25Q64_PageProgram(0x000000, ArrayWrite, 4):将 ArrayWrite 数组中的 4 个字节数据写入到闪存的地址 0x000000。
    • W25Q64_ReadData(0x000000, ArrayRead, 4):从闪存地址 0x000000 读取 4 个字节数据到 ArrayRead 数组。
  4. 显示读写的数据

    • 显示写入数据:通过 OLED_ShowHexNum() 显示 ArrayWrite 数组中写入的 4 个字节数据。
    • 显示读取数据:通过 OLED_ShowHexNum() 显示 ArrayRead 数组中读取的 4 个字节数据。
  5. 主循环

    • 在主循环中,程序保持空闲状态,不会执行其他操作。

总结:

这个程序通过 STM32F10x 微控制器与 W25Q64 闪存芯片进行交互,执行以下操作:

  1. 读取并显示闪存的制造商 ID(MID)和设备 ID(DID)。
  2. 执行擦除操作、写入操作和读取操作,将数据写入闪存并从中读取。
  3. 显示写入的数据和读取的数据到 OLED 显示屏上,便于用户观察。

使用场景:

此程序适用于嵌入式系统中需要使用闪存进行数据存储的应用,能够验证 W25Q64 闪存芯片的基本功能(读取 ID、写入和读取数据)。

 总结:

/**
* @brief W25Q64 Flash芯片初始化
* @note 初始化SPI接口,准备与Flash芯片通信
*/
void W25Q64_Init(void);/**
* @brief 读取W25Q64 Flash芯片的制造商ID和设备ID
* @param MID 指向存储制造商ID的指针
* @param DID 指向存储设备ID的指针
* @note 通过JEDEC标准协议读取芯片唯一标识信息
* 
* 通信流程:
* 1. 开始SPI通信
* 2. 发送读取ID指令
* 3. 读取制造商ID和设备ID
* 4. 结束SPI通信
*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID);/**
* @brief 向W25Q64 Flash指定地址写入数据页
* @param Address 目标写入地址
* @param DataArray 待写入的数据数组
* @param Count 写入的数据长度(字节数)
* @note 
* 1. 写入前需要先发送写使能指令
* 2. 一次写入不能跨页
* 3. 每页最大256字节
* 
* 通信流程:
* 1. 发送写使能指令
* 2. 发送页编程指令和地址
* 3. 逐字节写入数据
* 4. 等待写入完成
*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count);/**
* @brief 擦除W25Q64 Flash指定扇区
* @param Address 要擦除的扇区地址
* @note 
* 1. 擦除前需要先发送写使能指令
* 2. 擦除最小单位为扇区(4KB)
* 3. 擦除将把扇区内所有数据置为0xFF
* 
* 通信流程:
* 1. 发送写使能指令
* 2. 发送扇区擦除指令和地址
* 3. 等待擦除完成
*/
void W25Q64_SectorErase(uint32_t Address);/**
* @brief 从W25Q64 Flash读取数据
* @param Address 读取起始地址
* @param DataArray 存储读取数据的缓冲区
* @param Count 读取的数据长度(字节数)
* @note 
* 1. 支持任意长度的连续读取
* 2. 可以跨页、跨扇区读取
* 
* 通信流程:
* 1. 发送读取指令
* 2. 发送读取起始地址
* 3. 连续读取指定数量的数据
*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count);

代码解释:

/**
  * 函    数:W25Q64初始化
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_Init(void)

/**
  * 函    数:MPU6050读取ID号
  * 参    数:MID 工厂ID,使用输出参数的形式返回
  * 参    数:DID 设备ID,使用输出参数的形式返回
  * 返 回 值:无
  */
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)

/**
  * 函    数:W25Q64写使能
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WriteEnable(void)

/**
  * 函    数:W25Q64等待忙
  * 参    数:无
  * 返 回 值:无
  */
void W25Q64_WaitBusy(void)

/**
  * 函    数:W25Q64页编程
  * 参    数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray    用于写入数据的数组
  * 参    数:Count 要写入数据的数量,范围:0~256
  * 返 回 值:无
  * 注意事项:写入的地址范围不能跨页
  */
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)

/**
  * 函    数:W25Q64读取数据
  * 参    数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF
  * 参    数:DataArray 用于接收读取数据的数组,通过输出参数返回
  * 参    数:Count 要读取数据的数量,范围:0~0x800000
  * 返 回 值:无
  */
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)

软件SPI与W25Q64 Flash通信实验是非常经典的嵌入式系统通信实践。这个实验不仅能帮助你深入理解串行通信协议,还能掌握外部Flash芯片的基本操作。让我们来详细阐述这个实验的意义和关键步骤。

实验目的:

  1. 掌握软件模拟SPI通信的基本原理
  2. 学习W25Q64 Flash芯片的操作流程
  3. 理解数据读写和擦除的底层实现

关键技术点:

  • SPI通信协议
  • GPIO模拟时钟和数据线
  • 字节级数据交换
  • Flash芯片指令集

实验步骤:

  1. 硬件连接
    • MOSI:数据输出线
    • MISO:数据输入线
    • SCK:时钟线
    • CS:片选线
  2. 软件实现
    • MySPI_Init():初始化GPIO
    • MySPI_SwapByte():底层数据交换
    • W25Q64_ReadID():验证通信
    • W25Q64_ReadData():读取数据
    • W25Q64_PageProgram():写入数据
    • W25Q64_SectorErase():擦除扇区

推荐实验流程:

  1. 先实现SPI通信
  2. 读取芯片ID验证通信
  3. 测试数据读取
  4. 尝试数据写入和擦除

需要特别注意的是,软件SPI的时序控制至关重要,每个时钟周期和数据传输都需要精确控制。

工程源码:【免费】STM32与W25Q64闪存芯片的SPI通信资源-CSDN文库

下一节:我们将要进行硬件的SPI实验,本节的软件SPI的实现还是比较简单的,通过SPI作为通信双方的桥梁,连接STM32与W25Q64的交互,

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/33535.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

并发框架disruptor实现生产-消费者模式

Disruptor是LMAX公司开源的高性能内存消息队列&#xff0c;单线程处理能力可达600w订单/秒。本文将使用该框架实现生产-消费者模式。一、框架的maven依赖 <!-- https://mvnrepository.com/artifact/com.lmax/disruptor --><dependency><groupId>com.lmax<…

红日靶场vulnstack (五)

前言 好久没打靶机了&#xff0c;今天有空搞了个玩一下&#xff0c;红日5比前面的都简单。 靶机环境 win7&#xff1a;192.168.80.150(外)、192.168.138.136(内) winserver28&#xff08;DC&#xff09;&#xff1a;192.168.138.138 环境搭建就不说了&#xff0c;和之前写…

SpringBoot中@Import和@ImportResource和@PropertySource

1. Import Import注解是引入java类&#xff1a; 导入Configuration注解的配置类&#xff08;4.2版本之前只可以导入配置类&#xff0c;4.2版本之后也可以导入普通类&#xff09;导入ImportSelector的实现类导入ImportBeanDefinitionRegistrar的实现类 SpringBootApplication…

Cursor+Devbox AI开发快速入门

1. 前言 今天无意间了解到 Cursor 和 Devbox 两大开发神器,初步尝试以后发现确实能够大幅度提升开发效率,特此想要整理成博客以供大家快速入门. 简单理解 Cursor 就是一款结合AI大模型的代码编辑器,你可以将自己的思路告诉AI,剩下的目录结构的搭建以及项目代码的实现均由AI帮…

Linux之socket编程(一)

前言 网络通信的目的 我们已经大致了解了网络通信的过程: 如果主机A想发送数据给主机B, 就需要不断地对本层的协议数据单元(PDU)封装, 然后经过交换设备的转发发送给目的主机, 最终解封装获取数据. 那么网络传输的意义只是将数据由一台主机发送到另一台主机吗&#xff1f; …

Nmap数据包分片解析 -f 选项

Nmap数据包分片解析 1. 什么是数据包分片&#xff1f; 在网络扫描中&#xff0c;数据包的大小和分片机制可能会影响扫描的隐蔽性。Nmap通过-f选项来触发数据包的分片。启用-f选项后&#xff0c;Nmap会将IP数据包分割成8字节或更小的片段。如果你使用-f -f&#xff08;或-ff&a…

JAVAWeb——maven、SpringBoot、HTTP、Tomcat

目录 1.maven a.概述 b.作用 c.仓库 b.坐标 c.依赖管理 2.SpringBoot 3.HTTP a.概述 b.请求协议 c.响应协议 d.协议解析 4.Tomcat a.Web服务器 b.Tomcat c.SpringBoot与Tomcat关系 1.maven a.概述 Maven是apache旗下的一个开源项目&#xff0c;是一款用于管理…

Modbus TCP转profibusDP网关接防撞雷达快速配置

在工业自动化领域中&#xff0c;不同的设备可能使用不同的通信协议。当需要将使用 ModbusTCP 协议的防撞雷达连接到ProfibusDP网络时&#xff0c;需要使用协议转换器来实现协议的转换。 本文将详细介绍如何通过ModbusTCP转ProfibusDP网关接入防撞雷达&#xff0c;并提供快速配…

centos 报 ping: www.baidu.com: Name or service not known

[rootlocalhost ~]$ ping www.baidu.com ping: www.baidu.com: Name or service not known解决办法&#xff1a; 首先要求检查特定文件&#xff08;/etc/resolv.conf&#xff09;内是否正确配置了 DNS sudo vim /etc/resolv.conf没有正确配置可以添加如下代码&#xff1a; n…

机器学习代谢组学

Nature与Science重磅&#xff01;AI与生物医药迎来百年来最重磅进展&#xff01;https://mp.weixin.qq.com/s/Vw3Jm4vVKP14_UH2jqwsxA 第一天上午&#xff1a; A1 代谢物及代谢组学的发展与应用 &#xff08;1&#xff09; 代谢与生理过程&#xff1b; &#xff08;2&#…

AD21-原理图的统一编号设置

AD21-原理图的统一编号设置 1.顶部工具栏&#xff1a; 2.进入“原理图标注配置”之后&#xff1a; &#xff08;Step1&#xff09;: &#xff08;Step2&#xff09;: &#xff08;Step3&#xff09;:点击“执行变更” 至此&#xff0c;完成标注

58 基于 单片机的温湿度、光照、电压、电流检测

所有仿真详情导航&#xff1a; PROTEUS专栏说明-CSDN博客 目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用dht11温湿度传感器检测温湿度&#xff0c;采用光敏电阻连接数模转换器读取光照&#xff0c;通过lcd1602显…

python源码实例游戏开发小程序办公自动化网络爬虫项目开发源码(250+个项目、26.6GB)

文章目录 源代码下载地址项目介绍预览 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 python源码实例游戏开发小程序办公自动化网络爬虫项目开发源码(250个项目、26.6GB) 预览 项目备注 1、该资源内项目代码都经过测试运行成功&#xff0c;功能ok的情…

数字工厂管理系统如何做好供应链管理

在当今竞争激烈的制造业环境中&#xff0c;数字工厂管理系统已成为企业提升供应链管理效率与竞争力的关键。它借助数字化技术整合供应链各环节信息&#xff0c;实现高效协同与精准决策&#xff0c;为企业在市场中赢得先机奠定基础。 数字工厂管理系统首先要对供应链中的数据进行…

组蛋白修饰数据库

组蛋白修饰数据库 前言 组蛋白修饰是表观遗传控制的关键要素之一&#xff0c;在生物过程和疾病发展的调控中起着重要作用。组蛋白修饰可以通过标记特定的基因组位点来调节转录表观遗传&#xff0c;可以使用染色质免疫沉淀测序 &#xff08;ChIP-seq&#xff09; 进行定位。为…

gitlab-cicd部署安装与具体操作

一、安装 本例中是用安装包直接在ubuntu下安装的&#xff0c;也可以用docker镜像。 curl -LJO https://gitlab-runner-downloads.s3.amazonaws.com/latest/rpm/gitlab-runner_amd64.rpmrpm -i gitlab-runner_amd64.rpm 安装runner后&#xff0c;需要跟在runner所在服务器安装…

PETR:Position Embedding Transformation forMulti-View 3D Object Detection

全文摘要 本文介绍了一种名为“位置嵌入变换&#xff08;PETR&#xff09;”的新方法&#xff0c;用于多视角三维物体检测。该方法将三维坐标的位置信息编码为图像特征&#xff0c;并产生具有三维位置感知能力的特征。通过对象查询可以感知这些特征并进行端到端的目标检测。在…

使用 postman 传递 binary 类型的图片到后端接口遇到的坑

使用 psotman 传 binary 类型图片报错&#xff1a; -2024-12-04 [http-nio-9090-exec-1] WARN org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required r…

Pyside6 --Qt设计师--简单了解各个控件的作用之:Layouts,Spaces

目录 一、Layouts1.1 Vertical Layout说明1.1.1 Qt设计师图1.1.2 py代码 1.2 Horizontal Layout说明1.2.1 Qt设计师图1.2.2 py代码 1.3 Grid Layout说明1.3.1 Qt设计师图1.3.2 py代码 1.4 Form Layout说明1.4.1 Qt设计师图1.4.2 py代码 二、Spaces&#xff08;空格&#xff09;…

利用红黑树封装map,和set,实现主要功能

如果不知道红黑树是什么的时候可以去看看这个红黑树 思路 首先我们可以把封装分为两个层面理解&#xff0c;上层代码就是set,和map&#xff0c;底层就是红黑树 就相当于根据红黑树上面套了两个map,set的壳子&#xff0c;像下面这张图一样 对于map和set&#xff0c;map里面存…