基于ESP32和TinyUSB实现虚拟U盘功能的完整指南

基于ESP32和TinyUSB实现虚拟U盘功能的完整指南

  • 基于ESP32和TinyUSB实现虚拟U盘功能的完整指南
    • 摘要
    • 引言
    • 安装 esp_tinyusb 依赖
    • 自定义主机电脑卷标
    • USB描述符配置与初始化
    • MSC配置与SD卡初始化
      • SD卡初始化代码解析
    • 文件目录浏览与任务调度
    • 完整代码
    • 结论

摘要

本文详细介绍了如何利用ESP32与TinyUSB库实现虚拟U盘(Mass Storage Class, MSC)功能。通过对代码示例的解析,读者将了解到如何配置USB描述符、初始化SD卡、实现挂载与卸载操作,并学习如何将代码片段嵌入文章中。本文同时提供完整代码的占位符,方便开发者根据自身需求进行修改和调试。


引言

随着物联网和嵌入式设备的普及,利用ESP32实现多种外设功能已成为开发者的热门选择。本文重点介绍利用TinyUSB库在ESP32平台上构建虚拟U盘的实现方法。通过深入解析代码示例,我们将展示如何将SD卡内容以USB大容量存储设备的形式提供给主机系统。

在这篇文章中,我们将着重讨论以下几个关键点:

  • tinyusb依赖的安装:如何安装 esp-tinyusb 依赖。
  • U盘卷标的设置:如何自定义U盘驱动器卷标。
  • USB描述符的配置:如何为虚拟U盘定义设备、配置和接口描述符。
  • SD卡初始化和挂载:如何使用ESP-IDF的SDMMC驱动实现SD卡的初始化,并借助TinyUSB完成挂载。
  • 文件目录浏览:如何利用FreeRTOS任务和文件系统接口浏览SD卡中的文件。

这些内容不仅能帮助初学者快速上手,同时对于有经验的开发者来说也能提供优化思路。接下来我们将对代码进行详细剖析。

安装 esp_tinyusb 依赖

打开 esp32 组件仓库 https://components.espressif.com/

搜索 esp_tinyusb 关键字,然后安装 esp_tinyusb 组件

idf.py add-dependency "espressif/esp_tinyusb^1.7.2"

重启 vscode,刷新 sdkconfig 配置项,开启 MSC (Mass Storage Class) 选项。
在这里插入图片描述

自定义主机电脑卷标

  1. 使用 f_setlabel 函数
    在这里插入图片描述
ESP_ERROR_CHECK(tinyusb_msc_storage_mount(mount_point()));
// f_setlabel label max len less than 11 byte
// ref http://elm-chan.org/fsw/ff/doc/setlabel.html
if (auto ret = f_setlabel("XIONGWEI"); FR_OK != ret) {ESP_LOGI(TAG, "USB setlabel Failed code:%d", ret);
}
ESP_ERROR_CHECK(tinyusb_msc_storage_unmount());
  1. 使用 autorun.inf 配置文件

在这里插入图片描述

使用 autorun.inf 自定义驱动器的卷标(Volume Label)和驱动器图标(Drive Icon)

[AutoRun]
label=My Custom USB
icon=usb.ico

以下是一些关于 AutoRun.inf 文件的权威资源,供进一步参考:

  1. Microsoft Documentation (Legacy): AutoRun.inf Entries
    该文档详细介绍了 AutoRun.inf 文件中的各个条目及其用途,适用于早期版本的 Windows。

  2. Microsoft Documentation (Modern): AutoRun Commands
    该文档提供了关于 AutoRun.inf 文件中命令的现代解释,适用于当前版本的 Windows。

  3. Wikipedia: AutoRun.inf
    Wikipedia 上的文章提供了 AutoRun.inf 文件的概述,包括其历史、用途以及安全方面的考虑。


USB描述符配置与初始化

在代码的开头部分,我们定义了USB设备的描述符信息。描述符是USB设备与主机通信时所必须的元数据。下面代码片段展示了设备描述符的配置:

static tusb_desc_device_t descriptor_config = {.bLength = sizeof(descriptor_config),.bDescriptorType = TUSB_DESC_DEVICE,.bcdUSB = 0x0200,.bDeviceClass = TUSB_CLASS_MISC,.bDeviceSubClass = MISC_SUBCLASS_COMMON,.bDeviceProtocol = MISC_PROTOCOL_IAD,.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,.idVendor = 0x303A, // Espressif VID,用户可根据需求修改.idProduct = 0x4002,.bcdDevice = 0x100,.iManufacturer = 0x01,.iProduct = 0x02,.iSerialNumber = 0x03,.bNumConfigurations = 0x01
};

这段代码设置了ESP32设备的基本信息,包括厂商ID、产品ID以及USB标准版本。


MSC配置与SD卡初始化

为了实现虚拟U盘功能,必须配置MSC(Mass Storage Class)接口和初始化SD卡。以下代码片段展示了如何定义MSC配置描述符:

static uint8_t const msc_fs_configuration_desc[] = {TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN,TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 64),
};

在虚拟U盘类 virtual_u_diskmount() 方法中,首先调用 storage_init_sdmmc(handle()) 来初始化SD卡,然后配置TinyUSB MSC存储参数。通过注册回调函数 storage_mount_changed_cb,系统能够实时反馈挂载状态变化。初始化成功后,TinyUSB驱动被安装,并通过 f_setlabel("XI") 设置存储卷标。

SD卡初始化代码解析

esp_err_t storage_init_sdmmc(sdmmc_card_t **card) {sdmmc_host_t host = SDMMC_HOST_DEFAULT();host.slot = SDMMC_HOST_SLOT_0;host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;sd_pwr_ctrl_ldo_config_t ldo_config = {.ldo_chan_id = 4,};sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL;esp_err_t ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to create power control handle");return ret;}host.pwr_ctrl_handle = pwr_ctrl_handle;sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();slot_config.width = 4;slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;sdmmc_card_t *sd_card = (sdmmc_card_t *)malloc(sizeof(sdmmc_card_t));(*host.init)();ESP_ERROR_CHECK(sdmmc_host_init_slot(host.slot, (const sdmmc_slot_config_t *)&slot_config));while (sdmmc_card_init(&host, sd_card)) {ESP_LOGE(TAG, "The detection pin of the slot is disconnected (Insert uSD card). Retrying...");vTaskDelay(pdMS_TO_TICKS(1000));}sdmmc_card_print_info(stdout, sd_card);*card = sd_card;return ESP_OK;
}

这段代码展示了如何配置SDMMC主机、初始化电源控制以及反复尝试初始化SD卡,确保系统在检测不到SD卡时能够持续重试。对于嵌入式系统开发者而言,这种防护机制尤为重要。


文件目录浏览与任务调度

代码中还提供了一个 list_files 方法,用于遍历指定目录中的文件和子目录。该方法利用了标准C库中的 opendirreaddir 接口,并结合ESP32的日志打印机制输出结果:

static void list_files(const char *path) {DIR *dir = opendir(path);if (!dir) {ESP_LOGE(TAG, "Failed to open directory: %s", path);return;}struct dirent *entry;while ((entry = readdir(dir)) != NULL) {if (entry->d_type == DT_DIR) {// 处理目录文件ESP_LOGI(TAG, "DIR: %s", entry->d_name);} else if (entry->d_type == DT_REG) {// 处理常规文件ESP_LOGI(TAG, "FILE: %s", entry->d_name);} else {ESP_LOGI(TAG, "UNKNOWN: %s", entry->d_name);}}closedir(dir);
}

该函数不仅提高了代码的可读性,同时也为开发者提供了一个简单的示例,如何在嵌入式系统中实现目录遍历功能,这对于调试和日志记录非常有用。


完整代码

完整代码包括:

  1. 包含头文件部分
  2. USB描述符和MSC配置部分
  3. virtual_u_disk类的定义和方法实现
  4. SD卡初始化和文件浏览函数
  5. app_main函数,作为程序入口
#ifdef __cplusplus
extern "C" {
#endif#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"#include "tinyusb.h"
#include "tusb_msc_storage.h"#include "diskio_sdmmc.h"
#include "sd_pwr_ctrl_by_on_chip_ldo.h"#include <dirent.h>
#include <string.h>#ifdef __cplusplus
}
#endifstatic constexpr char TAG[] = "usb";/* TinyUSB descriptorsgoto sdkconfig Enable Massive Storage Class (MSC) Option
********************************************************************* */
#define EPNUM_MSC 1
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)enum { ITF_NUM_MSC = 0, ITF_NUM_TOTAL };enum {EDPT_CTRL_OUT = 0x00,EDPT_CTRL_IN = 0x80,EDPT_MSC_OUT = 0x01,EDPT_MSC_IN = 0x81,
};static tusb_desc_device_t descriptor_config = {.bLength = sizeof(descriptor_config),.bDescriptorType = TUSB_DESC_DEVICE,.bcdUSB = 0x0200,.bDeviceClass = TUSB_CLASS_MISC,.bDeviceSubClass = MISC_SUBCLASS_COMMON,.bDeviceProtocol = MISC_PROTOCOL_IAD,.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,.idVendor = 0x303A, // This is Espressif VID. This needs to be changed// according to Users / Customers.idProduct = 0x4002,.bcdDevice = 0x100,.iManufacturer = 0x01,.iProduct = 0x02,.iSerialNumber = 0x03,.bNumConfigurations = 0x01};static uint8_t const msc_fs_configuration_desc[] = {// Config number, interface count, string index, total length, attribute,// power in mATUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN,TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),// Interface number, string index, EP Out & EP In address, EP sizeTUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 64),
};#if (TUD_OPT_HIGH_SPEED)
static const tusb_desc_device_qualifier_t device_qualifier = {.bLength = sizeof(tusb_desc_device_qualifier_t),.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,.bcdUSB = 0x0200,.bDeviceClass = TUSB_CLASS_MISC,.bDeviceSubClass = MISC_SUBCLASS_COMMON,.bDeviceProtocol = MISC_PROTOCOL_IAD,.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,.bNumConfigurations = 0x01,.bReserved = 0};static uint8_t const msc_hs_configuration_desc[] = {// Config number, interface count, string index, total length, attribute,// power in mATUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN,TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),// Interface number, string index, EP Out & EP In address, EP sizeTUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 512),
};
#endif // TUD_OPT_HIGH_SPEEDstatic char const *string_desc_arr[] = {(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)"TinyUSB",                  // 1: Manufacturer"TinyUSB Device",           // 2: Product"123456",                   // 3: Serials"Example MSC",              // 4. MSC
};
/*********************************************************************** TinyUSB* descriptors*/class virtual_u_disk {
public:esp_err_t mount() {ESP_ERROR_CHECK(storage_init_sdmmc(handle()));tinyusb_msc_sdmmc_config_t config_sdmmc = {.card = *handle(),.callback_mount_changed =storage_mount_changed_cb,.mount_config{.max_files = 5}};ESP_ERROR_CHECK(tinyusb_msc_storage_init_sdmmc(&config_sdmmc));ESP_ERROR_CHECK(tinyusb_msc_register_callback(TINYUSB_MSC_EVENT_MOUNT_CHANGED, storage_mount_changed_cb));ESP_ERROR_CHECK(tinyusb_msc_storage_mount(mount_point()));// f_setlabel label max len less than 11 byte// ref http://elm-chan.org/fsw/ff/doc/setlabel.htmlif (auto ret = f_setlabel("XIONGWEI"); FR_OK != ret) {ESP_LOGI(TAG, "USB setlabel Failed code:%d", ret);}ESP_ERROR_CHECK(tinyusb_msc_storage_unmount());const tinyusb_config_t tusb_cfg = {.device_descriptor = &descriptor_config,.string_descriptor = string_desc_arr,.string_descriptor_count =sizeof(string_desc_arr) / sizeof(string_desc_arr[0]),.external_phy = false,
#if (TUD_OPT_HIGH_SPEED).fs_configuration_descriptor = msc_fs_configuration_desc,.hs_configuration_descriptor = msc_hs_configuration_desc,.qualifier_descriptor = &device_qualifier,
#else.configuration_descriptor = msc_fs_configuration_desc,
#endif // TUD_OPT_HIGH_SPEED};ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));ESP_LOGI(TAG, "USB MSC initialization DONE");return ESP_OK;}esp_err_t unmount() {ESP_ERROR_CHECK(tinyusb_msc_storage_unmount());return ESP_OK;}sdmmc_card_t **handle() {static sdmmc_card_t *sdcard_handle{nullptr};return &sdcard_handle;}static void list_files(const char *path) {DIR *dir = opendir(path);if (!dir) {ESP_LOGE(TAG, "Failed to open directory: %s", path);return;}struct dirent *entry;while ((entry = readdir(dir)) != NULL) {if (entry->d_type == DT_DIR) {DIR *subdir = opendir(path);int file_count = 0;if (subdir) {while (readdir(subdir) != NULL) {file_count++;}closedir(subdir);}ESP_LOGI(TAG, "DIR (%d): %s", file_count, entry->d_name);} else if (entry->d_type == DT_REG) {ESP_LOGI(TAG, "FILE: %s", entry->d_name);} else {ESP_LOGI(TAG, "UNKNOWN: %s", entry->d_name);}}closedir(dir);}private:static void storage_mount_changed_cb(tinyusb_msc_event_t *event) {ESP_LOGI(TAG, "Storage mounted to application: %s",event->mount_changed_data.is_mounted ? "Yes" : "No");}esp_err_t storage_init_sdmmc(sdmmc_card_t **card) {sdmmc_host_t host = SDMMC_HOST_DEFAULT();host.slot = SDMMC_HOST_SLOT_0;host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;sd_pwr_ctrl_ldo_config_t ldo_config = {.ldo_chan_id = 4,};sd_pwr_ctrl_handle_t pwr_ctrl_handle = NULL;esp_err_t ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &pwr_ctrl_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to create power control handle");return ret;}host.pwr_ctrl_handle = pwr_ctrl_handle;sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();slot_config.width = 4;slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;sdmmc_card_t *sd_card = (sdmmc_card_t *)malloc(sizeof(sdmmc_card_t));(*host.init)();ESP_ERROR_CHECK(sdmmc_host_init_slot(host.slot, (const sdmmc_slot_config_t *)&slot_config));while (sdmmc_card_init(&host, sd_card)) {ESP_LOGE(TAG, "The detection pin of the slot is disconnected(Insert uSD ""card). Retrying...");vTaskDelay(pdMS_TO_TICKS(1000));}sdmmc_card_print_info(stdout, sd_card);*card = sd_card;return ESP_OK;}const char *mount_point() { return MOUNT_POINT; }static constexpr char MOUNT_POINT[] = "/usb";
};// sdkconfig Enable Massive Storage Class (MSC) Option
// sdkconfig Enable Use FATFS volume label option to call f_setlabel function
extern "C" void app_main(void) {virtual_u_disk usb;usb.mount();
}

在这里插入图片描述


结论

本文详细讲解了如何基于ESP32和TinyUSB实现虚拟U盘功能,从USB描述符配置、MSC接口设置,到SD卡的初始化及文件系统的操作,都进行了全面的说明和代码解析。对于希望在嵌入式项目中实现USB大容量存储功能的开发者而言,这篇文章提供了一个从头到尾的实现思路。希望通过本文的学习,读者能够灵活运用这些技术,开发出符合实际需求的产品。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++:泛型算法

​操作数据&#xff0c;而非容器 一、概述 泛型算法&#xff08;Generic Algorithm&#xff09;​ 是 C Standard Template Library (STL) 的核心组成部分&#xff0c;其本质是与容器类型无关的通用操作逻辑。通过模板和迭代器机制&#xff0c;泛型算法能够对任意满足迭代器接…

UE4学习笔记 FPS游戏制作20 重写机器人和玩家死亡 切换相机和模型

定义父类中的死亡方法 在父类中定义OnDie方法&#xff0c;不需要实现&#xff0c;由子类实现各自的死亡逻辑 新建一个Die方法&#xff0c;处理公共的死亡逻辑 Die的实现&#xff1a; 以前的分离控制现在要延迟做&#xff0c;如果分离了控制器&#xff0c;就无法再获取到玩家的…

AI小白的第七天:必要的数学知识(概率)

概率 Probability 1. 概率的定义 概率是一个介于 0 和 1 之间的数&#xff0c;表示某个事件发生的可能性&#xff1a; 0&#xff1a;事件不可能发生。1&#xff1a;事件必然发生。0 到 1 之间&#xff1a;事件发生的可能性大小。 例如&#xff0c;掷一枚公平的硬币&#xf…

Occlum 是一个内存安全的、支持多进程的 library OS,特别适用于 Intel SGX。

前言 大家好&#xff0c;我是老马。 sofastack 其实出来很久了&#xff0c;第一次应该是在 2022 年左右开始关注&#xff0c;但是一直没有深入研究。 最近想学习一下 SOFA 对于生态的设计和思考。 sofaboot 系列 SOFABoot-00-sofaboot 概览 SOFABoot-01-蚂蚁金服开源的 s…

【day1】数据结构刷题 链表

一 反转链表 206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]…

canvas学习:如何绘制带孔洞的多边形

在canvas中可以通过路径绘制多边形&#xff0c;但是多边形有一种特殊的情况就是带有孔洞的多边形。这种多边形又该如何绘制呢&#xff0c;今天我就来探究一下这个问题 一、使用通常的方法绘制&#xff08;失败&#xff09; 我准备了如下的两组坐标&#xff0c;outer构成了多边…

Linux信号的诞生与归宿:内核如何管理信号的生成、阻塞和递达?

个人主页&#xff1a;敲上瘾-CSDN博客 个人专栏&#xff1a;Linux学习、游戏、数据结构、c语言基础、c学习、算法 目录 一、认识信号 二、信号的产生 1.键盘输入 2.系统调用 3.系统指令 4.硬件异常 5.软件条件 三、信号的保存 1.block 2.pending 3.handler 四、信号…

阳台光伏新守护者:电流传感器助力安全发电

安科瑞顾强 插即用光伏&#xff08;Plug-In Solar PV&#xff09;以其便捷的安装方式和亲民的准入标准&#xff0c;正在推动欧洲能源结构的革新性转变。根据SolarPower Europe发布的最新行业报告显示&#xff0c;预计到2025年&#xff0c;仅德国通过认证的即插即用光伏系统注册…

【工程记录】QwQ-32b 8bit量化部署教程(vLLM | 缓解复读)

文章目录 写在前面1. 环境配置2. 下载QwQ-32b 8bit量化模型3. 使用vLLM本地推理 写在前面 仅作个人学习记录用。本文记录QwQ-32b 8bit量化模型的部署的详细方法。 1. 环境配置 以下环境经测试无bug&#xff08;Deepseek R1用这个环境也能直接跑&#xff09;&#xff1a; gp…

Elasticsearch 入门

Elasticsearch 入门 1. 认识 Elasticsearch 1.1 现有查询数据存在的问题 查询效率较低 由于数据库模糊查询不走索引&#xff0c;在数据量较大的时候&#xff0c;查询性能很差。 功能单一 数据库的模糊搜索功能单一&#xff0c;匹配条件非常苛刻&#xff0c;必须恰好包含用户…

Docker镜像相关命令(Day2)

文章目录 前言一、问题描述二、相关命令1.查看镜像2.搜索镜像3.拉取镜像4.删除镜像5.镜像的详细信息6.标记镜像 三、验证与总结 前言 Docker 是一个开源的容器化平台&#xff0c;它让开发者能够将应用及其依赖打包到一个标准化的单元&#xff08;容器&#xff09;中运行。在 D…

网站服务器常见的CC攻击防御秘籍!

CC攻击对网站的运营是非常不利的&#xff0c;因此我们必须积极防范这种攻击&#xff0c;但有些站长在防范这种攻击时可能会陷入误区。让我们先了解下CC攻击&#xff01; CC攻击是什么 CC是DDoS攻击的一种&#xff0c;CC攻击是借助代理服务器生成指向受害主机的合法请求&#x…

【PICO】开发环境配置准备

Unity编辑器配置 安装Unity编辑器 安装UnityHub 安装Unity2021.3.34f1c1 添加安卓平台模块 Pico软件资源准备 资源准备地址&#xff1a;Pico Developer PICO SDK PICO Unity Integration SDK PICO Unity Integration SDK 为 PICO 基于 Unity 引擎研发的软件开发工具…

传输层安全协议 SSL/TLS 详细介绍

传输层安全性协议TLS及其前身安全套接层SSL是一种安全传输协议&#xff0c;目前TLS协议已成为互联网上保密通信的工业标准&#xff0c;在浏览器、邮箱、即时通信、VoIP等应用程序中得到广泛的应用。本文对SSL和TLS协议进行一个详细的介绍&#xff0c;以便于大家更直观的理解和认…

一文解读DeepSeek在工业制造领域的应用

引言 在当今数字化浪潮席卷全球的背景下&#xff0c;各个行业都在积极寻求创新与变革&#xff0c;工业制造领域也不例外。然而&#xff0c;传统工业制造在生产效率、质量控制、成本管理等方面面临着诸多挑战。在这一关键时期&#xff0c;人工智能技术的兴起为工业制造带来了新的…

3.Excel:快速分析

补充&#xff1a;快捷键&#xff1a;CTRLQ 一 格式化 1.数据条 2.色阶 3.开始菜单栏里面选择更多 补充&#xff1a;想知道代表什么意思&#xff1a;管理规则-编辑规则 二 表格 点击后会变成超级表&#xff0c;之前是普通表。 三 迷你图 图放在单元格里面。 补充&#xff1a;除了…

区间端点(java)(贪心问题————区间问题)

deepseek给了一种超级简单的做法 我是真的想不到 贪心的思路是 局部最优——>全局最优 这种我是真的没有想到&#xff0c;这样的好处就是后面便利的时候可以通过foreach循环直接便利qu的子元素也就是对应的某一个区间, 将一个二维数组变成一维数组&#xff0c;每一个一维…

STM32蜂鸣器播放音乐

STM32蜂鸣器播放音乐 STM32蜂鸣器播放音乐 Do, Re, Mi, Fa, 1. 功能概述 本系统基于STM32F7系列微控制器&#xff0c;实现了以下功能&#xff1a; 通过7个按键控制蜂鸣器发声&#xff0c;按键对应不同的音符。每个按键对应一个音符&#xff08;Do, Re, Mi, Fa, Sol, La, Si&a…

基于docker-compose 部署可道云资源管理器

容器编排Explorer 容器化部署MariaDB容器化部署Redis容器化部署PHP容器化部署Nginx编排部署compose服务 var code “9861ce02-1202-405b-b419-4dddd337aaa7” GitHub官网 KodExplorer 是一款网页文件管理器。它也是一个网页代码编辑器&#xff0c;可让你直接在网页浏览器中开…

【Git】--- Git远程操作 标签管理

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; Git 前面我们学习的操作都是在本地仓库进行了&#xff0c;如果团队内多人协作都在本地仓库操作是不行的&#xff0c;此时需要新的解决方案 --- 远程仓库。…