ESP32 分区表介绍

前言

  1. 个人邮箱:zhangyixu02@gmail.com
  2. 关于分区表,很多人看了很多资料很可能依旧是一脸懵逼。不知道各位有没有玩过 EEPROM,他可以断电保存数据。这里你也可以理解为分区表将 Flash 中划分出来了一个 EEPROM。
  3. 虽然这样说从专业的角度是毫无疑问大错特错,但是你可以这样理解。
  4. 关于各种存储器相关内容,可以阅读这篇博客 : 半岛体存储器常见类型简介
  5. 这里需要注意的一点是,当前介绍的函数并不是文件系统,整体而言是简陋和底层的。ESP32 的文件系统如果你感兴趣,会发现本质就是调用的本篇博客所介绍的函数,进行了一层封装。

CSV文件介绍

语法介绍

  1. 如下为分区表的类型介绍。
  2. 需要注意的是,当 Type 被指定为 app 类型时,flags 会被强制加密。
类型分区属性值类型
Name分区名称用于标识分区
Type类型app、data、0x40-0xFE(自定义)
SubType子类型Type=app(可选 factory、ota0 ~ ota15)
Type=data(可选 ota、phy、nvs等)
Offset偏移地址分区在 Flash 中的起始地址
Size分区大小分区占用空间
flags标志可选 加密(encrypted)和 仅可读(readonly)
  1. 如下为一个常见的分区表。一般来说,只需要在这三个分区后面追加你想要添加的内容即可。
  • nvs : 用来存储想断电保存的数据。例如每台设备的 wifi 数据,当芯片上电后,会查看这里有没有 wifi 数据,如果有就会直接连接网络,如果没有就需要进行配网。
  • phy_init : 用于存储 wifi 物理层初始化数据,这样可以保证每个设备单独配置 wifi 物理层数据,优化 wifi 性能。
  • factory : 默认的 APP 程序分区。二级 Bootloader 执行完成后立刻执行这个程序,但是需要注意的是,如果 SubType 中存在 ota 的分区,那么 Bootloader 将会检查 ota 分区内容再决定启动哪个分区里面的内容,主要是为了做 OTA 升级使用。
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs,      data, nvs,     ,        0x6000,
phy_init, data, phy,     ,        0x1000,
factory,  app,  factory, ,        1M,
  1. 我们看到上面的 offset 并没有写上偏移地址,这是为什么呢?因为有一个默认的二级 Bootloader会存储在起始地址为 0x1000 的地方,大小 0x7000。同时,我们的分区表也需要占用空间,紧跟在二级 Bootloader之后,起始地址为 0x8000,大小为0x1000。因此 nvs 起始地址为 0x9000。
  2. 想必这个时候有人可能会问了,二级 Bootloader 为什么起始地址是 0x1000 呢?这个是由 ROM 引导程序决定的,我们在 ESP-IDF 中无法修改。而且这个不同的芯片型号 二级 Bootloader 并不是固定为 0x1000 ,这个由不同的芯片型号决定。如下图所言。
芯片型号二级 Bootloader 起始地址
ESP32/ESP32S20x1000
ESP32P40x2000
其他芯片0x0000

在这里插入图片描述

  1. 虽然说,二级 Bootloader 的起始地址是固定的,大小可以通过设置分区表起始地址来配置。我们进入 menuconfig -> Partition Table -> Offset of partition table 即可。
  2. 这里需要注意,如果设置的起始地址必须是 0x1000 的倍数,因为 ESP32 的闪存扇区(最小可擦除单元) 为 0x1000(4KB)。因此,分区表虽然大概率用不上 4KB 这么大的内存,依旧给它分配这么多空间,就是因为需要进行对齐操作。

在这里插入图片描述
8. 虽然 ESP32 的闪存 扇区为 4KB,但是为了优化性能简化分区管理,所以 APP 程序必须与 块 (Block) 0x10000(64KB) 对齐。
9. 这里在总结:

  • 偏移地址 : app 分区必须与 0x10000 (64 KB) 对齐,其他分区与 0x1000 (4 KB) 对齐。
  • 大小:如果没有启用安全启动 V1,那么 app 分区大小需要与 0x1000 (4 KB) 对齐。否则 app 分区需要与 0x10000 (64 KB) 对齐。其他分区与 0x1000 (4 KB) 对齐。
  1. 这个时候我们需要思考一个问题了,不知道各位是否遇到过一个问题。如果你程序有配网相关的程序,如果配网失败,整个程序就会重启。这个时候,你在配网的时候,内容写错了,最终导致程序反复重启。之后你重新烧录程序,发现程序依旧反复重启。这个是为什么呢?
  2. 我们这个时候就可以结合上面的内容了,因为 ESP32 的 app 是需要和 0x10000(64KB) 对齐 ,为了提高程序烧录效率,程序实际是从 0x10000 开始 擦写。因此,存储配网信息的 nvs 区域并没有被擦除,你代码中可能是设置的三,如果检测到 nvs 有配网信息,那么就不再次配网直接连接,因此导致了反复配网失败,然后重启。
# shell 中调用该命令将闪存全部擦除
idf.py erase-flash
# 代码中调用该函数将 nvs 区内存闪存
nvs_flash_erase();
  1. 我们可以输入如下命令看看最终分区表的内容是否符合我上述所说的预期。可以发现,结果是符合的。
➜  sample_project idf.py partition-table

*******************************************************************************
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,data,nvs,0x9000,24K,
phy_init,data,phy,0xf000,4K,
factory,app,factory,0x10000,1M,
user,64,1,0x110000,4K,
*******************************************************************************

默认分区表和自定义分区表

  1. 乐鑫官方的 partition_table 在如下路径中可以找到。
  • partitions_singleapp_coredump.csv : 定义了一个单应用程序的分区表,其中包含一个用于核心转储(coredump)的分区。核心转储用于在设备崩溃时保存内存内容,以便进行故障排查。
  • partitions_singleapp.csv : 定义了一个单应用程序的分区表。适用于没有启用 OTA(Over-The-Air)更新的设备。
  • partitions_singleapp_encr_nvs.csv : 定义了一个单应用程序的分区表,并启用了 NVS(非易失性存储)加密。适用于需要保护 NVS 数据的场景。
  • partitions_singleapp_large_coredump.csv : 定义了一个单应用程序的分区表,包含一个较大的核心转储(coredump)分区。适用于需要更大核心转储空间的应用场景。
  • partitions_singleapp_large.csv : 定义了一个单应用程序的分区表,适用于需要较大分区空间的应用。没有启用 OTA 更新或 NVS 加密。
  • partitions_singleapp_large_encr_nvs.csv : 定义了一个单应用程序的分区表,并启用了 NVS 加密,同时分配了较大的应用程序分区。
  • partitions_two_ota_coredump.csv : 定义了一个支持双 OTA 更新的分区表,同时包含一个用于核心转储的分区。适用于需要 OTA 更新和核心转储功能的设备。
  • partitions_two_ota.csv : 定义了一个支持双 OTA 更新的分区表。不包含核心转储分区。适用于需要 OTA 更新的设备。
  • partitions_two_ota_encr_nvs.csv : 定义了一个支持双 OTA 更新的分区表,并启用了 NVS 加密。适用于需要 OTA 更新和保护 NVS 数据的设备。
${esp-idf}/components/partition_table
  1. 我们可以进入 menuconfig 找到 (Top) → Partition Table → Partition Table 路径配置自己希望的分区表类型。
  • Single factory app, no OTA : 使用上述的 partitions_singleapp.csv 分区表
  • Single factory app (large), no OTA : 使用上述的 partitions_singleapp_large.csv 分区表
  • Factory app, two OTA definitions : 使用上述的 partitions_two_ota.csv 分区表
  • Custom partition table CSV : 自定义分区表

在这里插入图片描述

  1. 如果是采用的自定义分区,我们可以在 menuconfig 的 (Top) → Partition Table -> Custom partition CSV file 中配置自定义分区表文件名称

在这里插入图片描述

常见 API 介绍

寻找分区

esp_partition_find

  1. 根据给定的分区类型、子类型和标签查找符合条件的所有分区。他最终返回的是一个迭代器。
/**
 * @brief 根据一个或多个参数查找分区
 *
 * @param type 分区类型,可以是 esp_partition_type_t 的值或 8 位无符号整数。
 *             要查找所有类型的分区,可以使用 ESP_PARTITION_TYPE_ANY,并将
 *             subtype 参数设置为 ESP_PARTITION_SUBTYPE_ANY。
 * @param subtype 分区子类型,可以是 esp_partition_subtype_t 的值或 8 位无符号整数。
 *                要查找所有给定类型的分区,可以使用 ESP_PARTITION_SUBTYPE_ANY。
 * @param label (可选)分区标签。如果要查找特定名称的分区,请设置此值。
 *             否则传递 NULL。
 *
 * @return 可以用于枚举所有找到的分区的迭代器,如果未找到任何分区,则返回 NULL。
 *         通过此函数获得的迭代器在不再使用时必须使用 esp_partition_iterator_release 释放。
 */
esp_partition_iterator_t esp_partition_find(esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label);
  1. 用术语解释可能会比较麻烦,这里直接上代码会方便一点。假设现在我们有两个 Type 和 SubType 一样的数据,我想将两个都给找到。那么就可以使用如下方法
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs,      data, nvs,     ,        0x6000,
phy_init, data, phy,     ,        0x1000,
factory,  app,  factory, ,        1M,
user,     0x40, 0x01,    ,        0x1000,
user1,     0x40, 0x01,    ,        0x1000,
    esp_partition_iterator_t it = esp_partition_find(USER_PARTITION_TYPE, USER_PARTITION_SUBTYPE, NULL);
    if (it == NULL) {
        ESP_LOGI(TAG,"esp_partition_find err");
        return;
    }

    const esp_partition_t* partition;
    while ((partition = esp_partition_get(it)) != NULL) {
        // 处理分区
        ESP_LOGI(TAG,"Found partition: %s\n", partition->label);
        // 移动到下一个分区
        it = esp_partition_next(it);
        if (it == NULL) {
            break; // 如果没有更多分区,退出循环
        }
    }
    esp_partition_iterator_release(it); // 释放迭代器
  1. 最终打印内容
I (429) main: Found partition: user

I (429) main: Found partition: user1

esp_partition_find_first

  1. 找到指定分区,与上面的区别在于,如果有两块 Type 、 SubType 和 label 一样的,那么他将只会找到第一个数据。如果你指定了 Type、SubType 和 label,我个人建议使用这个函数,因为他找到对应的数据之后会立刻返回,并不会浪费时间继续往下执行。
/**
 * @brief 根据一个或多个参数查找第一个分区
 *
 * @param type 分区类型,可以是 esp_partition_type_t 的值或 8 位无符号整数。
 *             要查找所有类型的分区,可以使用 ESP_PARTITION_TYPE_ANY,并将
 *             subtype 参数设置为 ESP_PARTITION_SUBTYPE_ANY。
 * @param subtype 分区子类型,可以是 esp_partition_subtype_t 的值或 8 位无符号整数。
 *                要查找所有给定类型的分区,可以使用 ESP_PARTITION_SUBTYPE_ANY。
 * @param label (可选)分区标签。如果要查找特定名称的分区,请设置此值。
 *             否则传递 NULL。
 *
 * @return 指向 esp_partition_t 结构的指针,如果未找到分区,则返回 NULL。
 *         该指针在应用程序的生命周期内有效。
 */
const esp_partition_t* esp_partition_find_first(esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label);
  1. 代码
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs,      data, nvs,     ,        0x6000,
phy_init, data, phy,     ,        0x1000,
factory,  app,  factory, ,        1M,
user,     0x40, 0x01,    ,        0x1000,
user1,     0x40, 0x01,    ,        0x1000,
    //找到自定义分区,返回分区指针,后续用到这个指针进行各种操作
    partition_res = esp_partition_find_first(USER_PARTITION_TYPE,USER_PARTITION_SUBTYPE,NULL);
    if(partition_res == NULL)
    {
        ESP_LOGI(TAG,"Can't find partition,return");
        return;
    }
    ESP_LOGI(TAG,"esp_partition_find_first Found partition: %s\n", partition_res->label);
  1. 最终打印内容
I (439) main: esp_partition_find_first Found partition: user

迭代器进行的操作

esp_partition_get

  1. 当我们调用 esp_partition_find() 函数获取到迭代器了,这时就需要得到迭代器中的分区信息,此时就可以调用当前函数。使用方法参考 esp_partition_find() 介绍的例程。
/**
 * @brief 获取给定分区的 esp_partition_t 结构
 *
 * @param iterator 使用 esp_partition_find 获得的迭代器。必须非 NULL。
 *
 * @return 指向 esp_partition_t 结构的指针。该指针在应用程序的生命周期内有效。
 */
const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator);

esp_partition_next

  1. 在讲解 esp_partition_find() 函数的时候,我们需要依次打印所有符合条件的分区信息,那么就需要调用当前函数进行移动。
/**
 * @brief 移动分区迭代器到下一个找到的分区
 *
 * 这个调用后的迭代器副本将失效。
 *
 * @param iterator 使用 esp_partition_find 获得的迭代器。必须非 NULL。
 *
 * @return 如果未找到分区,则返回 NULL,否则返回有效的 esp_partition_iterator_t。
 */
esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t iterator);

esp_partition_iterator_release

  1. 当我们使用完迭代器后,就需要调用当前函数释放迭代器。
/**
 * @brief 释放分区迭代器
 *
 * @param iterator 使用 esp_partition_find 获得的迭代器。
 *                 迭代器可以是 NULL,因此在调用此函数之前不需要检查其值。
 *
 */
void esp_partition_iterator_release(esp_partition_iterator_t iterator);

分区中常见操作 API

esp_partition_erase_range

  1. 这个是进行擦除操作,你需要传入需要擦除的分区。需要注意,因为扇区为 0x1000(4kb) 因此你的偏移地址和擦写范围需要和 0x1000(4kb) 对齐。
/**
 * @brief 擦除分区的一部分
 *
 * @param partition 使用 esp_partition_find_first 或 esp_partition_get 获取的分区结构的指针。
 *                  必须非空。
 * @param offset 擦除操作的起始偏移量,从分区开始处计算。
 *               必须与 partition->erase_size 对齐。
 * @param size 要擦除的范围大小,以字节为单位。
 *             必须是 partition->erase_size 的整数倍。
 *
 * @return 如果范围成功擦除,返回 ESP_OK;
 *         如果迭代器或 dst 为 NULL,返回 ESP_ERR_INVALID_ARG;
 *         如果擦除超出了分区范围,返回 ESP_ERR_INVALID_SIZE;
 *         如果分区为只读,返回 ESP_ERR_NOT_ALLOWED;
 *         或者返回来自底层闪存驱动程序的错误代码之一。
 */
esp_err_t esp_partition_erase_range(const esp_partition_t* partition,
                                    size_t offset, size_t size);

  1. 如下为打印扇区大小和进行擦写的示例。
    // 打印扇区
    ESP_LOGI(TAG,"partition->erase_size : 0x%lx",partition_res->erase_size);
    // 擦除
    ESP_ERROR_CHECK(esp_partition_erase_range(partition_res,0*partition_res->erase_size,1*partition_res->erase_size));

esp_partition_write

  1. 这里向指定的分区写入数据,需要注意,如果是对标有**加密(encryption)**标志的区域,该函数将会变成 esp_flash_write_encrypted() 函数自动写入,此时这里的 dst_offset 和 size 要求 16 字节的倍数
  2. 如果是没有加密的分区,那么将不会存在这样的限制。
/**
 * @brief 向分区中写入数据
 *
 * 在将数据写入闪存之前,需要先擦除闪存的相应区域。
 * 可以使用 esp_partition_erase_range 函数完成此操作。
 *
 * 标记为加密的分区将自动通过 esp_flash_write_encrypted() 函数进行写入。如果写入加密分区,所有写入偏移量和长度必须是 16 字节的倍数。有关详细信息,请参见 esp_flash_write_encrypted() 函数。未加密的分区没有此限制。
 *
 * @param partition 指向通过 esp_partition_find_first 或 esp_partition_get 获得的分区结构的指针。必须非 NULL。
 * @param dst_offset 数据应写入的地址,相对于分区的开始位置。
 * @param src 指向源缓冲区的指针。指针必须非 NULL,且缓冲区至少要有 'size' 字节长。
 * @param size 要写入的数据大小,以字节为单位。
 *
 * @note 在将数据写入闪存之前,请确保通过 esp_partition_erase_range 调用擦除闪存。
 *
 * @return ESP_OK,表示数据写入成功;
 *         ESP_ERR_INVALID_ARG,表示 dst_offset 超过分区大小;
 *         ESP_ERR_INVALID_SIZE,表示写入超出分区边界;
 *         ESP_ERR_NOT_ALLOWED,表示分区为只读;
 *         或来自低级闪存驱动程序的错误代码之一。
 */
esp_err_t esp_partition_write(const esp_partition_t* partition,
                              size_t dst_offset, const void* src, size_t size);

esp_partition_read

  1. 该函数将会从分区表指定的区域读取数据。操作的单位为字节。
/**
 * @brief 从分区中读取数据
 *
 * 标记为加密的分区将自动通过缓存映射进行读取和解密。
 *
 * @param partition 指向通过 esp_partition_find_first 或 esp_partition_get 获得的分区结构的指针。必须非 NULL。
 * @param dst 指向应存储数据的缓冲区的指针。指针必须非 NULL,且缓冲区至少要有 'size' 字节长。
 * @param src_offset 要读取的数据的地址,相对于分区的开始位置。
 * @param size 要读取的数据大小,以字节为单位。
 *
 * @return ESP_OK,表示数据读取成功;
 *         ESP_ERR_INVALID_ARG,表示 src_offset 超过分区大小;
 *         ESP_ERR_INVALID_SIZE,表示读取超出分区边界;
 *         或来自低级闪存驱动程序的错误代码之一。
 */
esp_err_t esp_partition_read(const esp_partition_t* partition,
                             size_t src_offset, void* dst, size_t size);

示例

修改 menuconfig

  1. 进入 menuconfig 找到 (Top) → Partition Table → Partition Table 设置为 Custom partition table CSV。
    在这里插入图片描述
  2. 进入 menuconfig 找到 (Top) → Partition Table → Custom partition CSV file 中配置自定义分区表文件名称
    在这里插入图片描述

修改 csv 文件

  1. 因为上面我们设置的自定义分区表文件名称为 partitions_user.csv,因此我们需要创建一个名称为 partitions_user.csv 的文件,然后加入如下内容。
# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs,      data, nvs,     ,        0x6000,
phy_init, data, phy,     ,        0x1000,
factory,  app,  factory, ,        1M,
user,     0x40, 0x01,    ,        0x1000,
user1,     0x40, 0x01,    ,        0x1000,

调整 c 文件

  1. 如下代码为上述内容的集合。
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_partition.h"

static const char* TAG = "main";

#define USER_PARTITION_TYPE     0x40        //自定义的分区类型
#define USER_PARTITION_SUBTYPE  0x01        //自定义的分区子类型

//读取缓存
static char g_esp_buf[1024];

void app_main(void)
{
    esp_partition_iterator_t it = esp_partition_find(USER_PARTITION_TYPE, USER_PARTITION_SUBTYPE, NULL);
    if (it == NULL) {
        ESP_LOGI(TAG,"esp_partition_find err");
        return;
    }

    const esp_partition_t* partition;
    while ((partition = esp_partition_get(it)) != NULL) {
        // 处理分区
        ESP_LOGI(TAG,"Found partition: %s\n", partition->label);
        // 移动到下一个分区
        it = esp_partition_next(it);
        if (it == NULL) {
            break; // 如果没有更多分区,退出循环
        }
    }
    esp_partition_iterator_release(it); // 释放迭代器

    //分区指针
    static const esp_partition_t* partition_res = NULL;
    // 找到自定义分区,返回分区指针,后续用到这个指针进行各种操作
    partition_res = esp_partition_find_first(USER_PARTITION_TYPE,USER_PARTITION_SUBTYPE,NULL);
    if(partition_res == NULL)
    {
        ESP_LOGI(TAG,"Can't find partition,return");
        return;
    }
    ESP_LOGI(TAG,"esp_partition_find_first Found partition: %s\n", partition_res->label);
    // 打印扇区大小
    ESP_LOGI(TAG,"partition->erase_size : 0x%lx",partition_res->erase_size);
    // 擦除
    ESP_ERROR_CHECK(esp_partition_erase_range(partition_res,0*partition_res->erase_size,1*partition_res->erase_size));
    // 测试字符串
    const char* test_str = "this is for test string";
    // 从分区偏移位置 5 写入字符串
    ESP_ERROR_CHECK(esp_partition_write(partition_res,5, test_str, strlen(test_str)));
    // 从分区偏移位置 10 读取字符串
    ESP_ERROR_CHECK(esp_partition_read(partition_res,10, g_esp_buf, strlen(test_str)-5));
    ESP_LOGI(TAG,"Read partition str:%s",g_esp_buf);
    while(1)
    {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

参考

  1. idf.py menuconfig
  2. 乐鑫官方文档 : 引导加载程序(Bootloader)
  3. 乐鑫官方文档 : 分区表
  4. B站:【2024最新版 ESP32教程(基于ESP-IDF)】ESP32入门级开发课程 更新中 中文字幕

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

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

相关文章

对于llama3.1 8B模型,FP32和BF16混合精度训练,用的是AdamW优化器,模型训练时占用显存分析

目录 为什么先不考虑激活值的显存占用 1. 模型参数 含义 计算 2. 梯度参数 含义 3. 优化器参数 含义 4. 较固定总显存占用 计算 详细解释 5. 激活值计算&#xff1a; 计算公式 插入数值 计算步骤 结论 显存主要被用在四个模块上&#xff1a; 模型权重本身 梯度…

C语言基础(十一)

1、指针&#xff1a; C语言中的指针是一种非常重要的数据类型&#xff0c;可以直接访问和操作内存地址。指针存储变量的内存地址&#xff0c;而不是变量的值本身。通过使用指针&#xff0c;可以灵活地控制数据的存储和访问&#xff0c;实现复杂的数据结构如链表、树。 定义指…

Redis (day 3)

一、通过jedis连接数据库 1.首先导入依赖 <!-- https://mvnrepository.com/artifact/redis.clients/jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>5.1.0</version></de…

Mac系统安装Homebrew【已成功】

1、正常安装失败原因 1.1命令行安装失败 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 原因 没挂&#x1fa9c;&#xff0c;不过我挂了梯子安装很多次也还是失败&#xff0c;所以可能是网站原因 1.2、网…

MyBatis进阶-1-面向接口编程

通过 MyBatis 底层自动创建接口实现类&#xff0c;我们可以直接对接口的方法进行编程 若简单的 sql 语句可以使用注解的方式进行&#xff0c;复杂的查询建议使用 xml 文件编写语句 注解使用时直接在接口的方法上加上对应语句的注解即可&#xff0c;而使用 xml 需要在文件中的…

ES6解构赋值详解;全面掌握:JavaScript解构赋值的终极指南

目录 全面掌握&#xff1a;JavaScript解构赋值的终极指南 一、数组解构赋值 1、基本用法 2、跳过元素 3、剩余元素 4、默认值 二、对象解构赋值 1、基本用法 2、变量重命名 3、默认值 4、嵌套解构 三、复杂的嵌套结构解构 四、函数参数解构赋值 1、对象解构作为函…

Jenkins汉化配置详解

Window安装构建神器Jenkins Window安装构建神器Jenkins详细教程-CSDN博客DevOps&#xff0c;CI&#xff0c;CD&#xff0c;自动化简单介绍选择其他需要和Jenkins一起安装的服务&#xff0c;点击Next。https://blog.csdn.net/qq_37237487/article/details/141299623 登录进入J…

【机器学习】CNN的基本架构模块

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 CNN的基本架构模块1. 引言2. 卷积层2.1 基本原理2.2 卷积层的特性2.3 卷积层的超…

SQL,解析 json

Google BigQuery数据库的data表存储了若干多层的Json串&#xff0c;其中一条形如&#xff1a; [{"active":true,"key":"key1","values":[{"active":true,"value":"value1"}]},{"active":tru…

Java巅峰之路---进阶篇---面向对象(二)

Java巅峰之路---进阶篇---面向对象&#xff08;二&#xff09; 多态介绍多态调用成员的特点多态的优势、弊端以及解决方案综合练习 包和final包的介绍使用其他类的规则&#xff08;导包&#xff09;final关键字final的用途常量 权限修饰符和代码块权限修饰符的介绍四个权限修饰…

Halo个人博客Docker部署结合内网穿透为本地站点配置公网地址远程访问

文章目录 前言1. Docker部署Halo1.1 检查Docker版本如果未安装Docker可参考已安装Docker步骤&#xff1a;1.2 在Docker中部署Halo 2. Linux安装Cpolar2.1 打开服务器防火墙2.2 安装cpolar内网穿透 3. 配置Halo个人博客公网地址4. 固定Halo公网地址 前言 本文主要介绍如何在Cen…

C#学习第二节课 ,伤害计算

伤害计算 我一直好奇游戏的伤害计算是怎么计算并输出的,这第二节课利用学过的初级语法,Console.WriteLine,Console.ReadLine(),以及基础变量,int,string 和if 判断 组合,来实现打印一下伤害计算吧! 老规矩 先上结果图 代码区域 namespace hello01 {internal class Program …

望繁信科技荣膺上海市浦东新区博士后创新实践基地称号

近日&#xff0c;上海望繁信科技有限公司&#xff08;简称“望繁信科技”&#xff09;凭借在大数据流程智能领域的卓越表现&#xff0c;成功入选上海市浦东新区博士后创新实践基地。这一荣誉不仅是对望繁信科技创新能力和技术实力的高度认可&#xff0c;也标志着公司在推动产学…

EasyCVR视频汇聚平台构建远程安防监控:5大亮点解析,助力安防无死角

随着科技的飞速发展&#xff0c;远程安防监控系统已经成为现代社会中不可或缺的一部分&#xff0c;无论是在小区、公共场所还是工业领域&#xff0c;安防监控都发挥着至关重要的作用。而EasyCVR作为一款功能强大的视频监控综合管理平台&#xff0c;其在构建远程安防监控系统方面…

Qt 学习第六天:页面布局

如何设计页面&#xff1f; 有个类似沙盒模式的玩法&#xff0c;Qt Widget Designer可以更好的帮助我们设计页面 点击.ui文件进入 右上方可以看到四种常见的布局&#xff1a; 四种布局 &#xff08;一&#xff09;水平布局horizontalLayout&#xff1a;QHBoxLayout H 是 hori…

算法之工程化内容(3)—— Docker常用命令

目录 1. 配置docker镜像加速 2. 创建镜像docker-name 3. 查看正在运行的镜像 4. 拉取镜像 5. 运行镜像 6. 停止/启动指定 id 的容器 7. 删除指定 id 的镜像/容器 8. docker发布和部署 (推荐教程&#xff1a;&#x1f69a; 发布和部署 - Docker 快速入门) 1. 配置docke…

【蓝桥杯集训100题】scratch时间计算 蓝桥杯scratch比赛专项预测编程题 集训模拟练习题第26题

目录 scratch时间计算 一、题目要求 编程实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 5、python资料 scratc…

【网络】UDP和TCP之间的差别和回显服务器

文章目录 UDP 和 TCP 之间的差别有连接/无连接可靠传输/不可靠传输面向字节流/面向数据报全双工/半双工 UDP/TCP API 的使用UDP APIDatagramSocket构造方法方法 DatagramPacket构造方法方法 回显服务器&#xff08;Echo Server&#xff09;1. 接收请求2. 根据请求计算响应3. 将…

AdMob聚合平台

Google Admob产品介绍 Google给开发者提供了3款用于流量变现的产品&#xff0c;分别是AdMob&#xff0c;通过应用内广告帮助App开发者变现&#xff1b;AdSense&#xff0c;通过网站广告帮助所有者变现&#xff1b;Google Ads Manager&#xff0c;通过全面管理和优化广告资源&a…

[ICLR-24] LRM: Large Reconstruction Model for Single Image to 3D

[pdf | proj | code] 本文首次提出大型重建模型&#xff08;Large Reconstruction Model, LRM&#xff09;&#xff0c;实现5s内对单图物体的3D重建。在128张A100&#xff08;40G&#xff09;上训练30 epochs。 LRM包含三个部分&#xff0c;具体框架如下&#xff1a; 图片编码…