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,则在接收数据最低位置1
        if(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还是1
        MySPI_W_MOSI(Byte & (0x80>>i));
        
        // 拉高时钟线,从设备在上升沿采样数据
        MySPI_W_SCLK(1);
        
        // 读取MISO上的数据
        if(MySPI_R_MISO()==1)
        {
            // 如果接收到1,则在对应位置置1
            ReceiveData |= (0x80>>i);
        }
        
        // 拉低时钟线,准备下一个bit
        MySPI_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还是1
        MySPI_W_MOSI(Byte & (0x80>>i));
        
        // 拉高时钟线,从设备在上升沿采样数据
        MySPI_W_SCLK(1);
        
        // 读取MISO上的数据
        if(MySPI_R_MISO()==1)
        {
            // 如果接收到1,则在对应位置置1
            ReceiveData |= (0x80>>i);
        }
        
        // 拉低时钟线,准备下一个bit
        MySPI_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还是1
        MySPI_W_MOSI(Byte & (0x80>>i));
        
        // 拉高时钟线,从设备在上升沿采样数据
        MySPI_W_SCLK(0);
        
        // 读取MISO上的数据
        if(MySPI_R_MISO()==1)
        {
            // 如果接收到1,则在对应位置置1
            ReceiveData |= (0x80>>i);
        }
        
        // 拉低时钟线,准备下一个bit
        MySPI_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还是1
        MySPI_W_MOSI(Byte & (0x80>>i));
        
        // 拉高时钟线,从设备在上升沿采样数据
        MySPI_W_SCLK(0);
        
        // 读取MISO上的数据
        if(MySPI_R_MISO()==1)
        {
            // 如果接收到1,则在对应位置置1
            ReceiveData |= (0x80>>i);
        }
        
        // 拉低时钟线,准备下一个bit
        MySPI_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 命令以读取闪存芯片的 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();
}

代码功能概述:

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和DID
    OLED_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的交互,

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PMP–一、二、三模、冲刺–分类–10.沟通管理

文章目录 技巧十、沟通管理 一模10.沟通管理--1.规划沟通管理--文化意识--军事背景和非军事背景人员有文化差异5、 [单选] 项目团队由前军事和非军事小组成员组成。没有军事背景的团队成员认为前军事团队成员在他们的项目方法中过于结构化和僵化。前军事成员认为其他团队成员更…

「Mac畅玩鸿蒙与硬件42」UI互动应用篇19 - 数字键盘应用

本篇将带你实现一个数字键盘应用&#xff0c;支持用户通过点击数字键输入数字并实时更新显示内容。我们将展示如何使用按钮组件和状态管理来实现一个简洁且实用的数字键盘。 关键词 UI互动应用数字键盘按钮组件状态管理用户交互 一、功能说明 数字键盘应用将实现以下功能&…

Svn如何切换删除账号

记录Svn清除切换账号 1.首先打开小乌龟的设置如下图 打开设置后单击已保存数据&#xff0c;然后选择清除 接上图选择清除后&#xff0c;就可以打勾选择清除已保存的账号&#xff0c;我们再次检出的就可以切换账号了 &#x1f449;总结 本次记录Svn清除切换账号 如能帮助到你…

7. 一分钟读懂“单例模式”

7.1 模式介绍 单例模式就像公司里的 打印机队列管理系统&#xff0c;无论有多少员工提交打印任务&#xff0c;大家的请求都汇总到唯一的打印管理中心&#xff0c;按顺序排队输出。这个中心必须全局唯一&#xff0c;避免多个队列出现资源冲突&#xff0c;保证打印任务井然有序。…

基于Transformer的编码器-解码器图像描述模型在AMD GPU上的应用

Transformer based Encoder-Decoder models for image-captioning on AMD GPUs — ROCm Blogs 图像描述&#xff0c;即基于生成式人工智能&#xff08;GenAI&#xff09;自动生成简洁的图像文本描述&#xff0c;在现实世界中有着非常重要的应用。例如&#xff0c;图像描述可以为…

Python爬虫——猫眼电影

用python中requests库爬取猫眼电影信息并保存到csv文件中 猫眼专业版 爬取界面 效果预览 代码 import requests import jsonurl1https://piaofang.maoyan.com/dashboard-ajax?orderType0&uuid1938bd58ddac8-02c2bbe3b009ed-4c657b58-144000-1938bd58ddac8&timeStamp…

非对称任意进制转换器(安卓)

除了正常进制转换&#xff0c;还可以输入、输出使用不同的数字符号&#xff0c;达成对数值进行加密的效果 点我下载APK安装包 使用unity开发。新建一个c#代码文件&#xff0c;把代码覆盖进去&#xff0c;再把代码文件添加给main camera即可。 using System.Collections; usin…

【HarmonyOS】鸿蒙应用地理位置获取,地理名称获取

【HarmonyOS】鸿蒙应用地理位置获取&#xff0c;地理名称获取 一、前言 首先要理解地理专有名词&#xff0c;当我们从系统获取地理位置&#xff0c;一般会拿到地理坐标&#xff0c;是一串数字&#xff0c;并不是地理位置名称。例如 116.2305&#xff0c;33.568。 这些数字坐…

OpenGL ES详解——文字渲染

目录 一、文字渲染 二、经典文字渲染&#xff1a;位图字体 1.概念 2.优缺点 三、现代文字渲染&#xff1a;FreeType 1.着色器 2.渲染一行文字 四、关于未来 一、文字渲染 当你在图形计算领域冒险到了一定阶段以后你可能会想使用OpenGL来绘制文字。然而&#xff0c;可能…

C++设计模式之外观模式

动机 下图中左边方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合&#xff0c;随着外部客户程序和各子系统的演化&#xff0c;这种过多的耦合面临很多变化的挑战。 如何简化外部客户程序和系统间的交互接口&#xff1f;如何将外部客户程序的演化和内部子系统…

【Redis篇】 List 列表

在 Redis 中&#xff0c;List 是一种非常常见的数据类型&#xff0c;用于表示一个有序的字符串集合。与传统的链表结构类似&#xff0c;Redis 的 List 支持在两端进行高效的插入和删除操作&#xff0c;因此非常适合实现队列&#xff08;Queue&#xff09;和栈&#xff08;Stack…

RE逆向基础知识及常见题型

通用寄存器 FAX: &#xff08;针对操作数和结果数据的&#xff09;累加器EBX: &#xff08;DS段的数据指针&#xff09;基址寄存器ECX: &#xff08;字符串和循环操作的&#xff09;计数器EDX: &#xff08;I/O指针&#xff09;数据寄存器ESI: &#xff08;字符串操作源指针&a…

APM装机教程(四):山鹰H743飞控四旋翼装机

文章目录 前言一、飞控说明书二、接线三、参数设置四、电机接线和转向 前言 固件版本&#xff1a;Copter 4.5.7 地面站&#xff1a;QGC 遥控器&#xff1a;云卓T10 飞控&#xff1a;山鹰H743 GPS&#xff1a;微空M9 这个飞控的原理图是开源的&#xff0c;网盘链接&#xff1a;…

实验四:MyBatis 的关联映射

目录&#xff1a; 一 、实验目的&#xff1a; 熟练掌握实体之间的各种映射关系。 二 、预习要求&#xff1a; 预习数据库原理中所讲过的一对一、一对多和多对多关系 三、实验内容&#xff1a; 1. 查询所有订单信息&#xff0c;关联查询下单用户信息(注意&#xff1a;因为一…

【C#设计模式(17)——迭代器模式(Iterator Pattern)】

前言 迭代器模式可以使用统一的接口来遍历不同类型的集合对象&#xff0c;而不需要关心其内部的具体实现。 代码 //迭代器接口 public interface Iterator {bool HashNext();object Next(); } //集合接口 public interface Collection {Iterator CreateIterator(); } //元素迭…

MySQL 主从同步一致性详解

MySQL主从同步是一种数据复制技术&#xff0c;它允许数据从一个数据库服务器&#xff08;主服务器&#xff09;自动同步到一个或多个数据库服务器&#xff08;从服务器&#xff09;。这种技术主要用于实现读写分离、提升数据库性能、容灾恢复以及数据冗余备份等目的。下面将详细…

Redis4——持久化与集群

Redis4——持久化与集群 本文讲述了1.redis在内存占用达到限制后的key值淘汰策略&#xff1b;2.redis主从复制原理&#xff1b;3.redis的哨兵模式&#xff1b;4.redis集群模式。 1. 淘汰策略 设置过期时间 expire key <timeout>只能对主hash表中的键设置过期时间。 查…

矩阵转置        ‌‍‎‏

矩阵转置 C语言代码C 语言代码Java语言代码Python语言代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 输入一个n行m列的矩阵A&#xff0c;输出它的转置 A T A^T AT。 输入 第一行包含两个整数n和m&#xff0c;表示矩阵A的行数和列数。…

Linux 无界面模式下使用 selenium

文章目录 前言什么是无界面模式&#xff1f;具体步骤安装谷歌浏览器查看安装的谷歌浏览器的版本下载对应版本驱动并安装Python 测试代码 总结个人简介 前言 在 Linux 服务器上运行自动化测试或网页爬虫时&#xff0c;常常需要使用 Selenium 来驱动浏览器进行操作。然而&#x…

windows部署PaddleSpeech详细教程

windows安装paddlespeech步骤&#xff1a; 1. 安装vs c编译环境 对于 Windows 系统&#xff0c;需要安装 Visual Studio 来完成 C 编译环境的安装。 Microsoft C Build Tools - Visual Studio 2. 安装conda conda create -y -p paddlespeech python3.8 conda activate pad…