跳转至

C语言内存数组(zbuff)

作者:王棚嶙

一、ZBUFF(C 内存数组)简介

1.1 什么是zbuff

zbuff 是 LuatOS 中用于直接操作二进制内存数据的库,类似于 C 语言中的内存指针。它提供以下核心功能:

  • 动态内存管理:申请指定长度的内存空间(支持 SRAM/PSRAM)。
  • 灵活读写:支持字节、整数、浮点数等数据类型的读写(如 readU32()writeF32())。
  • 高级操作:内存拷贝(copy)、填充(set)、比较(isEqual)、Base64 编码等。
  • 帧缓冲(FrameBuffer):可直接操作显示缓冲区(如设置像素 pixel()、画线 drawLine())。

可以在 sram 上或者 psram 上申请空间,也可以自动申请(如存在 psram 则在 psram 进行申请,如不存在或失败则在 sram 进行申请)。

操作里面的元素时,可以根据 光标进行增删改查。偏移方式有三种:从头、当前位置、末尾开始。常用参数如下:

常量 类型 解释
zbuff.SEEK_SET number 以头为基点
zbuff.SEEK_CUR number 以当前位置为基点
zbuff.SEEK_END number 以末尾为基点
zbuff.HEAP_AUTO number 自动申请(如存在 psram 则在 psram 进行申请,如不存在或失败则在 sram 进行申请)
zbuff.HEAP_SRAM number 在 sram 申请
zbuff.HEAP_PSRAM number 在 psram 申请

1.2 何时会用到 zbuff 库?

zbuff库主要用于需要高效处理原始二进制数据的场景,尤其是在嵌入式设备中,这些场景通常具有以下特点:

  • 数据量大:例如图像、音频、网络数据包等。
  • 需要频繁修改:如实时数据解析、协议封装等。
  • 内存受限:需要避免Lua字符串的不可变特性导致的内存碎片和重复拷贝。

以下场景可作为参考:

1.2.1 网络通信协议处理

  • 场景描述:在TCP/UDP通信中,接收到的数据包是原始的二进制流,需要按照协议解析(如MQTT、HTTP头部、自定义二进制协议)。
  • 为什么用zbuff:网络数据包可能很大,且需要多次读写指针位置(如解析完头部后再解析内容)。使用zbuff可以避免反复创建新字符串,减少内存分配。

1.2.2 图像/视频数据处理

  • 场景描述:摄像头采集的图像帧数据(如JPEG或RGB原始数据)需要处理(如裁剪、旋转、压缩)。
  • 为什么用zbuff:一帧图像可能占用几KB到几十KB,直接使用Lua字符串处理会因不可变性导致多次拷贝,而zbuff支持原地操作,节省内存和时间。

1.2.3 传感器原始数据解析

  • 场景描述:传感器(如加速度计、陀螺仪)通过I2C/SPI返回多字节原始数据(如6字节的XYZ三轴数据)。
  • 为什么用zbuff:传感器数据通常是小而频繁的二进制流,使用zbuff的readI16()等类型化读取接口,比手动拆解字符串更高效。

1.3 zbuff、string、pack 的关系与区别

既然已经有了string/pack库了,为什么还要单独有个zbuff库呢?

用途 区别
string 处理文本(如 JSON、URL 编码) 不可修改,拼接生成新对象;适合小文本。
pack 结构化数据打包/解包(如将数字转为大端字节序) 依赖 string 存储结果;适合协议封装(如 Modbus 帧)。
zbuff 操作原始二进制数据(如图像、网络包) 可原地修改;支持指针偏移;适合大块数据流处理。

1.3.1 string库文本的代价

  • 不可变性:无法多个变量持有同一份字符串。如:s2=s1,会将 s1 中的数据复制一份放到 s2 中。会复制整个字符串 → 内存碎片+高延迟
  • 不能直接以数组形式操作。如:不能使用 s[1]操作。而使用 s:byte(2)比较麻烦。
  • 文本局限:字符串新建后就无法修改。如:新建 local s = string.char(0x01,0x02)后,字符串是无法修改的,除非再新建一个字符串赋值给变量 s。

1.3.2 pack库结构化数据翻译

  • 核心功能:解决 字节序 和 数据类型转换
-- 打包 4 字节 IP 地址(大端序)
local ip_str = pack.pack(">I", 0xC0A80101) -- "192.168.1.1" → 0xC0A80101
  • 依赖 string:输出结果为字符串 → 再次修改需全量拷贝

需要重点强调它们的底层差异:zbuff直接操作内存块,而另外两者依赖字符串。可以举一个实际协议解析的例子说明三者的协作关系,比如先通过zbuff接收原始数据,再用pack解析特定字段,最后用string处理文本部分。

原始数据 → zbuff 承载 → pack 解析结构化部分 → string 处理文本字段 → 结果

掌握三者结合,可高效解决嵌入式开发中 99% 的数据处理问题。

二、演示功能概述

本demo进行了一个完整的 zbuff 二进制数据处理库 的演示,示了在嵌入式环境中高效处理二进制数据的全流程。项目分为三个核心模块,覆盖了从基础到高级的二进制数据处理场景

1、main.lua:主程序入口

2、zbuff_core.lua:为zbuff的基础操作模块,包含zbuff最常用的创建,读写高效查询等基础功能。

3、zbuff_advanced.lua:为zbuff高级操作模块,包含zbuff较为复杂的结构化打包,类型化操作等数据处理功能。

4、zbuff_memory.lua:为内存管理模块,核心业务逻辑为内存管理操作。

三、准备硬件环境

在介绍本功能示例之前,我们首先需要确保以下硬件环境的准备工作已经完成。

参考:硬件环境清单,准备以及组装好硬件环境。

四、软件环境清单

在开始实践本示例之前,先筹备一下软件环境:

1.Luatools工具

2.内核固件文件(底层core固件文件):

3.luatos需要的脚本和资源文件脚本和资源文件

lib脚本文件:使用Luatools烧录时,勾选 添加默认lib 选项,使用默认lib脚本文件;

4.本教程用到的API接口参考:zbuff-api地址

五、代码示例

5.1 核心功能模块 (zbuff_core.lua)

5.1.1 缓冲区管理:

(1)创建固定大小(1024字节)的缓冲区 zbuff.create

(2)索引直接访问(如 buff[0] = 0xAE)

    local buff = zbuff.create(1024)
    log.info("zbuff_core", "缓冲区创建", "长度:", buff:len()) -- 打印缓冲区长度
    -- === 缓冲区创建与初始化演示 ===
    log.info("zbuff_core", "=== 缓冲区创建与初始化演示 ===")
    buff[0] = 0xAE -- 通过索引直接访问和修改数据(索引从0开始)
    log.info("zbuff_core", "索引访问示例", "buff[0] =", buff[0])

5.1.2 基础IO操作

(1)写入字符串和数值数据(write("123"))

(2)指针控制(seek()定位操作)

(3)数据读取(read(3))

    buff:write("123")
    log.info("zbuff_core", "写入字符串", "123")
    buff:write(0x12, 0x13, 0x13, 0x33)
    log.info("zbuff_core", "写入数值", "0x12, 0x13, 0x13, 0x33")

    buff:seek(5, zbuff.SEEK_CUR)
    log.info("zbuff_core", "指针当前位置", "向后移动5字节","当前位置:", buff:used())

    buff:seek(0)
    log.info("zbuff_core", "指针移动", "重置到开头")

    local data = buff:read(3)
    log.info("zbuff_core", "读取数据", "长度3:",data)
    -- === 缓冲区清除操作 ===
    log.info("zbuff_core", "=== 缓冲区清除操作 ===")
    buff:clear()
    log.info("zbuff_core", "清除操作", "全部清零")

    buff:clear(0xA5)
    log.info("zbuff_core", "清除操作", "填充0xA5")

5.1.3 元信息查询

(1)获取缓冲区总长度(len())

(2)查询已使用空间(used())

    log.info("zbuff_core", "=== 元信息查询 ===")
    local len = buff:len()
    log.info("zbuff_core", "元信息", "总长度:", len)

    local used = buff:used()
    log.info("zbuff_core", "元信息", "已使用:", used)

5.1.4 高效数据查询(query接口)

(1)query()接口快速提取数据

(2)自动格式转换(大端序处理)

    buff:write(0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC)

    local all_data = buff:query(0, 6)
    log.info("zbuff_core", "query查询", "全部数据:" ,all_data:toHex())
     -- 查询部分数据并转换格式,查询1,2,4,8字节的时候会自动根据后续参数进行转换(大端序、无符号)
    -- 参数:起始位置0,长度4,大端序,无符号,非浮点
    local part_data = buff:query(0, 4, true, false, false)
    log.info("zbuff_core", "query查询", "大端序格式:", part_data)

5.2 高级功能模块 (zbuff_advanced.lua)

5.2.1 结构化数据处理

(1)数据打包(pack(">IIHA", ...)):支持大端序/多种数据类型

(2)数据解包(unpack(">IIHA10")):自动解析复合数据结构

-- 打包数据:大端序,2个32位整数,1个16位整数,1个字符串
    buff:pack(">IIHA", 0x1234, 0x4567, 0x12, "abcdefg")
    log.info("zbuff_advanced", "数据打包", "格式: >IIHA", "值: 0x1234, 0x4567, 0x12, 'abcdefg'")
    -- 显示打包后的二进制内容
    local packed = buff:toStr(0, buff:used())--按照起始位置和长度,取出数据,并转换为字符串
    log.info("zbuff_advanced", "打包后数据", packed:toHex())


    -- 重置指针到开头
    buff:seek(0)

    -- 解包数据:大端序,2个32位整数,1个16位整数,1个10字节字符串
    local cnt, a, b, c, s = buff:unpack(">IIHA10")
    log.info("zbuff_advanced", "数据解包", "数量:", cnt, "值:", a, b, c, s)
     -- 显示解包后的输出内容
     -- string.forma是Lua的格式化字符串函数,按照格式化参数formatstring,返回后面...内容的格式化版本。string.format("0x%X", a)表示将整数a转换为十六进制字符串。
    log.info("zbuff_advanced", "解包输出内容", 
        "cnt:", cnt, 
        "a(32位):", string.format("0x%X", a),
        "b(32位):", string.format("0x%X", b),
        "c(16位):", string.format("0x%X", c),
        "s(字符串):", s)

5.2.2 类型化操作

(1)精确类型读写:writeI8()/readU32()等

-- 重置指针到开头
    buff:seek(0)

    -- 写入8位有符号整数
    buff:writeI8(10)
    log.info("zbuff_advanced", "类型化写入", "I8:", 10)

    -- 写入32位无符号整数
    buff:writeU32(1024)
    log.info("zbuff_advanced", "类型化写入", "U32:", 1024)

    -- 重置指针到开头
    buff:seek(0)

    -- 读取8位有符号整数
    local i8data = buff:readI8()
    log.info("zbuff_advanced", "类型化读取", "I8:", i8data)

    -- 读取32位无符号整数
    local u32data = buff:readU32()
    log.info("zbuff_advanced", "类型化读取", "U32:", u32data)

5.2.3 浮点处理

(1)单精度浮点写入(writeF32(1.2))

(2)浮点数据读取(readF32())

buff:seek(0)

    -- 写入32位浮点数
    buff:writeF32(1.2)
    log.info("zbuff_advanced", "浮点数操作", "写入F32:", 1.2)

    -- 重置指针到开头
    buff:seek(0)

    -- 读取32位浮点数
    local f32data = buff:readF32()
    log.info("zbuff_advanced", "浮点数操作", "读取F32:", f32data)

5.3 内存管理模块 (zbuff_memory.lua)

5.3.1 动态内存管理

(1)缓冲区动态扩容resize(2048)

    local buff = zbuff.create(1024)
    local original_size = buff:len()
    buff:resize(2048)  -- 扩容到2048字节
    log.info("zbuff_memory", "大小调整", "原始大小:", original_size, "新大小:", buff:len())

5.3.2 块操作

(1)内存块设置(set(10,0xaa,5))类似 memset

(2)数据删除(del(2,3))及前移

    buff:set(10,0xaa,5)
    log.info("zbuff_memory", "内存设置", "位置10-14设置为0xaa")
    -- 验证设置结果
    log.info("zbuff_memory", "验证结果", "位置10:", buff[10], "应为0xaa")

    -- 3. 数据删除操作
    -- 写入测试数据
    buff:clear()
    buff:seek(0)
    buff:write("ABCDEFGH")
    log.info("zbuff_memory", "删除前数据", buff:toStr())

    -- 删除位置2开始的3个字节
    buff:del(2, 3)
    log.info("zbuff_memory", "删除操作", "删除位置2-4", "结果:", buff:toStr())

5.3.3 数据工具

(1)内存比较(isEqual())

(2)Base64编码转换(toBase64())

    local buff2 = zbuff.create(10)
    buff2:write("12345")

    -- 比较两个缓冲区前5字节内容
    local equal, offset = buff:isEqual(0, buff2, 0, 5)
    log.info("zbuff_memory", "内存比较", "结果:", equal, "差异位置:", offset)

    -- 5. Base64编码转换
    local dst = zbuff.create(buff:used() * 2)  -- 创建足够大的目标缓冲区

    -- 进行Base64编码
    local len = buff:toBase64(dst)
    log.info("zbuff_memory", "Base64编码", "长度:", len, "结果:", dst:toStr(0, len))

六、功能验证

6.1 核心功能模块 (zbuff_core.lua)

img

6.2 高级功能模块 (zbuff_advanced.lua)

img

6.3 内存管理模块 (zbuff_memory.lua)

img

七、总结

本示例详细介绍了zbuff的应用场景,与string/pack库之间的配合关系,以及为什么要用zbuff,以及zbuff的用法。

常见问题

1、先 buff:write(0x12),再设置光标到正确位置后,使用 buff:read(1)读取不到数据。

直接读取是不可见字符,要使用 buff:read(1):toHex()读取数据。