【ESP32 IDF SPI硬件驱动W25Q64】

news2024/9/21 19:04:39

目录

  • SPI
    • SPI介绍
    • idf配置
      • 初始化配置
      • 通信
  • 驱动代码

SPI

SPI介绍

详细SPI介绍内容参考我之前写的内容【ESP32 IDF 软件模拟SPI驱动 W25Q64存储与读取数组】

idf配置

初始化配置

spi_bus_initialize()
在这里插入图片描述
参数1 :spi几,例如spi2,spi3
在这里插入图片描述

参数2:指向spi_bus_config_t的结构体,该结构体有较多值,取自己用的就行
参数3:是否使用DMA

对于参数2,有如下成员变量:

typedef struct {
    int miso_io_num;   // MISO引脚号
    int mosi_io_num;   // MOSI引脚号
    int sclk_io_num;   // 时钟引脚号
    int quadwp_io_num; // 用于Quad模式的WP引脚号,未使用时设置为-1
    int quadhd_io_num; // 用于Quad模式的HD引脚号,未使用时设置为-1
    int max_transfer_sz; // 最大传输大小
} spi_bus_config_t;

设备配置
spi_bus_add_device()
在这里插入图片描述

在这里插入图片描述
参数1:和上面的一样,选择spi几
参数2:指向设备的结构体
参数3:返回该设备的句柄

其中参数2,成员变量如下:
在这里插入图片描述
在这里插入图片描述

参数二的结构体的成员变量也很多,我们一样是挑着用上的配置。

    spi_device_handle_t dev_handle;
    spi_device_interface_config_t device_initer={
        .command_bits=0,//指令的位数
        .address_bits=0,//地址的位数
        .mode=0,//模式0,spi四种模式
        .spics_io_num=15,//ss/cs片选引脚号
        .clock_speed_hz=1000*1000//时钟通信频率
    };
    if(spi_bus_add_device(SPI2_HOST,&dev_handle,&dev_handle)!=ESP_OK)  printf("add device success\r\n");

通信

spi_device_transmit()
在这里插入图片描述
在这里插入图片描述
参数二的结构体成员变量也不少,但是我们配置好要发送的数据和长度,以及接收数据的地方和长度即可。
举例

uint32_t bsp_spi_flash_ReadID(spi_device_handle_t handle)
{
    //00 定义错误标志
    esp_err_t e;

    //01 接收数据的时候发送的空指令
    uint8_t data[3];
    data[0] = 0xff;
    data[1] = 0xff;
    data[2] = 0xff;

    //02 定义用于返回的数据
    uint32_t Temp;

    //03 定义数据发送接收结构体
    spi_transaction_ext_t ext;                  //因为读取设备ID的指令结构与前面定义的默认的不一样,所以需要更改位长
    memset(&ext, 0, sizeof(ext));               //初始化结构体
    ext.command_bits =  8;                      //指令位长度为8
    ext.address_bits =  0;                      //地址位长度为0
    ext.base.cmd =      W25X_JedecDeviceID;     //设备ID
    ext.base.length =   3 * 8;                  //要发送数据的长度
    ext.base.tx_buffer = data;                  //要发送数据的内容
    ext.base.rx_buffer = NULL;                  //接收数据buffer使用结构体内部带的
    ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA;  

    //04 数据收发
    e=spi_device_polling_transmit(handle, &ext.base);

    if (e != ESP_OK)
    {
        printf("get ID error!\n");
        return 0;
    }

    //05 返回获取的数据ID
    uint8_t temp0 = ext.base.rx_data[0];
    uint8_t temp1 = ext.base.rx_data[1];
    uint8_t temp2 = ext.base.rx_data[2];

    Temp = (temp0 << 16) | (temp1 << 8) | temp2;

    

驱动代码

w25q64.c

#include "W25Q64.h"

/**********************************************************
 * 函 数 名 称:w25q64_init_config
 * 函 数 功 能:w25q64初始化
 * 传 入 参 数:无
 * 函 数 返 回:无
 * 备       注:无
**********************************************************/
esp_err_t w25q64_init_config(spi_device_handle_t* handle)
{   
    //00 定义错误标志
    esp_err_t e;

    //01 配置总线初始化结构体
    static spi_bus_config_t bus_cfg;    //总线配置结构体

    bus_cfg.miso_io_num     = Flash_SPI_MISO;                //miso
    bus_cfg.mosi_io_num     = Flash_SPI_MOSI;                //mosi
    bus_cfg.sclk_io_num     = Flash_SPI_SCLK;                //sclk
    bus_cfg.quadhd_io_num   = Flash_SPI_HD;                 // HD
    bus_cfg.quadwp_io_num   = Flash_SPI_WP;                 // WP
    bus_cfg.max_transfer_sz = 4092;                         //非DMA最大64bytes,DMA最大4092bytes
    //bus_cfg.intr_flags = 0;                               //这个用于设置中断优先级的,0是默认
    bus_cfg.flags = SPICOMMON_BUSFLAG_MASTER;
    //这个用于设置初始化的时候要检测哪些选项。比如这里设置的是spi初始化为主机模式是否成功。检测结果通过spi_bus_initialize函数的
    //返回值进行返回。如果初始化为主机模式成功,就会返回esp_ok

    //02 初始化总线配置结构体
    e = spi_bus_initialize(Flash_SPI, &bus_cfg, SPI_DMA_CH_AUTO);

    if (e != ESP_OK)
    {
        printf("bus initialize failed!\n");
        return e;
    }

    //03 配置设备结构体
    static spi_device_interface_config_t interface_cfg; //设备配置结构体

   interface_cfg.address_bits = Flash_Address_Bits;   //配置地址位长度
   //(1)如果设置为0,在通讯的时候就不会发送地址位。
   //(2)如果设置了非零值,就会在spi通讯的地址发送阶段发送指定长度的address数据。
   //如果设置了非零值并且在后面数据发送结构体中没有定义addr的值,会默认发送指定长度0值
   //(3)我们后面发送数据会使用到spi_transaction_t结构体,这个结构体会使用spi_device_interface_config_t中定义好address、command和dummy的长度
   //如果想使用非固定长度,就要使用spi_transaction_ext_t结构体了。这个结构体包括了四个部分,包含了一个spi_transaction_t和address、command、dummy的长度。
   //我们要做的就是在spi_transaction_ext_t.base.flags中设置SPI_TRANS_VARIABLE_ADDR/CMD/DUMMY
   //然后定义好这三部分数据的长度,然后用spi_transaction_ext_t.base的指针代替spi_transaction_t的指针即可

    interface_cfg.command_bits = Flash_Command_Bits;  //配置命令位长度
    //与address_bits是一样的

    interface_cfg.dummy_bits = Flash_Dummy_Bits;  //配置dummy长度
    //这里的配置方法与address_bits是一样的。但是要着重说一下这个配置的意义,后面会再说一遍
    //(1)dummy_bits是用来用来补偿输入延迟。
    //(2)在read phase开始阶段之前被插入进去。在dummy_bits的时钟下,并不进行数据读取的工作
    //相当于这段时间发送的clock都是虚拟的时钟,并没有功能。在输入延迟最大允许时间不够的时候,可以通过这种方法进行配置,从而
    //能够使得系统工作在更高的时钟频率下。
    //(3)如果主机设备只进行write操作,可以在flags中设置SPI_DEVICE_NO_DUMMY,关闭dummy bits的发送。只有写操作的话,即使使用了gpio交换矩阵,时钟周期也可以工作在80MHZ

    //interface_cfg.input_delay_ns = 0;  //配置输入延时的允许范围
    //时钟发出信号到miso进行输入直接会有延迟,这个参数就是配置这个允许的最大延迟时间。
    //如果主机接收到从机时钟,但是超过这个时间没有收到miso发来的输入信号,就会返回通讯失败。
    //这个时间即使设置为0,也能正常工作,但是最好通过手册或逻辑分析仪进行估算。能够实现更好的通讯。
    //超过8M的通讯都应该认真设置这个数字

    interface_cfg.clock_speed_hz = Flash_CLK_SPEED;  //配置时钟频率
    //配置通讯的时钟频率。
    //这个频率受到io_mux和input_delay_ns限制。
    //如果是io直连的,时钟上限是80MHZ,如果是gpio交换矩阵连接进来的,时钟上限是40MHZ。
    //如果是全双工,时钟上限是26MHZ。并且还要考虑输入延时。在相同输入延时的条件下,使用gpio交换矩阵会比使用io mux最大允许的时钟频率小。可以通过
    //spi_get_freq_limit()来计算能够允许的最大时钟频率是多少
    //有关SPI通讯时钟极限和配置的问题,后面会详细说一下。

    interface_cfg.mode = 0; //设置SPI通讯的相位特性和采样边沿。包括了mode0-3四种。要看从设备能够使用哪种模式

    interface_cfg.spics_io_num = Flash_SPI_CS; //配置片选线

    interface_cfg.duty_cycle_pos = 0;  //配置占空比
    //设置时钟的占空比,比例是 pos*1/256,默认为0,也就是50%占空比

    //interface_cfg.cs_ena_pretrans; //在传输之前,片选线应该保持激活状态多少个时钟,只有全双工的时候才需要配置
    //interface_cfg.cs_ena_posttrans; //在传输之后,片选线应该保持激活状态多少个时钟,只有全双工的时候才需要配置

    interface_cfg.queue_size = 6; //传输队列的长度,表示可以在通讯的时候挂起多少个spi通讯。在中断通讯模式的时候会把当前spi通讯进程挂起到队列中

    //interface_cfg.flags; //配置与从机有关的一些参数,比如MSB还是LSB,使不使用三线SPI

    //interface_cfg.pre_cb; 
    //配置通讯前中断。比如不在这里配置cs片选线,把片选线作为自行控制的线,把片选线拉低放在通讯前中断中

    //interface_cfg.post_cb;
    //配置通讯后中断。比如不在这里配置cs片选线,把片选线作为自行控制的线,把片选线拉高放在通讯前中断中

    //04 设备初始化
    e = spi_bus_add_device(Flash_SPI, &interface_cfg, handle);
    if (e != ESP_OK)
    {
        printf("device config error\n");
        return e;
    }
    return ESP_OK;
}

uint32_t bsp_spi_flash_ReadID(spi_device_handle_t handle)
{
    //00 定义错误标志
    esp_err_t e;

    //01 接收数据的时候发送的空指令
    uint8_t data[3];
    data[0] = Dummy_Byte;
    data[1] = Dummy_Byte;
    data[2] = Dummy_Byte;

    //02 定义用于返回的数据
    uint32_t Temp;

    //03 定义数据发送接收结构体
    spi_transaction_ext_t ext;                  //因为读取设备ID的指令结构与前面定义的默认的不一样,所以需要更改位长
    memset(&ext, 0, sizeof(ext));               //初始化结构体
    ext.command_bits =  8;                      //指令位长度为8
    ext.address_bits =  0;                      //地址位长度为0
    ext.base.cmd =      W25X_JedecDeviceID;     //设备ID
    ext.base.length =   3 * 8;                  //要发送数据的长度
    ext.base.tx_buffer = data;                  //要发送数据的内容
    ext.base.rx_buffer = NULL;                  //接收数据buffer使用结构体内部带的
    ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA;  

    //04 数据收发
    e=spi_device_polling_transmit(handle, &ext.base);

    if (e != ESP_OK)
    {
        printf("get ID error!\n");
        return 0;
    }

    //05 返回获取的数据ID
    uint8_t temp0 = ext.base.rx_data[0];
    uint8_t temp1 = ext.base.rx_data[1];
    uint8_t temp2 = ext.base.rx_data[2];

    Temp = (temp0 << 16) | (temp1 << 8) | temp2;

    return Temp;
}

/**
* @breif flash写使能。在执行页写入和擦除命令之前,都必须执行一次页写入
* @param[in]  handle: 提供SPI的操作句柄
* @retval 无
**/

void bsp_spi_flash_WriteEnable(spi_device_handle_t handle)
{
    esp_err_t e; //错误标志位

    // 定义数据发送接收结构体
    spi_transaction_ext_t ext;                  //写使能的长度与默认的不同,需要修改
    memset(&ext, 0, sizeof(ext));               //初始化结构体
    ext.command_bits = 8;                       //指令位长度为8
    ext.address_bits = 0;                       //地址位长度为0
    ext.base.cmd = W25X_WriteEnable;            //写使能
    ext.base.length = 0;                        //要发送数据的长度,这里不需要发送数据
    ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR;

    //发送指令
    e = spi_device_polling_transmit(handle, &ext.base);

    if (e != ESP_OK)
    {
        printf("write enable failed!\n");
        
    }

}

/**
* @breif 等待flash完成当前操作
* @param[in]  handle: 提供SPI的操作句柄
* @retval 无
**/

void bsp_spi_flash_WaitForWriteEnd(spi_device_handle_t handle)
{
    
    // 定义数据发送接收结构体
    spi_transaction_ext_t ext;                  //写使能的长度与默认的不同,需要修改
    memset(&ext, 0, sizeof(ext));               //初始化结构体
    ext.command_bits = 8;                       //指令位长度为8
    ext.address_bits = 0;                      //地址位长度为 0
    ext.base.cmd = W25X_ReadStatusReg;          //读取状态寄存器
    ext.base.length = 1 * 8;                    //要发送数据的长度,这里不需要发送数据
    ext.base.rx_buffer = NULL;                  //不使用外部数据
    ext.base.tx_buffer = NULL;                  //不使用外部数据
    ext.base.tx_data[0] = Dummy_Byte;           //发送数据

    ext.base.flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA;

    do
    {
        //发送指令
        spi_device_polling_transmit(handle, &ext.base);
    }

    while ( (ext.base.rx_data[0] & WIP_Flag )== WIP_SET);
}

/**
* @breif 扇区擦除
* @param[in]  handle: 提供SPI的操作句柄
* @param[in] SectorAddr: 要擦除的起始扇区地址
* @retval 无
**/

void bsp_spi_flash_SectorErase(spi_device_handle_t handle,uint32_t SectorAddr)
{
    bsp_spi_flash_WriteEnable(handle);
    bsp_spi_flash_WaitForWriteEnd(handle);

    // 定义数据发送接收结构体
    spi_transaction_t t;                        //配置位与默认一致,不需要修改
    memset(&t, 0, sizeof(t));                   //初始化结构体
    t.cmd = W25X_SectorErase;                   //擦除指令
    t.addr = SectorAddr;                        //擦除地址
    t.length = 0;                               //不需要额外数据了

    //发送指令
    spi_device_polling_transmit(handle, &t);

    //等待擦除完毕
    bsp_spi_flash_WaitForWriteEnd(handle);
}

/**
* @breif 页写入
* @param[in]  handle: 提供SPI的操作句柄
* @param[in]  pBuffer:要写入的数据地址
* @param[in]  WriteAddr:要写入的地址
* @param[in]  NumByteToWrite: 要写入的长度
* @retval 无
**/

void bsp_spi_flash_PageWrite(spi_device_handle_t handle, uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
    bsp_spi_flash_WriteEnable(handle);

    // 定义数据发送接收结构体
    spi_transaction_t t;                        //配置位与默认一致,不需要修改
    memset(&t, 0, sizeof(t));                   //初始化结构体
    t.cmd = W25X_PageProgram;                   //页写入
    t.addr = WriteAddr;                         //擦除地址
    t.length = 8*NumByteToWrite;                //写入长度
    t.tx_buffer = pBuffer;                      //写入的数据
    t.rx_buffer = NULL;                         //不需要读取数据

    if (NumByteToWrite > SPI_Flash_PageSize)
    {
        printf("length is too long!\n");
        return ;
    }

    //发送指令
    spi_device_polling_transmit(handle, &t);

    //等待擦除完毕
    bsp_spi_flash_WaitForWriteEnd(handle);

}

/**
* @breif 不定量数据写入
* @param[in]  handle: 提供SPI的操作句柄
* @param[in]  pBuffer:要写入的数据地址
* @param[in]  WriteAddr:要写入的地址
* @param[in]  NumByteToWrite: 要写入的长度
* @retval 无
**/

void bsp_spi_flash_BufferWrite(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

    //进行取余运算,查看是否进行了页对齐
    Addr = WriteAddr % SPI_Flash_PageSize;
    
    //差count个数据值可以进行页对齐
    count = SPI_Flash_PageSize - Addr;

    //计算要写多少个完整的页
    NumOfPage = NumByteToWrite / SPI_Flash_PageSize;

    //计算剩余多少字节不满1页
    NumOfSingle = NumByteToWrite % SPI_Flash_PageSize;

    //如果Addr=0,也就是进行了页对齐
    if (Addr == 0)
    {
        //如果写不满1页
        if (NumOfPage == 0)
        {
            bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumByteToWrite);
        }
        
        else
        {
            //如果超过1页,先把满的写了
            while (NumOfPage--)
            {
                bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, SPI_Flash_PageSize);
                WriteAddr += SPI_Flash_PageSize;
                pBuffer += SPI_Flash_PageSize;
            }
            //不满的1页再写
            bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumOfSingle);
        }

    }
    //如果没有进行页对齐
    else
    {

        if (NumOfPage == 0)
        {
            //如果当前页剩下的count个位置比NumOfSingle小,1页写不完
            if (NumOfSingle > count)
            {
                //先把这页剩下的写了
                temp = NumOfSingle - count;
                bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, count);

                WriteAddr += count;
                pBuffer += count;

                //再把多了的写了
                bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, temp);
            }
            else
            {
                //如果剩下的空间足够大,就直接写
                bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumByteToWrite);
            }

        }
        //如果不止1页
        else
        {
            //先把对不齐的字节写了
            NumByteToWrite -= count;
            NumOfPage = NumByteToWrite / SPI_Flash_PageSize;
            NumOfSingle = NumByteToWrite % SPI_Flash_PageSize;
            bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr,count);

            //重复地址对齐的情况
            WriteAddr += count;
            pBuffer += count;

            while (NumOfPage--)
            {
                bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, SPI_Flash_PageSize);
                pBuffer += SPI_Flash_PageSize;
                WriteAddr += SPI_Flash_PageSize;

            }

            if (NumOfSingle != 0)
            {
                bsp_spi_flash_PageWrite(handle, pBuffer, WriteAddr, NumOfSingle);

            }

        }

    }

}

/**
* @breif 数据读取
* @param[in]  handle: 提供SPI的操作句柄
* @param[out]  pBuffer:要读取的数据buffer地址
* @param[in]  WriteAddr:要写入的地址
* @param[in]  NumByteToWrite: 要写入的长度
* @retval 无
**/

void bsp_spi_flash_BufferRead(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    bsp_spi_flash_WriteEnable(handle);

    // 定义数据发送接收结构体
    spi_transaction_t t;                        //配置位与默认一致,不需要修改
    memset(&t, 0, sizeof(t));                   //初始化结构体
    t.cmd = W25X_ReadData;                      //读取数据
    t.addr = WriteAddr;                         //擦除地址
    t.length = 8 * NumByteToWrite;              //读取长度
    t.tx_buffer = NULL;                         //不需要写入数据
    t.rx_buffer = pBuffer;                      //读取数据

    

    //发送指令
    spi_device_polling_transmit(handle, &t);

    //等待擦除完毕
    bsp_spi_flash_WaitForWriteEnd(handle);

}

w25q64.h

/*
 * @Author: i want to 舞动乾坤
 * @Date: 2024-07-27 09:26:08
 * @LastEditors: i want to 舞动乾坤
 * @LastEditTime: 2024-07-27 17:21:08
 * @FilePath: \spi_hardware_driver_w25q64\main\W25Q64.h
 * @Description: 
 * 
 * Copyright (c) 2024 by i want to 舞动乾坤, All Rights Reserved. 
 */
#ifndef __W25Q64_H__
#define __W25Q64_H__


#include "driver/spi_master.h"
#include "driver/spi_common.h"
#include "hal/gpio_types.h"
#include <string.h>   

//定义Flash实验所需要的引脚
#define Flash_SPI                               SPI3_HOST
#define Flash_SPI_MISO                          GPIO_NUM_19     
#define Flash_SPI_MOSI                          GPIO_NUM_23
#define Flash_SPI_SCLK                          GPIO_NUM_18
#define Flash_SPI_CS                            GPIO_NUM_5     
#define Flash_SPI_WP                            -1
#define Flash_SPI_HD                            -1
#define Flash_SPI_DMA                           SPI_DMA_CH1

//定义设备参数
#define Flash_CLK_SPEED                         6 * 1000 * 1000  //6M的时钟
#define Flash_Address_Bits                      3*8             //地址位长度
#define Flash_Command_Bits                      1*8             //命令位长度
#define Flash_Dummy_Bits                        0*8             //dummy位长度
#define SPI_Flash_PageSize                      256             //页写入最大值

//定义命令指令
#define W25X_JedecDeviceID                      0x9F        //获取flashID的指令
#define W25X_WriteEnable                        0x06        //写入使能
#define W25X_WriteDisable                       0x04        //禁止写入
#define W25X_ReadStatusReg                      0x05        //读取状态寄存器
#define W25X_SectorErase                        0x20        //扇区擦除
#define W25X_BlockErase                         0xD8        //块擦除
#define W25X_ChipErase                          0xC7        //芯片擦除
#define W25X_PageProgram                        0x02        //页写入
#define W25X_ReadData                           0x03        //数据读取

#define Dummy_Byte                              0xFF        //空指令,用于填充发送缓冲区
#define WIP_Flag                                0x01        //flash忙碌标志位
#define WIP_SET                                 1 

esp_err_t w25q64_init_config(spi_device_handle_t* handle);
uint32_t bsp_spi_flash_ReadID(spi_device_handle_t handle);

void bsp_spi_flash_SectorErase(spi_device_handle_t handle,uint32_t SectorAddr);
void bsp_spi_flash_PageWrite(spi_device_handle_t handle, uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void bsp_spi_flash_BufferWrite(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void bsp_spi_flash_BufferRead(spi_device_handle_t handle, uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);

#endif

main.c

/*
 * @Author: i want to 舞动乾坤
 * @Date: 2024-07-27 09:14:50
 * @LastEditors: i want to 舞动乾坤
 * @LastEditTime: 2024-07-27 17:16:23
 * @FilePath: \spi_hardware_driver_w25q64\main\main.c
 * @Description: 
 * 
 * Copyright (c) 2024 by i want to 舞动乾坤, All Rights Reserved. 
 */

#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "W25Q64.h"
#include <esp_log.h>

spi_device_handle_t spi2_handle;
static const char * TAG = "Task";

void app_main(void)
{
    uint8_t pBuffer[11] = { 0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0x00 };
    uint8_t rBuffer[11] = {0};
    int id=0;

    //配置W25Q64
    w25q64_init_config(&spi2_handle);

    //读取器件ID
    id=bsp_spi_flash_ReadID(spi2_handle);
    ESP_LOGI(TAG,"id = %X\r\n",id);

    //向W25Q64的地址0写入10个数据
    bsp_spi_flash_SectorErase(spi2_handle, 0);
    ESP_LOGI(TAG,"Sector erase successfully\r\n");

    bsp_spi_flash_BufferWrite(spi2_handle, pBuffer, 0, 10);
    ESP_LOGI(TAG,"Write successfully\r\n");
    
    //向W25Q64的地址0读取10个数据
    bsp_spi_flash_BufferRead(spi2_handle, rBuffer, 0, 10);
    for (int i = 0; i < 10; i++)
    {
        ESP_LOGI(TAG,"0x%x ", rBuffer[i]);
    }
    ESP_LOGI(TAG,".");
    while(1)
    {
        ESP_LOGI(TAG,".");
        vTaskDelay(1000/portTICK_PERIOD_MS);
    }
}

项目结构:
在这里插入图片描述

调试
在这里插入图片描述

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

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

相关文章

MySQL体系结构与查询执行流程详解

MySQL 体系结构与查询执行过程详解 MySQL 是一个采用单进程多线程架构模式的关系型数据库管理系统。本文将详细介绍 MySQL 的体系结构及其查询语句的执行过程,并探讨性能优化的关键点。 MySQL 体系结构 MySQL 的架构为 Client-Server 架构。总体上,我们可以将 MySQL 的体系…

python—pandas基础(2)

文章目录 列操作修改变量列筛选变量列使用.loc[]&#xff08;基于标签)使用.iloc[]&#xff08;基于整数位置&#xff09;使用.filter()方法 删除变量列添加变量列 变量类型的转换Pandas 支持的数据类型在不同数据类型间转换 建立索引新建数据框时建立索引读入数据时建立索引指…

如何在宝塔面板给域名配置 SSL 证书

首先需要有证书 这里以阿里云为例 1. 首先进入到 SSL 证书管理控制台 选择个人测试证书&#xff0c;并点击购买 免费的可以使用三个月。 购买完成之后回到控制台。 点击创建证书&#xff0c;将标红的地方填写&#xff0c;其他默认就好。 然后提交审核就行。 这里需要对域名…

JS逆向高级爬虫

JS逆向高级爬虫 JS逆向的目的是通过运行本地JS的文件或者代码,以实现脱离他的网站和浏览器,并且还能拿到和浏览器加密一样的效果。 10.1、编码算法 【1】摘要算法&#xff1a;一切从MD5开始 MD5是一个非常常见的摘要(hash)逻辑. 其特点就是小巧. 速度快. 极难被破解. 所以,…

图像生成中图像质量评估指标—FID介绍

文章目录 1. 背景介绍2. 实际应用3. 总结和讨论 1. 背景介绍 Frchet Inception Distance&#xff08;\textbf{FID}&#xff09;是一种衡量生成模型性能的指标&#xff0c;它基于Inception网络提取的特征来计算模型生成的图像与真实图像集合之间的距离。 FID利用了Inception模…

repo中的default.xml文件project name为什么一样?

文章目录 default.xml文件介绍为什么 name 是一样的&#xff0c;path 不一样&#xff1f;总结 default.xml文件介绍 在 repo 工具的 default.xml 文件中&#xff0c;定义了多个 project 元素&#xff0c;每个元素都代表一个 Git 仓库。 XML 定义了多个不同的 project 元素&…

64.隐藏指定模块

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;63.利用PEB获取模块列表 效果图&#xff1a; 隐藏模块简单实现&#xff1a; #include …

苍穹外卖浏览器前端界面修改

背景&#xff1a; 客户原始方案是期望做一个Spring Boot Vue的饿了么系统&#xff0c;但时间上太仓促&#xff0c;所以建议选择开源的苍穹外码目作为作业提交。 客户接受了建议的方案后&#xff0c;期望对前端页面做一些个性化的定制修改。 过程&#xff1a; 苍穹外卖简单介…

【C++进阶】C++11特性(上)

1、统一列表初始化 1.1 {}初始化 C98的特性用{}统一初始化数组或结构体。 //{}初始化 struct Point {int _x;int _y; }; int main() {int array1[] { 1, 2, 3, 4, 5 };int array2[5] { 0 };Point p { 1, 2 };return 0; } C11则扩大其特性&#xff0c;可以不带进行初始化&…

【SQL 新手教程 2/20】关系模型 -- 主键

&#x1f497; 关系数据库建立在关系模型上⭐ 关系模型本质上就是若干个存储数据的二维表 记录 (Record)&#xff1a; 表的每一行称为记录&#xff08;Record&#xff09;&#xff0c;记录是一个逻辑意义上的数据 字段 (Column)&#xff1a;表的每一列称为字段&#xff08;Colu…

基于STM32F103的FreeRTOS系列(四)·FreeRTOS资料获取以及简介

目录 1. FreeRTOS简介 1.1 FreeRTOS介绍 1.2 为何选择FreeRTOS 1.3 FreeRTOS资料获取 1.3.1 官网下载 1.3.2 Github下载 1.3.3 托管网站下载 1.4 FreeRTOS的编程风格 1.4.1 数据类型 1.4.2 变量名 1.4.3 函数名 1.4.4 宏 1. FreeRTOS简介 1.1 Free…

IEC104转BACnet网关:实现电力监控与楼宇自动化的无缝对接

在电力监控和楼宇自控领域&#xff0c;IEC104和BACnet作为两种重要的通信协议扮演着重要的角色。随着不同系统之间的数据交换与集成需求的不断增长&#xff0c;深圳市钡铼技术有限公司推出IEC104转BACnet网关来实现这两种协议之间的无缝转换&#xff0c;助力电力监控和楼宇自控…

如何知道一个字段在selenium中是否可编辑?

这篇文章将检查我们如何使用Java检查selenium webdriver中的字段是否可编辑。 我们如何知道我们是否可以编辑字段&#xff1f;“readonly”属性控制字段的可编辑性。如果元素上存在“readonly”属性&#xff0c;则无法编辑或操作该元素或字段。 因此&#xff0c;如果我们找到一…

3.5-RNN文本生成

1语言模型生成文本的顺序 前面我们已经能够实现使用下图的LSTM网络进行语言建模&#xff1b; 对于一个已经在语料库上学习好的LSTM模型&#xff1b;如果语料库就只是you say goobye and i say hello&#xff1b;那么当把单词i输入到模型中&#xff0c;Time xxx层的第一个LSTM…

⌈ 传知代码 ⌋ 深度学习革新音乐转录

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

SpringBoot上传文件大小超出范围捕获异常

文件上传中&#xff0c;可以限定文件大小&#xff0c;防止用户上传过大的文件&#xff0c;但是出现异常会报错&#xff0c;不够优雅 这里做异常捕获&#xff0c;然后自定义提示文字&#xff0c;可以更加优雅的解决文件上传超出限制报异常 /*** 上传文件超出大小限制异常 适用于…

微信公众号获取用户openid(PHP版,snsapi_base模式)

微信公众号获取用户openid的接口有2个&#xff1a;snsapi_base、snsapi_userinfo 详情见微信公众号开发文档&#xff1a;https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html 本文介绍用PHP方式调用snsapi_base接口获取微信用户…

Godot入门 02玩家1.0版

添加Node2D节点&#xff0c;重命名Game 创建玩家场景&#xff0c;添加CharacterBody2D节点 添加AnimatedSprite2D节点 从精灵表中添加帧 选择文件 设置成8*8 图片边缘模糊改为清晰 设置加载后自动播放&#xff0c;动画循环 。动画速度10FPS&#xff0c;修改动画名称idle。 拖动…

Spark实时(六):Output Sinks案例演示

文章目录 Output Sinks案例演示 一、​​​​​​​File sink 二、​​​​​​​​​​​​​​Memory Sink 三、​​​​​​​​​​​​​​Foreach Sink 1、​​​​​​​foreachBatch 2、​​​​​​​​​​​​​​foreach Output Sinks案例演示 当我们对流式…

HarmonyOS NEXT星河版零基础入门到实战

文章目录 一、HarmonyOS NEXT介绍学习内容1、鸿蒙APP开发2、能力套件开发3、全场景开发适合人群 持续更新中✒️总结 一、HarmonyOS NEXT介绍 放弃安卓框架之后&#xff0c;HarmonyOS NEXT成为真正独立于安卓、iOS的操作系统&#xff0c;堪称是一场史无前例的脱胎换骨。在其众多…