当前位置: 首页 > article >正文

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

BLE 每帧协议格式:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

问题:

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

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


// 数据解析函数
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)的正确位置。以下是对每个部分的详细解析:


代码的整体功能

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 标准库中的内存拷贝函数,用于从源地址复制指定字节数的数据到目标地址。

  • 原型
    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]
计算偏移量

目标地址:

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) 确保每帧数据存储在正确的位置,避免数据覆盖或顺序错误。
  • 通用性强:通过计算偏移量和字节数,代码可支持任意数量的帧和大小。

总结

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 每次调用函数都会重新分配,即使是处理同一个动态图片的数据包。
  • 这种做法导致内存分配重复且覆盖之前已接收的数据。
改进方法
  • 使用 静态缓冲区全局缓冲区,使所有帧数据共享一个缓冲区。
  • 示例:
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[] 记录每帧接收状态。
  • 示例:
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 在函数结束时总是释放,但在最后一帧时需要保留完整数据。
改进方法
  • 仅在数据接收完成并保存后释放缓冲区。
  • 示例:
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; // 避免悬空指针
}

改进后的完整代码

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

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. 错误处理:增加分配失败和状态不一致的错误检查。

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


http://www.kler.cn/a/469178.html

相关文章:

  • vue 什么时候使用v-if 什么时候使用v-show
  • Element-UI:如何实现表格组件el-table多选场景下根据数据对某一行进行禁止被选中?
  • 移动端自动化测试Appium-java
  • C++单例模式跨DLL调用问题梳理
  • 【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】
  • springboot550乐乐农产品销售系统(论文+源码)_kaic
  • Linux网络命令
  • 网络安全技能试题总结参考
  • 《深入浅出HTTPS​​​​​​​​​​​​​​​​​》读书笔记(24):椭圆曲线密码学
  • Kotlin 协程基础知识总结七 —— Flow 与 Jetpack Paging3
  • 如何实现一个充满科技感的官网(二)
  • AWS DMS基础知识
  • 第27天:PHP应用原生语法全局变量数据接受身份验证变量覆盖任意上传
  • 人工智能深度学习物体检测之YOLOV7源码解读
  • 【C语言】_野指针
  • 利用DHCP自动分配IP
  • 手工专项测试--接口测试(jmeter)--单接口与关联接口与断言
  • 百度Android最新150道面试题及参考答案 (中)
  • 《Vue进阶教程》第三十四课:toRefs的使用
  • 【开源免费】基于SpringBoot+Vue.JS作业管理系统(JAVA毕业设计)
  • [python3]Excel解析库-xlrd
  • 简历_专业技能_熟悉分布式锁Redisson的原理以及使用
  • Linux2.4.20顶层Makefile文件分析
  • 【网络安全实验室】SQL注入实战详情
  • Fast R-CNN模型详解及分析
  • 【顶刊TPAMI 2025】多头编码(MHE)之极限分类 Part 3:算法实现