stm32开发之threadx+filex+levelx结合使用(底层设备使用的是w25q128)

前言

  1. 本篇主要的内容就是根据上篇博客内容的基础上增加levelx组件
  2. 调整对应的文件命令来进行适配w25q128和sd之间的一个切换

代码调整

宏定义的调整

  1. 说明sd卡的扇区大小一般是512字节,w25q128的扇区大小是4096,所以在分配的缓冲区的时候按最大的来分配
#define SD_CACHE_BUFFER_SIZE (512)
#define W25Q128_CACHE_BUFFER_SIZE (4096) /*4KB*/
#define FX_MEDIA_CACHE_BUF_SIZE (W25Q128_CACHE_BUFFER_SIZE) /*media 缓冲区分配大小*/
#define FX_FAULT_TOLERANT_BUF_SIZE (2048) /*容错区*/

levelx组件的调整

参照上篇博客

增加filex的格式化适配

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-15     shchl   first version
 */
#include "includes.h"


#if APP_COMPONENT_LX_ENABLE

#include "lx_api.h"
#include "levelx/lx_stm32_nor_w25q128_driver.h"


LX_NOR_FLASH nor_w25q128;


int lx_application_define(void) {

    lx_nor_flash_initialize();

    return LX_SUCCESS;
}

TX_APP_DEFINE_EXPORT(lx_application_define); /*首先创建模块应用*/


/**
 * @brief nor flash 设备初始化(用于验证flash内部是否正常使用)
 * @return 
 */
int app_nor_flash_init() {
    UINT status = lx_nor_flash_open(&nor_w25q128,
                                    "nor w25q128",
                                    lx_stm32_nor_w25q128bv_driver_initialize);
    /* Erase the simulated NOR flash.  */
    /*打开失败(将失败的空间进行格式化)*/
    if (status) { /*这个一般只会执行一次,后面基本上不会再执行*/
        tx_log("need format space\r\n");
        lx_stm32_nor_w25q128bv_flash_format(&nor_w25q128); /*格式化*/
        /*再尝试一次*/
        status = lx_nor_flash_open(&nor_w25q128,
                                   "nor w25q128",
                                   lx_stm32_nor_w25q128bv_driver_initialize);
        if (status) {

            tx_log("nor flash init error\r\n");
            // todo

            return (int) status;
        } else {
#if 1 /*此部分是为了适配fx 建立文件系统*/

#include "fx_stm32_nor_w25q128_driver.h"

            status = fx_media_format(&g_fx_media,
                                     fx_nor_flash_w25q128_driver,
                                     FX_NULL,                          // Unused
                                     g_fx_media_cache_buffer,                  // Media buffer pointer
                                     SD_CACHE_BUFFER_SIZE,              // Media buffer size
                                     "MY_NOR_DISK",                     // Volume Name
                                     1,                                // Number of FATs
                                     32,                              // Directory Entries
                                     0,                                // Hidden sectors
                                     LX_NOR_W25Q128_BLOCKS_NUM - 2,     // Total sectors(保留最后的两个扇区不用于文件系统)
                                     W25Q128_PHY_PER_SECTOR_SIZE,   // Sector size
                                     1,                             // Sectors per cluster
                                     1,                                      // Heads
                                     1);                             // Sectors per track


#endif
        }


    }
    tx_log("nor flash device init ok\r\n");

    lx_nor_flash_close(&nor_w25q128);/*这里直接关闭,后面如果需要,再打开*/

    return (int) LX_SUCCESS; /*返回状态*/
}

TX_THREAD_EXPORT(app_nor_flash_init);


#endif

filex组件的调整(调整变量名称)

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-16     shchl       fx 组件入口函数
 */
#include "includes.h"


#if 1

#include "fx_api.h"


/*
*******************************************************************************************************
*                               外部引入变量
*******************************************************************************************************
*/

/*
*******************************************************************************************************
*                               变量
*******************************************************************************************************
*/
void *g_fx_media_cache_buffer; /*缓冲区指针*/
FX_MEDIA g_fx_media; /*全局sd 文件硬盘结构体*/

#ifdef FX_ENABLE_FAULT_TOLERANT
void *g_fault_tolerant_buffer = NULL;
#endif
/*
*********************************************************************************************************
*                                       静态全局变量
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                      函数声明
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                      外部函数
*********************************************************************************************************
*/
int fx_application_define(void) {
    /* 分配内存空间.  */
    g_fx_media_cache_buffer = app_malloc(FX_MEDIA_CACHE_BUF_SIZE);
#ifdef FX_ENABLE_FAULT_TOLERANT
    g_fault_tolerant_buffer = app_malloc(FX_FAULT_TOLERANT_BUF_SIZE);
#endif

    /*fx 初始化*/
    fx_system_initialize();

    return FX_SUCCESS;
}

TX_APP_DEFINE_EXPORT(fx_application_define); /*首先创建模块应用*/

/*
*********************************************************************************************************
*                                      内部函数
*********************************************************************************************************
*/



#endif

文件封装后的api文件调整

UINT app_fx_open_sd();
UINT app_fx_close_sd();

  1. 增加挂载和卸载,移除打开和关闭的逻辑,用于适配sd卡和w25q128之间的切换

头文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-16     shchl   first version
 */

#ifndef STM32_PROJECT_APP_FILE_API_H
#define STM32_PROJECT_APP_FILE_API_H

#include "includes.h"

#define NOR_MEDIA_NAME "nor_media"
#define SD_MEDIA_NAME "sd_media"


UINT app_media_mount(char *name);

UINT app_media_unmount(char *name);

UINT app_fx_file_create(const CHAR *file_name);
UINT app_fx_dir_create(const CHAR *dir_name);
UINT app_fx_file_open(FX_FILE *fp, CHAR *file_name, UINT open_type);
UINT app_fx_file_close(FX_FILE *fp);
UINT app_fx_file_delete(char *file_name);

UINT app_fx_file_fprintf(FX_FILE *fp, const char *fmt, ...);
UINT app_fx_dir_attr_read(char *dir_name,UINT *attr);
UINT app_fx_dir_delete(char  *dir_name);
#endif //STM32_PROJECT_APP_FILE_API_H

源文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-16     shchl   first version
 */
#include "app_file_api.h"
#include "fx_stm32_sd_driver.h"
#include "fx_stm32_nor_w25q128_driver.h"
#include "levelx/lx_stm32_nor_w25q128_driver.h"

#define driver_media_ptr (&g_fx_media)
#define FX_DATA_BUF_SIZE 256
static char fx_data_buf[FX_DATA_BUF_SIZE] = {0};

static UINT media_init_stat = 0;

/**
 * @brief 挂载 media
 * @param name
 * @return
 */
UINT app_media_mount(char *name) {
    TX_INTERRUPT_SAVE_AREA
    UINT status = FX_NOT_FOUND;
    UINT cache_buf_size;/*缓存内存大小*/
    VOID (*drv_ptr)(FX_MEDIA *media_ptr);
    if (strcmp(name, NOR_MEDIA_NAME) == 0) {
        drv_ptr = fx_nor_flash_w25q128_driver;
        cache_buf_size = W25Q128_CACHE_BUFFER_SIZE;

    } else if (strcmp(name, SD_MEDIA_NAME) == 0) {
        drv_ptr = fx_stm32_sd_driver;
        cache_buf_size = SD_CACHE_BUFFER_SIZE;
    } else {
        return FX_NOT_FOUND;
    }
    if (media_init_stat) return FX_ALREADY_CREATED;
    status = fx_media_open(driver_media_ptr,
                           SD_MEDIA_NAME,
                           drv_ptr,
                           0,
                           g_fx_media_cache_buffer,
                           cache_buf_size);

    TX_DISABLE
    if (status == FX_SUCCESS) {

        media_init_stat = 1;

    }
    TX_RESTORE
#ifdef FX_ENABLE_FAULT_TOLERANT
    /* Enable the Fault-tolerant feature.  */
    status = fx_fault_tolerant_enable(driver_media_ptr,
                                      g_fault_tolerant_buffer,
                                      FX_FAULT_TOLERANT_BUF_SIZE);
#endif
    return status;
}

UINT app_media_unmount(char *name) {
    TX_INTERRUPT_SAVE_AREA
    UINT status = FX_SUCCESS;
    if (media_init_stat) {
        status = fx_media_close(driver_media_ptr);
        TX_DISABLE
        if (status == FX_SUCCESS) {
            media_init_stat = 0;
        }
        TX_RESTORE
    }
    return status;
}


/**
 * @brief 创建文件
 * @param file_name 文件名
 * @return
 */
UINT app_fx_file_create(const CHAR *file_name) {
    UINT status;
    status = fx_file_create(driver_media_ptr, (CHAR *) file_name);
    fx_media_flush(driver_media_ptr);/*将缓存数据刷新到sd卡中*/
    return status;
}

UINT app_fx_dir_create(const CHAR *dir_name) {
    UINT status;
    status = fx_directory_create(driver_media_ptr, (CHAR *) dir_name);
    fx_media_flush(driver_media_ptr); /*将缓存数据刷新到sd卡中*/

    return status;
}


/**
 * @brief 打开文件,
 * @param fp 文件指针
 * @param file_name 文件名称
 * @param open_type 打开类型
 *      @note 如果是 FX_OPEN_FOR_WRITE,文件如果不存在,则会创建新文件
 * @return
 */
UINT app_fx_file_open(FX_FILE *fp, CHAR *file_name, UINT open_type) {
    UINT status;
    status = fx_file_open(driver_media_ptr, fp, file_name, open_type);
    // 如果文件不存在,并且访问类型为写,则先创建文件,然后再打开一次
    if (status == FX_NOT_FOUND && open_type == FX_OPEN_FOR_WRITE) {
        status = fx_file_create(driver_media_ptr, file_name);
        if (status == FX_SUCCESS) {
            status = fx_file_open(driver_media_ptr, fp, file_name, open_type);
        }
    }
    return status;
}

UINT app_fx_file_close(FX_FILE *fp) {

    return fx_file_close(fp);


}

UINT app_fx_file_delete(char *file_name) {


    return fx_file_delete(driver_media_ptr, file_name);
}

UINT app_fx_dir_delete(char *dir_name) {

    return fx_directory_delete(driver_media_ptr, dir_name);

}

/**
 * @brief 目录属性读取
 * @param dir_name [in] 目录名
 * @param attr [out] 返回
 * @return
 */
UINT app_fx_dir_attr_read(char *dir_name, UINT *attr) {
    return fx_directory_attributes_read(driver_media_ptr, dir_name, attr);
}

/**
 * @brief 格式化文件写入数据
 * @param fp
 * @param fmt
 * @param ...
 * @return
 */
UINT app_fx_file_fprintf(FX_FILE *fp, const char *fmt, ...) {
    if (!fp) return FX_PTR_ERROR;
    va_list v_args;
    va_start(v_args, fmt);
    int len = vsnprintf((char *) fx_data_buf,
                        (size_t) sizeof(fx_data_buf),
                        (char const *) fmt,
                        v_args);
    va_end(v_args);
    if (len > FX_DATA_BUF_SIZE) return FX_NOT_ENOUGH_MEMORY;/*没有足够的内存*/
    return fx_file_write(fp, fx_data_buf, len);
}

文件操作的命令

  1. 移除 UINT app_fx_open_sd();UINT app_fx_close_sd(); 函数的调用集合

增加挂载和卸载命令

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-18     shchl   first version
 */
#include "includes.h"

#if 1

#include "fx_api.h"
#include "filex/app_file_api.h"


/**
 * @brief 文件驱动挂载
 * @param argc
 * @param argv
 *  @see 用法 mount 驱动名
 * @return
 */
int fx_mount_cmd(int argc, char *argv[]) {
    char *driver_name;
    UINT status;
    if (argc != 2) {
        logWarning("mount <driver_name>");
        return -1;
    } else {
        driver_name = argv[1];

        status = app_media_mount(driver_name);
        if (status) {
            logError("app_media_mount error:%#x", status);
        }
    }


    return 0;
}
SHELL_EXPORT_CMD(
        SHELL_CMD_PERMISSION(0) |
        SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), mount, fx_mount_cmd, "mount fx driver");

/**
 * @brief 文件驱动挂载
 * @param argc
 * @param argv
 *  @see 用法 mount 驱动名
 * @return
 */
int fx_unmount_cmd(int argc, char *argv[]) {
    char *driver_name;
    UINT status;
    if (argc != 2) {
        logWarning("unmount <driver_name>");
        return -1;
    } else {
        driver_name = argv[1];

        status = app_media_unmount(driver_name);
        if (status) {
            logError("app_media_mount error:%#x", status);
        }
    }
    return 0;
}


SHELL_EXPORT_CMD(
        SHELL_CMD_PERMISSION(0) |
        SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), unmount, fx_unmount_cmd, "unmount fx driver");


#endif

增加levelx适配fx的驱动代码(参考fx提供的ram模拟驱动代码)

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-17     shchl   first version
 */

#include "fx_stm32_nor_w25q128_driver.h"
#include "levelx/lx_stm32_nor_w25q128_driver.h"
/**
 * @brief fx lx
 * @param media_ptr
 */
/* Create a NOR flash control block.  */

extern LX_NOR_FLASH nor_w25q128;

/* Define prototypes.  */

VOID fx_nor_flash_w25q128_driver(FX_MEDIA *media_ptr) {

    UCHAR *source_buffer;
    UCHAR *destination_buffer;
    ULONG logical_sector;
    ULONG i;
    UINT status;
    /* 此值由驱动程序返回。如果操作成功,则此字段应设置为 FX_SUCCESS for before returning。否则,如果发生错误,则此字段应设置为 FX_IO_ERROR。
            FX_MEDIA Member                              Meaning
        fx_media_driver_request             FileX 请求类型。来自 FileX 的有效请求如下:
                                                    FX_DRIVER_READ
                                                    FX_DRIVER_WRITE
                                                    FX_DRIVER_FLUSH
                                                    FX_DRIVER_ABORT
                                                    FX_DRIVER_INIT
                                                    FX_DRIVER_BOOT_READ
                                                    FX_DRIVER_RELEASE_SECTORS
                                                    FX_DRIVER_BOOT_WRITE
                                                    FX_DRIVER_UNINIT
        fx_media_driver_status             此值由驱动程序返回。如果操作成功,则此字段应设置为 FX_SUCCESS for before returning。
                                                否则,如果发生错误,则此字段应设置为 FX_IO_ERROR。
        fx_media_driver_buffer             指向缓冲区的指针,用于读取或写入扇区数据。这是由 FileX 提供的。
        fx_media_driver_logical_sector      FileX 正在请求的逻辑扇区。
        fx_media_driver_sectors             FileX 请求的扇区数。
      以下是可选 FX_MEDIA 结构成员的摘要:

            FX_MEDIA Member                              Meaning
        fx_media_driver_info                指向任何其他信息或内存的指针。
                                            这对于驱动程序使用是可选的,并且是设置的
                                            从fx_media_open呼叫。RAM 磁盘使用
                                            此指针用于 RAM 磁盘内存本身。
        fx_media_driver_write_protect       DRIVER 将其设置为写入介质时FX_TRUE 保护。这通常是在初始化中完成的, 但可以随时完成。
        fx_media_driver_free_sector_update  DRIVER 在需要时将其设置为 FX_TRUE
                                            了解集群何时发布。这很重要 用于 FLASH 磨损均衡驱动器。
        fx_media_driver_system_write        FileX 将此标志设置为 FX_TRUE 如果扇区是  written 是一个系统扇区,例如,boot、fat 或
                                            目录扇区。驱动程序可以选择使用它 启动错误恢复逻辑以处理更大的故障 宽容。
        fx_media_driver_data_sector_read    FileX 将此标志设置为 FX_TRUE 如果扇区为 read 是文件数据扇区,即 NOT 系统扇区。
        fx_media_driver_sector_type         FileX 将此变量设置为特定类型的正在读取或写入的扇区。以下部门确定类型:
                                                    FX_UNKNOWN_SECTOR
                                                    FX_BOOT_SECTOR
                                                    FX_FAT_SECTOR
                                                    FX_DIRECTORY_SECTOR
                                                    FX_DATA_SECTOR
    */
    /*处理媒体控制块中指定的驱动程序请求。  */
    switch (media_ptr->fx_media_driver_request) {
        case FX_DRIVER_READ: {
            /* 设置目标缓冲区和逻辑扇区。  */
            logical_sector = media_ptr->fx_media_driver_logical_sector;
            destination_buffer = (UCHAR *) media_ptr->fx_media_driver_buffer;
            /* 循环从闪存读取扇区。  */
            for (i = 0; i < media_ptr->fx_media_driver_sectors; i++) {
                /* 从 NOR 闪存读取扇区。 */
                status = lx_nor_flash_sector_read(&nor_w25q128, logical_sector, destination_buffer);
                /* 确定读取是否成功。  */
                if (status != LX_SUCCESS) {
                    /* Return an I/O error to FileX.  */
                    media_ptr->fx_media_driver_status = FX_IO_ERROR;
                    return;
                }
                /* Move to the next entries.  */
                logical_sector++;
                destination_buffer = destination_buffer + media_ptr->fx_media_bytes_per_sector;
            }
            /* Successful driver request.  */
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;
        }
        case FX_DRIVER_WRITE: {
            /* Setup the source buffer and logical sector.  */
            logical_sector = media_ptr->fx_media_driver_logical_sector;
            source_buffer = (UCHAR *) media_ptr->fx_media_driver_buffer;
            /* Loop to write sectors to flash.  */
            for (i = 0; i < media_ptr->fx_media_driver_sectors; i++) {
                /* Write a sector to NOR flash.  */
                status = lx_nor_flash_sector_write(&nor_w25q128, logical_sector, source_buffer);
                /* Determine if the write was successful.  */
                if (status != LX_SUCCESS) {
                    /* Return an I/O error to FileX.  */
                    media_ptr->fx_media_driver_status = FX_IO_ERROR;
                    return;
                }
                /* Move to the next entries.  */
                logical_sector++;
                source_buffer = source_buffer + media_ptr->fx_media_bytes_per_sector;
            }
            /* Successful driver request.  */
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;
        }
        case FX_DRIVER_RELEASE_SECTORS: {

            /* Setup the logical sector.  */
            logical_sector = media_ptr->fx_media_driver_logical_sector;
            /* Release sectors.  */
            for (i = 0; i < media_ptr->fx_media_driver_sectors; i++) {
                /* Release NOR flash sector.  */
                status = lx_nor_flash_sector_release(&nor_w25q128, logical_sector);
                /* Determine if the sector release was successful.  */
                if (status != LX_SUCCESS) {
                    /* Return an I/O error to FileX.  */
                    media_ptr->fx_media_driver_status = FX_IO_ERROR;
                    return;
                }
                /* Move to the next entries.  */
                logical_sector++;
            }

            /* Successful driver request.  */
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;
        }
        case FX_DRIVER_FLUSH: {

            /* Return driver success.  */
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;
        }

        case FX_DRIVER_ABORT: {

            /* Return driver success.  */
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;
        }

        case FX_DRIVER_INIT: {

            /* FLASH 驱动程序负责在介质结构中设置多个字段,如下所示:
                    media_ptr -> fx_media_driver_free_sector_update
                    media_ptr -> fx_media_driver_write_protect
              fx_media_driver_free_sector_update 标志用于指示 FileX 在未使用扇区时通知驱动程序。
                这对于 FLASH 管理器特别有用,因此他们不必对不再使用的扇区进行维护映射。
                驱动程序可以随时设置fx_media_driver_write_protect标志,以指示媒体不可写。
               设置此标志时进行的写入尝试将作为错误返回。*/
            /* 在此处执行基本初始化...因为随后将再次读取卷名请求的引导记录。 */
            /* 通过闪边磨损均衡,FileX 应在扇区时告诉磨损均衡 不再使用。  */
            media_ptr->fx_media_driver_free_sector_update = FX_TRUE;
            /* Open the NOR flash simulation.  */
            status = lx_nor_flash_open(&nor_w25q128,
                                       "w25q128 nor flash",
                                       lx_stm32_nor_w25q128bv_driver_initialize);
            /* Determine if the flash open was successful.  */
            if (status != LX_SUCCESS) {

                /* Return an I/O error to FileX.  */
                media_ptr->fx_media_driver_status = FX_IO_ERROR;

                return;
            }

            /* Successful driver request.  */
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;
        }

        case FX_DRIVER_UNINIT: {

            /* There is nothing to do in this case for the RAM driver.  For actual
               devices some shutdown processing may be necessary.  */

            /* 关闭 NOR 闪存模拟。  */
            status = lx_nor_flash_close(&nor_w25q128);

            /* Determine if the flash close was successful.  */
            if (status != LX_SUCCESS) {

                /* Return an I/O error to FileX.  */
                media_ptr->fx_media_driver_status = FX_IO_ERROR;

                return;
            }

            /* Successful driver request.  */
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;
        }

        case FX_DRIVER_BOOT_READ: {

            /* 读取引导记录并返回给调用方。  */

            /* Setup the destination buffer.  */
            destination_buffer = (UCHAR *) media_ptr->fx_media_driver_buffer;

            /* Read boot sector from NOR flash.  */
            status = lx_nor_flash_sector_read(&nor_w25q128, 0, destination_buffer);

            /* For NOR driver, determine if the boot record is valid.  */
            if ((destination_buffer[0] != (UCHAR) 0xEB) ||
                (destination_buffer[1] != (UCHAR) 0x34) ||
                (destination_buffer[2] != (UCHAR) 0x90)) {

                /* Invalid boot record, return an error!  */
                media_ptr->fx_media_driver_status = FX_MEDIA_INVALID;
                return;
            }

            /* Determine if the boot read was successful.  */
            if (status != LX_SUCCESS) {

                /* Return an I/O error to FileX.  */
                media_ptr->fx_media_driver_status = FX_IO_ERROR;

                return;
            }

            /* Successful driver request.  */
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;
        }

        case FX_DRIVER_BOOT_WRITE: {

            /* Setup the source buffer.  */
            source_buffer = (UCHAR *) media_ptr->fx_media_driver_buffer;
            /* Write boot sector to NOR flash.  */
            status = lx_nor_flash_sector_write(&nor_w25q128, 0, source_buffer);
            /* Determine if the boot write was successful.  */
            if (status != LX_SUCCESS) {

                /* Return an I/O error to FileX.  */
                media_ptr->fx_media_driver_status = FX_IO_ERROR;

                return;
            }
            /* Successful driver request.  */
            media_ptr->fx_media_driver_status = FX_SUCCESS;
            break;
        }

        default: {

            /* Invalid driver request.  */
            media_ptr->fx_media_driver_status = FX_IO_ERROR;
            break;
        }
    }
}


测试验证

  1. 逻辑为 必须先挂载,然后才能操作,否则是无效的,然后切换到另外一个设备时,要先卸载之前的,重新挂载,没有做多个的适配。
  2. 在没有卸载以前的挂载,代码是直接限制返回错误

验证sd卡

在这里插入图片描述

验证w25q128

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

詩不诉卿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值