ESP32 分区表介绍

news2024/9/22 7:23:54

前言

  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.coloradmin.cn/o/2050901.html

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

相关文章

Ilya Sutskever 2023年伯克利大学演讲回顾:无监督学习与GPT的数学基础

引言 在2023年&#xff0c;OpenAI联合创始人之一的Ilya Sutskever在伯克利大学进行了一次极具影响力的演讲。这场演讲虽然内容复杂晦涩&#xff0c;但却被认为是人工智能发展历史上的一个重要里程碑。在演讲中&#xff0c;Sutskever深入探讨了无监督学习的数学依据&#xff0c…

即插即用的3D神经元注意算法

在快速发展的人工智能领域&#xff0c;科技的进步往往源于对复杂问题的突破性解决方案。如今&#xff0c;我们正站在一种激动人心的技术创新的前沿——即插即用的3D神经元注意算法。这一前沿技术不仅为计算神经科学提供了全新的视角&#xff0c;也为人工智能的未来打开了新的大…

elementplus 二次封装 select 自定义指令上拉加载更多 完美解决 多次接口调用 重新加载数据多次调用!!!

ps: 我封装的 select 实现了&#xff1a;1、上拉加载更多。2、远程搜索。3、单选多选。4、二次回显之前选择的数据。5、option 里面显示的内容自定义 如果有您想要实现的功能 可以私聊我 本文只讲解上拉触底加载更多 效果&#xff1a;&#xff08;名字都是测试数据 随便乱写…

【数据结构】PTA 链式表的按序号查找 C语言

本题要求实现一个函数&#xff0c;找到并返回链式表的第K个元素。 函数接口定义&#xff1a; ElementType FindKth( List L, int K ); 其中List结构定义如下&#xff1a; typedef struct LNode *PtrToLNode; struct LNode {ElementType Data;PtrToLNode Next; }; typedef P…

Mysql原理与调优-Mysql的内存结构

1.绪论 前面说过InnoDB每次查询数据或者更新数据&#xff0c;都是先以16kb的大小将数据读取到内存中&#xff0c;然后对内存中的数据页进行操作。为了减少磁盘IO&#xff0c;Innodb的会先单独的申请一块连续的空间&#xff0c;将从磁盘中的数据页缓存到这片内存中。这片内存就…

数字化转型下的客户服务创新:智能、便捷、人性化

当今这个日新月异的数字时代&#xff0c;企业的竞争已不再局限于产品或服务的本身&#xff0c;而是延伸到了客户体验的每一个细微之处。数字化转型作为推动这一变革的重要力量&#xff0c;正深刻改变着客户服务的面貌&#xff0c;使之变得更加智能、便捷且充满人性化。 一、数字…

Xilinx 7系列收发器GTX入门讲解

目录 一、前言 二、芯片间数据传输技术发展 2.1 时钟/数据同步方式 三、 7系列GTX/GTH 3.1 GTXE2 3.2 Quad 3.3 GTXE2_CHANNEL 3.4 参考时钟结构 3.4.1 外部参考时钟 3.5 CPLL 3.6 QPLL 四、收发器GTX/GTH 4.1 发送器TX 4.2 接收器RX 4.3 TX与RX全流程分析 五、…

CeresPCL 岭回归拟合(曲线拟合)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 由于在使用最小二乘插值拟合时,会涉及到矩阵求逆的操作,但是如果这个矩阵接近于奇异时,那么拟合的结果就会与我们期望的结果存在较大差距,因此就有学者提出在最小二乘的误差函数中添加正则项,即: 这里我们也可…

Catf1ag CTF Web(一)

前言 Catf1agCTF 是一个面向所有CTF&#xff08;Capture The Flag&#xff09;爱好者的综合训练平台&#xff0c;尤其适合新手学习和提升技能 。该平台由catf1ag团队打造&#xff0c;拥有超过200个原创题目&#xff0c;题目设计注重知识点的掌握&#xff0c;旨在帮助新手掌握C…

Pycharm远程连接服务器调试程序(包含VPN连接)

一、Ubuntu服务器使用指南 1、建立个人账户&#xff1a; sudo adduser yourname 2、个人账户中建立conda环境 1&#xff09;将anaconda\miniconda安装包上传到服务器home\yourname目录下 2&#xff09;bash安装miniconda 3&#xff09;source .bashrc激活 3、MobaXterm连…

Spring Cloud Gateway 请求转发源码分析

一、背景 Spring Cloud Gateway 作为一种微服务网关组件&#xff0c;相信大家都不陌生&#xff0c;一个请求经过Spring Cloud Gateway是如何转发出去的&#xff0c;今天我们就来分析一下这部分的源码。 二、正文 下面这张图大家在学习Spring Cloud Gateway的时候肯定见过&am…

NASA数据集:DC-8 飞机上收集测量数据(冰原和永久冻土融化、雪反照率降低以及海盐气溶胶)

ARCTAS DC-8 Aircraft Merge Data 简介 ARCTAS_Merge_DC8_Aircraft_Data 是在 "从飞机和卫星收集对流层成分的北极研究 "亚轨道活动期间&#xff0c;从 DC-8 飞机上收集测量数据的各种现场仪器中预先生成的合并文件。该产品的数据收集工作已经完成。 北极是了解气…

网络编程-阻塞、非阻塞、多路复用、Selector对于accept、read、write事件监听实现详解

阻塞 理论 阻塞模式下&#xff0c;相关方法&#xff08;accept、read、write&#xff09;都会导致线程暂停。 ServerSocketChannel.accept 会在没有连接建立时让线程暂停。SocketChannel.read 会在没有数据可读时让线程暂停。阻塞的表现其实就是线程暂停了&#xff0c;暂停期…

Spring 解决bean的循环依赖

Spring循环依赖-博客园 1. 什么是循环依赖 2. 循环依赖能引发什么问题 循环依赖可能引发以下问题&#xff1a; 初始化顺序不确定&#xff1a;循环依赖导致无法确定哪个对象应该先被创建和初始化&#xff0c;从而造成初始化顺序的混乱。这可能导致错误的结果或意外的行为。死…

YouTube 创作者起诉 Nvidia 和 OpenAI

- **YouTube 创作者 David Millette**&#xff1a;一位 YouTube 创作者 David Millette 起诉了 AI 巨头 Nvidia&#xff0c;指控该公司未经许可使用他的视频来训练 AI 模型。此前不久&#xff0c;Millette 还起诉了 OpenAI&#xff0c;但并未指控这两家公司侵犯版权。 - **指控…

YOLOV8网络结构|搞懂Backbone-C2f

c2f没有改变图像的输入通道数和分辨率 传进去的和传出去的没变 n = 3*d d是模型缩放里面的depth split成两个分支:a和b 经过一个bottleneck就是加一个c 有n个bottleneck 一共是(n+2)个 学习资料:

加密创投周期进化论(下篇):失落的未来

“中心化主义收编”后时代&#xff0c;叙事枯竭怎么破&#xff1f; 作者&#xff1a;Wenser&#xff1b;编辑&#xff1a;郝方舟 出品 | Odaily星球日报&#xff08;ID&#xff1a;o-daily&#xff09; 在《加密创投周期进化论&#xff08;上篇&#xff09;&#xff1a;再造新世…

Win 10录屏也能如此专业?2024年还有3款免费工具,让你大开眼界

无论你是想捕捉游戏中的精彩瞬间&#xff0c;还是打算制作专业的教学视频&#xff0c;或者需要录制在线会议&#xff0c;Win 10都自带了很强的屏幕录制功能。今天我会告诉你怎么用Win 10自带的录屏工具&#xff0c;还会给你推荐三个好用的录屏软件&#xff0c;并且告诉你它们各…

【区块链+金融服务】农业大宗供应链线上融资平台 | FISCO BCOS应用案例

释放数据要素价值&#xff0c;FISCO BCOS 2024 应用案例征集 粮食贸易受季节性影响显著。每年的粮收季节&#xff0c;粮食收储企业会根据下游订单需求&#xff0c;从上游粮食贸易商或粮农手 里大量采购粮食&#xff0c;并分批销售给下游粮食加工企业&#xff08;面粉厂、饲料厂…

HTML—css

css概述 C S S 是 C a s c a d i n g S t y l e S h e e t s &#xff08; 级 联 样 式 表 &#xff09; 。 C S S 是 一 种 样 式 表 语 言 &#xff0c; 用 于 为 H T M L 文 档 控 制 外 观 &#xff0c; 定 义 布 局 。 例 如 &#xff0c; C S S 涉 及 字 体 、 颜 色 、…