ESP32-C3 入门笔记08:多帧数据解析

BLE 每帧协议格式:


问题:

ESP32设备通过BLE接收微信小程序端发送的数据,而且是多帧数据。

数据解析函数初始代码逻辑如下:

c 复制代码
// 数据解析函数
void esp_ble_parse_data(uint8_t *frame)
{
    if (frame[0] != FRAME_HEADER || frame[53] != FRAME_TAIL) // 判断帧头/帧尾
    {
        ESP_LOGE(ESP_BLE_TAG, "Invalid frame header or tail");
        return;
    }

    esp_ble_data_frame_t *parsed_frame = (esp_ble_data_frame_t *)frame;
    // uint8_t computed_checksum = esp_ble_calculate_checksum(frame + 1, 52); // 校验和计算 1-54  0-53 0-52  数据长度 54 /
    // if (computed_checksum != parsed_frame->checksum)
    // {
    //     ESP_LOGE(ESP_BLE_TAG, "Checksum mismatch: expected 0x%02X, got 0x%02X", computed_checksum, parsed_frame->checksum);
    //     return;
    // }

    ESP_LOGI(ESP_BLE_TAG, "Valid frame received");
    ESP_LOGI(ESP_BLE_TAG, "Mode: 0x%02X, Data Length: %d, Interval: 0x%02X", parsed_frame->mode, parsed_frame->data_length, parsed_frame->interval);

    // 根据模式设置模式变量
    switch (parsed_frame->mode)
    {
    case 0x01:
        t_show_mode = true;
        clear_erase_key(esp_nvs_keys[0]);
        clear_erase_key(esp_nvs_keys[2]);
        save_show_mode(esp_nvs_keys[0], t_show_mode); // 保存转灯的显示模式
        save_img_data(esp_nvs_keys[2], tsl_static_img, parsed_frame->data, sizeof(parsed_frame->data));
        ESP_LOGI(ESP_BLE_TAG, "转灯静图模式, t_show_mode = %d", t_show_mode);
        break;
    case 0x02:
        t_show_mode = false;
        clear_erase_key(esp_nvs_keys[0]);
        clear_erase_key(esp_nvs_keys[3]);
        save_show_mode(esp_nvs_keys[0], t_show_mode); // 保存转灯的显示模式
        save_img_data(esp_nvs_keys[3], tsl_dynamic_img, parsed_frame->data, sizeof(parsed_frame->data));
        ESP_LOGI(ESP_BLE_TAG, "转灯动态图模式, t_show_mode = %d", t_show_mode);
        break;
    case 0x03:
        d_show_mode = true;
        clear_erase_key(esp_nvs_keys[1]);
        clear_erase_key(esp_nvs_keys[4]);
        save_show_mode(esp_nvs_keys[1], d_show_mode); // 保存日行灯的显示模式
        save_img_data(esp_nvs_keys[4], drl_static_img, parsed_frame->data, sizeof(parsed_frame->data));
        ESP_LOGI(ESP_BLE_TAG, "日行灯静图模式, d_show_mode = %d", d_show_mode);
        xEventGroupSetBits(light_event_group, EVENT_RUNNING_LIGHT); // 触发日行灯显示刷新
        break;
    case 0x04:
        // d_show_mode = false;
        // clear_erase_key(esp_nvs_keys[1]);
        // save_show_mode(esp_nvs_keys[1], d_show_mode); // 保存日行灯的显示模式
        // // clear_erase_key(esp_nvs_keys[5]);
        // // save_img_data(esp_nvs_keys[5], drl_static_img, parsed_frame->data, sizeof(parsed_frame->data));
        // ESP_LOGI(ESP_BLE_TAG, "日行灯动态图模式, d_show_mode = %d", d_show_mode);
        // xEventGroupSetBits(light_event_group, EVENT_RUNNING_LIGHT); // 触发日行灯显示刷新

/*********************************************/
        // uint8_t total_frames = (parsed_frame->data_length >> 4) & 0x0F; // 高位表示总包数 最大6
        // uint8_t frame_index = parsed_frame->data_length & 0x0F;         // 低位表示包序号 最小1
        // // 计算延时时间(单位:毫秒)
        // uint8_t drl_dynamic_img_delay_time = parsed_frame->interval * 100;
        // (void)drl_dynamic_img_delay_time; // 暂时忽略未使用警告

        // // 检查总包数和当前帧序号的有效性
        // if (total_frames > 6 || frame_index < 1 || frame_index > total_frames)
        // {
        //     ESP_LOGE(ESP_BLE_TAG, "Invalid frame index (%d) or total frames (%d)", frame_index, total_frames);
        //     return;
        // }

        // // 分配缓冲区
        // uint8_t *drl_dynamic_img_buffer = (uint8_t *)malloc(48 * total_frames);
        // if (!drl_dynamic_img_buffer)
        // {
        //     ESP_LOGE(ESP_BLE_TAG, "Failed to allocate memory for dynamic image buffer");
        //     return;
        // }

        // // 拷贝数据
        // memcpy(drl_dynamic_img_buffer + (frame_index - 1) * 48, parsed_frame->data, 48);

        // // 检查是否接收到所有帧
        // if (frame_index == total_frames)
        // {
        //     // drl_dynamic_img_delay = drl_dynamic_img_delay_time;
        //     d_show_mode = false;
        //     clear_erase_key(esp_nvs_keys[1]);
        //     clear_erase_key(esp_nvs_keys[5]);

        //     save_show_mode(esp_nvs_keys[1], d_show_mode); // 保存日行灯的显示模式
        //     save_img_data(esp_nvs_keys[5], drl_dynamic_img, drl_dynamic_img_buffer, 48 * total_frames);
            
        //     ESP_LOGI(ESP_BLE_TAG, "日行灯动态图模式, d_show_mode = %d", d_show_mode);
        //     xEventGroupSetBits(light_event_group, EVENT_RUNNING_LIGHT); // 触发日行灯显示刷新
        // }

        // free(drl_dynamic_img_buffer);
        // break;
/*********************************************/




        break;
    default:
        ESP_LOGE(ESP_BLE_TAG, "Unknown mode: 0x%02X", parsed_frame->mode);
        break;
    }
}

关键代码解释:

memcpy(drl_dynamic_img_buffer + (frame_index - 1) * 48, parsed_frame->data, 48);

这段代码的作用是将当前帧的数据复制到动态图片缓冲区(drl_dynamic_img_buffer)的正确位置。以下是对每个部分的详细解析:


代码的整体功能

c 复制代码
memcpy(drl_dynamic_img_buffer + (frame_index - 1) * 48, parsed_frame->data, 48);
功能
  • 目的 :将 parsed_frame->data 中的 48 字节数据复制到 drl_dynamic_img_buffer 的指定位置。
  • 场景drl_dynamic_img_buffer 是一个存储多帧动态图片数据的缓冲区,而 parsed_frame->data 是当前帧的数据。

代码分解与解释

1. memcpy 函数

memcpy 是 C 标准库中的内存拷贝函数,用于从源地址复制指定字节数的数据到目标地址。

  • 原型

    c 复制代码
    void *memcpy(void *dest, const void *src, size_t n);
    • dest:目标内存地址(复制到这里)。
    • src:源内存地址(从这里复制)。
    • n:需要复制的字节数。
2. drl_dynamic_img_buffer + (frame_index - 1) * 48

目标地址

  • drl_dynamic_img_buffer 是整个动态图片缓冲区的起始地址。
  • (frame_index - 1) 是帧的索引,从 0 开始计算偏移量。
  • 每帧数据占用 48 字节,因此偏移量需要乘以 48

计算结果 :当前帧的数据存储到 drl_dynamic_img_buffer 偏移量为 (frame_index - 1) * 48 的位置。

3. parsed_frame->data

源地址

  • parsed_frame->data 是当前帧的数据指针,指向 48 字节的数据块。
  • 这些数据是从帧结构体中提取的,通常由某种协议或外部数据包解析得到。
4. 48

复制的字节数

  • 每帧固定大小为 48 字节,所以 memcpy 只复制 48 个字节的数据。

数据流示例

假设条件
  • drl_dynamic_img_buffer 大小为 288 字节(支持存储 6 帧,每帧 48 字节)。
  • 当前处理第 frame_index = 2 帧。
  • parsed_frame->data 包含 48 字节的内容,例如:[0x01, 0x02, ..., 0x30]
计算偏移量

目标地址:

c 复制代码
drl_dynamic_img_buffer + (frame_index - 1) * 48
= drl_dynamic_img_buffer + (2 - 1) * 48
= drl_dynamic_img_buffer + 48

数据会被存储到缓冲区的第 48 字节位置(从索引 48 到 95)。


缓冲区内容更新

初始缓冲区(假设第 1 帧已存储 0xFF):

[0xFF, 0xFF, ..., 0xFF] (48 字节,帧 1)
[未初始化] (48 字节,帧 2)
[未初始化] (192 字节,其余帧)

复制后缓冲区内容:

[0xFF, 0xFF, ..., 0xFF] (48 字节,帧 1)
[0x01, 0x02, ..., 0x30] (48 字节,帧 2)
[未初始化] (192 字节,其余帧)

使用场景与意义

  • 多帧动态图片存储 :动态图片可能由多个帧组成,每帧数据分包发送到设备。drl_dynamic_img_buffer 用于依次存储这些帧数据,最终形成完整的动态图。
  • 数据顺序管理 :通过 (frame_index - 1) 确保每帧数据存储在正确的位置,避免数据覆盖或顺序错误。
  • 通用性强:通过计算偏移量和字节数,代码可支持任意数量的帧和大小。

总结

c 复制代码
memcpy(drl_dynamic_img_buffer + (frame_index - 1) * 48, parsed_frame->data, 48);

作用:将当前帧的 48 字节数据复制到动态图片缓冲区中,存储在对应的帧位置。

核心逻辑

  1. 根据 frame_index 计算目标存储位置。
  2. 将当前帧的数据完整复制到缓冲区。
  3. 确保多帧数据按顺序存储在统一的缓冲区中,为后续处理或显示提供基础。

如果有更多细节需要补充,请随时告诉我!


代码逻辑分析与改进建议


代码功能分析

功能
  1. 解析数据包

    • total_frames:表示数据包总数(最大 6)。
    • frame_index:当前帧的序号(最小 1)。
  2. 检查数据有效性

    • 验证 total_framesframe_index 的范围。
  3. 动态分配缓冲区

    • 为总帧数据分配内存,大小为 48 * total_frames
  4. 存储帧数据

    • 将当前帧数据存储到动态缓冲区的正确位置。
  5. 接收完成时处理

    • 如果接收完成(即 frame_index == total_frames),保存数据并更新显示模式。
  6. 释放资源

    • 释放动态分配的缓冲区内存,避免内存泄漏。

关键问题与改进

问题 1:每次函数调用都会重新分配缓冲区
  • 当前代码中,缓冲区 drl_dynamic_img_buffer 每次调用函数都会重新分配,即使是处理同一个动态图片的数据包。
  • 这种做法导致内存分配重复且覆盖之前已接收的数据。
改进方法
  • 使用 静态缓冲区全局缓冲区,使所有帧数据共享一个缓冲区。
  • 示例:
c 复制代码
static uint8_t *drl_dynamic_img_buffer = NULL; // 静态缓冲区
static uint8_t total_frames_allocated = 0;    // 记录已分配的帧数

if (drl_dynamic_img_buffer == NULL) {
    drl_dynamic_img_buffer = (uint8_t *)malloc(48 * total_frames);
    if (!drl_dynamic_img_buffer) {
        ESP_LOGE(ESP_BLE_TAG, "Failed to allocate memory for dynamic image buffer");
        return;
    }
    total_frames_allocated = total_frames; // 记录分配的总帧数
} else if (total_frames != total_frames_allocated) {
    ESP_LOGE(ESP_BLE_TAG, "Inconsistent total_frames allocation");
    free(drl_dynamic_img_buffer);
    return;
}
问题 2:未处理数据包丢失情况
  • 当前逻辑假定所有数据包按顺序到达,未处理数据包丢失或乱序的问题。
  • 如果数据包丢失,最后一帧接收完成时可能会保存不完整的动态图。
改进方法
  • 增加接收状态标记,如使用一个布尔数组 frame_received[] 记录每帧接收状态。
  • 示例:
c 复制代码
static bool frame_received[6] = {false}; // 最大支持 6 帧

// 标记当前帧为已接收
frame_received[frame_index - 1] = true;

// 检查所有帧是否接收完成
bool all_received = true;
for (int i = 0; i < total_frames; i++) {
    if (!frame_received[i]) {
        all_received = false;
        break;
    }
}

if (all_received) {
    // 处理接收完成逻辑
}
问题 3:缓冲区释放时机不当
  • 缓冲区 drl_dynamic_img_buffer 在函数结束时总是释放,但在最后一帧时需要保留完整数据。
改进方法
  • 仅在数据接收完成并保存后释放缓冲区。
  • 示例:
c 复制代码
if (frame_index == total_frames) {
    // 保存数据
    save_img_data(esp_nvs_keys[5], drl_dynamic_img, drl_dynamic_img_buffer, 48 * total_frames);

    // 释放缓冲区
    free(drl_dynamic_img_buffer);
    drl_dynamic_img_buffer = NULL; // 避免悬空指针
}

改进后的完整代码

以下是改进后的代码示例:

c 复制代码
static uint8_t *drl_dynamic_img_buffer = NULL;
static uint8_t total_frames_allocated = 0;
static bool frame_received[6] = {false}; // 最大支持 6 帧

void process_dynamic_img(parsed_frame_t *parsed_frame) {
    uint8_t total_frames = (parsed_frame->data_length >> 4) & 0x0F; // 高位表示总包数 最大6
    uint8_t frame_index = parsed_frame->data_length & 0x0F;         // 低位表示包序号 最小1

    // 检查总包数和当前帧序号的有效性
    if (total_frames > 6 || frame_index < 1 || frame_index > total_frames) {
        ESP_LOGE(ESP_BLE_TAG, "Invalid frame index (%d) or total frames (%d)", frame_index, total_frames);
        return;
    }

    // 初始化缓冲区
    if (drl_dynamic_img_buffer == NULL) {
        drl_dynamic_img_buffer = (uint8_t *)malloc(48 * total_frames);
        if (!drl_dynamic_img_buffer) {
            ESP_LOGE(ESP_BLE_TAG, "Failed to allocate memory for dynamic image buffer");
            return;
        }
        total_frames_allocated = total_frames;
        memset(frame_received, 0, sizeof(frame_received));// 初始化为 0
    } else if (total_frames != total_frames_allocated) {
        ESP_LOGE(ESP_BLE_TAG, "Inconsistent total_frames allocation");
        free(drl_dynamic_img_buffer);
        drl_dynamic_img_buffer = NULL;
        return;
    }

    // 拷贝当前帧数据
    memcpy(drl_dynamic_img_buffer + (frame_index - 1) * 48, parsed_frame->data, 48);
    frame_received[frame_index - 1] = true;

    // 检查所有帧是否接收完成
    bool all_received = true;
    for (int i = 0; i < total_frames; i++) {
        if (!frame_received[i]) {
            all_received = false;
            break;
        }
    }

    if (all_received) {
        // 保存数据
        clear_erase_key(esp_nvs_keys[1]);
        clear_erase_key(esp_nvs_keys[5]);
        save_show_mode(esp_nvs_keys[1], false);
        save_img_data(esp_nvs_keys[5], drl_dynamic_img, drl_dynamic_img_buffer, 48 * total_frames);

        ESP_LOGI(ESP_BLE_TAG, "日行灯动态图模式, d_show_mode = false");
        xEventGroupSetBits(light_event_group, EVENT_RUNNING_LIGHT); // 触发显示刷新

        // 释放缓冲区
        free(drl_dynamic_img_buffer);
        drl_dynamic_img_buffer = NULL;
    }
}

主要改进点

  1. 静态缓冲区:避免每次函数调用重复分配内存。
  2. 接收状态检查:确保所有帧完整接收后再处理。
  3. 释放时机优化:仅在数据完全保存后释放缓冲区。
  4. 错误处理:增加分配失败和状态不一致的错误检查。

通过这些改进,代码的内存管理更高效,逻辑更可靠。

相关推荐
__xu_1 小时前
【Notepad++】Notepad++如何删除包含某个字符串所在的行
notepad++
Jackilina_Stone3 小时前
【HUAWEI】HCIP-AI-MindSpore Developer V1.0 | 第五章 自然语言处理原理与应用(2 自然语言处理关键技术) | 学习笔记
人工智能·笔记·学习·自然语言处理·hcip·huawei
垂杨有暮鸦⊙_⊙4 小时前
2024年6月英语六级CET6听力原文与解析
笔记·学习·六级
油炸自行车4 小时前
【阅读】认知觉醒
笔记·阅读·读书
田梓燊4 小时前
人机交互复习笔记
笔记·人机交互
LuH11245 小时前
【论文阅读笔记】MoGe: 使用最优训练监督解锁开放域图像的精确单目几何估计
论文阅读·笔记
Spcarrydoinb5 小时前
python学习笔记—15—数据容器之列表
笔记·python·学习
油焖茄子5 小时前
flutter 独立开发之笔记
笔记·flutter
nuise_5 小时前
李宏毅机器学习课程笔记02 | 机器学习任务攻略General Guide
人工智能·笔记·机器学习
vv啊vv5 小时前
ROS笔记
笔记