
ESP-IDF BitScrambler 驱动与汇编程序设计完全指南【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf导读BitScrambler 是 ESP-IDF 中一种面向 DMA 数据流的外设它通过用户编写的一段程序对流入/流出的数据执行各种位级变换位重排、条件跳转、查表映射等。本文以 ESP-IDF 官方 API 参考文档为核心结合仓库中的驱动源码bitscrambler.h、汇编器实现bsasm.py、构建系统集成project_include.cmake与可运行示例bitscrambler 示例系统讲解 BitScrambler 汇编程序结构、指令与元指令语法、CMake 构建接入、回环Loopback模式编程流程以及它与 Parlio、RMT 等外设驱动的联动方式。读完本文你将能够独立编写.bsasm位变换程序并将其嵌入应用完成真实的数据格式转换。概述BitScrambler 能做什么BitScrambler 是一个对 DMA 数据流应用各种数据变换的外设变换规则由用户提供的程序决定。ESP-IDF 为该外设提供了三件套支持汇编器assembler把易读的文本文件转换为 BitScrambler 二进制程序构建系统集成将.bsasm源文件汇编并链接进固件驱动driver负责分配硬件通道、加载程序、启停与释放。在 {IDF_TARGET_NAME}当前仓库目标芯片见 bsasm_targets支持 esp32p4、esp32c5、esp32s31 等上BitScrambler 具有独立的TX 与 RX 通道两个通道可以挂接到同一个外设也可以分别挂接不同外设。此外该组件在 POSIX/Linux 模拟器上不受支持见 CMakeLists.txt仅针对CONFIG_SOC_BITSCRAMBLER_SUPPORTED的芯片编译。功能总览BitScrambler 的使用流程可以分为四个环节编写汇编程序掌握 BitScrambler 汇编语言的语法标签、指令束、元指令接入构建系统通过target_bitscrambler_add_src()将.bsasm汇编并链接进应用加载与执行分配 BitScrambler 实例用bitscrambler_load_program()载入程序回环模式验证无需真实外设直接在内存缓冲区上验证程序行为。下面按此脉络展开。BitScrambler 汇编语言BitScrambler 程序本质是一段难以手写的二进制 blob因此 ESP-IDF 提供了汇编器将其从文本转换为二进制。一个.bsasm汇编文件由注释comments、标签labels、指令束instruction bundles、元指令meta-instructions四类元素组成注释会被汇编器忽略标签定义一个程序位置指令可以跳转到该位置指令束是一组子指令的集合整体被汇编为一条257 位的二进制 BitScrambler 指令元指令定义全局 BitScrambler 配置如尾部字节数量、预取模式、LUT RAM 内容等。汇编文件大小写不敏感也不敏感于缩进。文档中的大小写仅为可读性汇编器本身不区分。整数字段默认为十进制也可以用0x前缀写十六进制、用0b前缀写二进制。注释Comments注释以#开头并延伸到行尾。它可以出现在任何允许空格的位置甚至包括指令束内两个子指令之间。标签Labels任何一串非空白字符后跟冒号即构成一个标签。标签是对汇编文件中下一个指令束的符号引用。注意标签不能属于指令束的一部分它必须位于指令束开始之前。loop_back: set 0..3 4..7, set 4..7 0..3, read 8, write 8, jmp loop_back上例中指令束里的jmp跳回自身起点意味着该指令束会以紧密循环的方式反复执行。指令束Instruction Bundle指令束由逗号分隔的子指令组成整个束被汇编为一条 257 位指令BitScrambler 在单个时钟周期内执行它。也就是说束内所有子指令都是并行执行的与它们在汇编源码中的书写顺序无关。指令束以最后一个不带逗号的子指令作为结束标志。具体的硬件细节以{IDF_TARGET_NAME} Technical Reference Manual中BitScrambler (BITSCRM)章节为准。概括而言BitScrambler 包含一个32 位输出寄存器其中每一位都可以取自下列任一输入源由输入 DMA 流馈入的64 位输入寄存器两个16 位计数器A 与 B一个30 位寄存器保存各种比较操作的结果固定的高电平位与低电平位查找表LUTRAM的输出上一周期输出寄存器的值。子指令set [output] [source_bits]将一个或多个源位路由到输出位。可以使用..操作符一次路由多个位例如set 0..3 O4..O7等价于set 0 O4, set 1 O5, set 2 O6, set 3 O7。第一个参数是输出位或输出位范围输出位编号为 031第二个参数是一个或一个范围的 源位source bits。注意指令束中没有被set子指令赋值的输出位会被置为低电平。write [n]在所有输出位路由完成后取输出寄存器最低的 n 个位推入输出 DMA 管道。n可以是 0、8、16 或 32。若指令束中没有write子指令等价于write 0。read [n]在所有输出位路由并写入输出寄存器之后从输入 DMA 管道取n个位推入 64 位输入寄存器。n同样只能是 0、8、16 或 32。这些位会从 MSB 开始移入输入 FIFO例如read 16会把输入寄存器 63–16 位下移到 47–0 位而新读入的 16 位占据 63–48 位。若指令束中没有read子指令等价于read 0。关于读写与跳转在同一周期内的时序示例程序 中的注释提供了一个关键细节read在指令执行结束时才加载新数据因此可以在上一条指令中提前发起读取实现 64 位数据的分两批read 32read 32流水式处理。操作码Opcode指令束中可用的操作码因芯片而异。以下分别列出文档中针对不同芯片的定义。esp32p4LOOP(A|B) end_val ctr_add tgt若所选计数器A 或 B小于end_val则将ctr_add加到所选计数器并跳转到标签tgt否则继续顺序执行。ADD(A|B)[H|L] val将val加到所选计数器。若追加H或L则只写回计数器的高 8 位或低 8 位。IF[N] source_bit tgt若源位source_bit为 1IF或 0IFN则跳转到标签tgt。LDCTD(A|B)[H|L] val将val载入指定计数器。若追加H或L只更新高 8 位或低 8 位。LDCTI(A|B)[H|L]用输出寄存器 16–31 位载入指定计数器。若追加H或L只更新高 8 位或低 8 位。JMP tgt无条件跳转到标签tgt等价于IF h tgt。NOP空操作等价于ADDA 0。esp32c5esp32s31 同族LOOP(A|B) end_val ctr_add tgt语义同 esp32p4。ADD(A|B)[H|L] val语义同 esp32p4。IF[N] source_bit tgt语义同 esp32p4。LDCTD(A|B)[H|L] val语义同 esp32p4。LDCTI(A|B)[H|L]用发送到输出寄存器的 16–31 位载入指定计数器。若追加H或L只更新高 8 位或低 8 位。ADDCTI(A|B)[H|L]将发送到输出寄存器的 16–31 位加到指定计数器。若追加H或L只评估并更新高 8 位或低 8 位。JMP tgt无条件跳转等价于IF h tgt。NOP空操作等价于ADDA 0。限制一个指令束中只能包含一个操作码、一个read、一个writeset可以有多个但多个set不能给同一个输出位赋值。源位Source Bitsset与if/ifn指令中的source_bit字段可以取以下值0–63取自输入寄存器中对应位O0–O31取自上一周期输出寄存器被赋的值A0–A15取自 A 计数器寄存器对应位B0–B15取自 B 计数器寄存器对应位L0–L31取自 LUT RAM 输出。按 TRM 的描述LUT RAM 输出是上一周期路由到输出寄存器的位中最高 N 位所指示位置的 LUT 条目当 LUT 宽度为 32、16 或 8 位时N 分别为 9、10 或 11条件比较将部分B 计数器与上一周期路由到输出寄存器的位进行比较由三部分组成第一部分指定比较 B 的全部位还是仅高/低 8 位B整个 B 寄存器、BHB 的高 8 位、BLB 的低 8 位第二部分是比较运算符支持、、第三部分指定输出寄存器中与 B 的所选部分进行比较的偏移16 位比较的偏移为O0或O168 位比较的偏移为O0、O8、O16或O24。H或L固定高电平或固定低电平源。约束并非所有源都能在同一指令中使用。例如不能在同一个指令束中同时使用某个计数器的位和输入 FIFO 高 32 位的位。汇编器遇到此类组合会报错。完整示例高低半字节交换loop_back: set 0..3 4..7, set 4..7 0..3, read 8, write 8, jmp loop_back该程序只有一个指令束只有带jmp的行不以逗号结尾。它把从内存读入的数据的低 4 位送到输出寄存器第一个字节的高 4 位把输入寄存器的次 4 位送到输出寄存器的低 4 位随后向输出写入 8 位一个字节同时从输入读取 8 位最后跳回指令起始处继续。所有这些都在一个 BitScrambler 周期内完成子指令顺序可以任意排列。最终效果是输入01 23 45 67输出10 32 54 76——即每个字节的高、低半字节互换。元指令Meta-instructions元指令用于设置 BitScrambler 的全局配置。元指令可以出现在汇编文件的任意位置指令束内部除外由于其全局属性也可能影响前面已汇编的代码。目前定义了两类元指令cfg设置全局配置与lut定义 LUT RAM 内容。全局配置元指令cfg prefetch true|false若为trueBitScrambler 启动时从输入 DMA 流读取 64 位到输入寄存器若为false输入寄存器初始化为零。默认值为true。注意若开启了 prefetch 但输入流无法提供至少 64 位数据BitScrambler 会挂起hang。cfg eof_on upstream|downstream输入流结束后BitScrambler 仍会处理一定数量的尾部trailing伪字节以冲刷寄存器中的数据。此设置指示数据从哪里计数upstream表示按读入的字节计数downstream表示按写出的字节计数。默认值为upstream。cfg trailing_bytes N指示在 BitScrambler 于输出端发出流结束信号之前将读取/写入取决于eof_on设置多少个伪字节。默认值为0。cfg lut_width_bits 8|16|32选择 LUT 输出 RAM 的总线宽度位。LUT 容量可以是 2048×8 位、1024×16 位或 512×32 位。默认值为32。LUT 内容元指令lut指令用于指定 LUT RAM 的内容后跟一个或多个数值用空格或逗号分隔。LUT RAM 位置按其在汇编程序中出现的顺序依次填充第一个值总是存到位置 0第二个值存到位置 1依此类推。单条lut的参数个数是任意的因为 LUT 元指令可以随意拆分或合并例如lut 1,2,3,4与两行lut 1,2、lut 3,4完全等价。注意LUT 值必须在cfg lut_width_bits设定的位宽范围内。下面是一个同时使用cfg与lut的完整示例改编自测试程序 lut32.bsasmcfg prefetch false # 禁用数据预取 cfg eof_on downstream # 按输出侧计数 EOF cfg trailing_bytes 4 cfg lut_width_bits 32 # 定义查找表内容 lut 0xF0011111 # index 0 lut 0xF0022222 # index 1 lut 0xF0033333 # index 2 lut 0xF0004444 # index 3 set 16..17 L # 设置 LUT 索引0 (0b00) loop: read 8, set 31..0 L31..L0, write 32, jmp loop注意set 31..0 L31..L0使用了反向范围写法将 LUT 输出整体路由到输出寄存器set 16..17 L则把固定高电平路由到输出位 16–17以 LUT 位宽 32 为例其对应索引为最高 9 位N9。构建系统集成BitScrambler 拥有完整的 ESP-IDF 构建系统支持。任何组件包括 main 组件都可以在其源目录中放置 BitScrambler 汇编源文件这些文件通常以.bsasm为后缀。要将某个.bsasm文件汇编并链接进应用在组件的 CMakeLists.txt 中调用idf_component_register(SRCS main.c some-file.c INCLUDE_DIRS ./include) target_bitscrambler_add_src(my_program.bsasm)以示例工程为例idf_component_register(SRCS bitscrambler_example_main.c PRIV_REQUIRES esp_driver_bitscrambler INCLUDE_DIRS .) target_bitscrambler_add_src(example.bsasm)从实现上看project_include.cmaketarget_bitscrambler_add_src()会为每个.bsasm源文件添加一条自定义命令调用tools/bsasm.py并传入对应芯片目标的 JSON 描述文件bsasm_targets/{target}.json例如 esp32p4.json生成.bsbin二进制随后通过target_add_binary_data()以bitscrambler_program_{basename}的名字把该二进制嵌入应用。在 C 代码中引用程序使用汇编好的 BitScrambler 程序时用BITSCRAMBLER_PROGRAM宏声明一个符号定义见 bitscrambler.h// 声明变量 my_bitscrambler_program解析到二进制 bitscrambler 程序。 // 第 2 个参数与汇编文件名去掉 .bsasm保持一致 BITSCRAMBLER_PROGRAM(my_bitscrambler_program, my_program); [...] bitscrambler_handle_t bs; [...create bitscrambler instance] bitscrambler_enable(bs); bitscrambler_load_program(bs, my_bitscrambler_program); [...] bitscrambler_disable(bs);资源分配与程序加载单个方向的 BitScrambler 通道通过bitscrambler_new()创建bitscrambler.h配置结构bitscrambler_config_t包含两个字段dir方向tx或rxattach_to要挂接的外设取值为SOC_BITSCRAMBLER_ATTACH_*系列常量之一。bitscrambler_new()的典型返回值为ESP_OK、ESP_ERR_NO_MEM内存不足或ESP_ERR_NOT_FOUND无空闲硬件通道。创建之后驱动 API 的使用顺序有严格要求bitscrambler_enable(bs)——必须先于bitscrambler_load_program、bitscrambler_load_lut、bitscrambler_reset与bitscrambler_start调用bitscrambler_load_program(bs, program)——将二进制程序载入 BitScrambler 内存非法的二进制或无效句柄返回ESP_ERR_INVALID_ARGbitscrambler_load_lut(bs, lut, size_bytes)——按字节数载入查找表数据可选bitscrambler_reset(bs)——复位程序与 FIFO 以开始新事务注意不影响已加载的程序本身bitscrambler_start(bs)——开始执行程序bitscrambler_disable(bs)——必须先于bitscrambler_free调用bitscrambler_free(bs)——释放句柄。回环Loopback模式BitScrambler 支持回环模式非常适合那些不涉及真实外设的数据变换场景。回环模式会同时占用 BitScrambler 的 TX 与 RX 通道但只有 TX BitScrambler 实际执行代码。需要特别注意的是即使回环模式不涉及外设也必须选择一个外设进行挂接该外设不需要被初始化或使用但一旦被使用它的 DMA 功能将不可用。回环模式的完整编程流程对应 bitscrambler_loopback.hbitscrambler_handle_t bs; // 1. 创建回环实例attach_to 仍须指定一个外设如 SOC_BITSCRAMBLER_ATTACH_GPSPI2 ESP_ERROR_CHECK(bitscrambler_loopback_create(bs, SOC_BITSCRAMBLER_ATTACH_GPSPI2, result_buf_size)); // 2. 加载程序可在多次 run 之间随时更换 ESP_ERROR_CHECK(bitscrambler_load_program(bs, bitscrambler_program_example)); // 3. 对内存缓冲区执行变换 size_t result_len; ESP_ERROR_CHECK(bitscrambler_loopback_run(bs, (void*)testdata, input_data_len, result_buf, result_buf_size, result_len)); // 4. 释放硬件资源并清理内存 bitscrambler_free(bs);关键 API 说明bitscrambler_loopback_create(handle, attach_to, max_transfer_sz_bytes)创建回环句柄。max_transfer_sz_bytes是传入bitscrambler_loopback_run的输入/输出数据的最大传输字节数。可能的返回值包括ESP_OK、ESP_ERR_NO_MEM、ESP_ERR_NOT_FOUND、ESP_ERR_INVALID_ARG与ESP_FAIL。bitscrambler_loopback_run(bs, buffer_in, length_bytes_in, buffer_out, length_bytes_out, bytes_written)在数据缓冲区上运行程序。若某缓冲区大小超过max_transfer_sz_bytes返回ESP_ERR_INVALID_SIZE若程序未在超时时间内完成返回ESP_ERR_TIMEOUT。bytes_written输出实际写入的字节数可传NULL。bitscrambler_loopback_run可以调用任意次数也允许在两次调用之间用bitscrambler_load_program更换程序。驱动层实现要点见 bitscrambler_loopback.c回环模式在内部通过 DMA 完成输入缓冲与输出缓冲之间的搬运因此输出缓冲区通常应使用heap_caps_calloc(..., MALLOC_CAP_DMA)分配示例代码 bitscrambler_example_main.c 即是如此而输入数据由于 DMA 可以读取 Flash 区域甚至可以直接使用const数组该示例在注释中明确说明了这一点。与外围驱动集成BitScrambler 可以与其它支持GDMA 接口的外设模块协同执行数据变换与传输任务。目前仓库中已集成以下两个外设模块Parlio TX 驱动BitScrambler 充当传输层的装饰器decorator函数可在运行时动态启用详见 Parlio 文档中的 parlio TX bitscrambler decorator 章节RMT TX 驱动BitScrambler 的功能类似 RMT 编码器并已基于 BitScrambler 设计了专用编码器详见 RMT 文档中的 RMT bitscrambler encoder 章节。这种装饰器/编码器式集成意味着位变换逻辑与具体的收发时序解耦开发者可以把 BitScrambler 程序当作一个可插拔的数据整形单元挂到既有外设驱动上。应用示例与测试验证官方示例位于 examples/peripherals/bitscrambler演示了如何用回环模式将一段数据缓冲变换为另一种格式。其核心程序 example.bsasm 实现的是位旋转bit rotation每 8 个输入字节输出 8 个字节输出字节 0 由输入字节 07 的第 0 位拼成输出字节 1 由输入字节 07 的第 1 位拼成依此类推。该程序对理解指令时序很有价值cfg eof_on downstream cfg trailing_bytes 8 # 输入出现 EOF 时仍需处理 M0/M1 中的 64 位 cfg prefetch true # 期望 M0/M1 已被填满 loop: # 起始时 M0 和 M1 已有 64 位可以立即开始输出 # 输出字节 0set 0 0, set 1 8, set 2 16, ...共 32 个 set ... write 32 # 每次只能写 32 位先写低 32 位 # 第 2 条指令路由另外 32 位并写出 ... write 32, read 32 # 指令结束时加载新数据可提前读取下一批 64 位的前半 read 32, # 读取后半 64 位然后回到循环起点 jmp loop对应的 C 主程序 bitscrambler_example_main.c 以 5 组测试数据全 1、单字节0xFF、单位矩阵型、0xAA、0x55验证旋转结果并注释了期望输出例如单位矩阵数据01 02 04 08 10 20 40 80旋转后应保持不变AA序列应输出00 ff 00 ff ...。此外驱动自带的测试应用 test_apps/bitscrambler 覆盖了更多场景trivial.bsasm纯转发程序set 0..31 0..31write 32read 32用于验证无变换直通lut32.bsasmLUT 查表变换验证lut_width_bits 32与lut元指令还有 loop.bsasm、eof_upstream.bsasm、eof_downstream.bsasm、timeout.bsasm 等分别验证循环、上游/下游 EOF 计数与超时行为配合 pytest_bitscrambler.py 可在硬件上自动化运行。API 参考完整的驱动 API 以 Doxygen 头文件形式提供driver/bitscrambler.hbitscrambler_new、bitscrambler_free、bitscrambler_load_program、bitscrambler_load_lut、bitscrambler_start、bitscrambler_reset、bitscrambler_enable、bitscrambler_disable以及BITSCRAMBLER_PROGRAM宏driver/bitscrambler_loopback.hbitscrambler_loopback_create与bitscrambler_loopback_runesp_private/bitscrambler.h外设选择等私有接口。从源码结构可以推断该组件通过REQUIRES esp_hal_dma与PRIV_REQUIRES esp_mm esp_driver_dmaCMakeLists.txt依赖 DMA 抽象层并在 linker.lf 中定义了链接片段确保回环模式所需的 DMA 能力随组件一起被链接进固件。总结BitScrambler 为 ESP-IDF 提供了一套用程序定义数据变换的完整工具链.bsasm汇编语言让位级变换逻辑可读、可维护target_bitscrambler_add_src()让程序可以随组件一键嵌入固件bitscrambler_loopback_*系列 API 让开发者可以在不接外设的情况下快速验证程序行为而与 Parlio、RMT 等 GDMA 外设的集成则让它能直接服务于真实的收发场景。结合驱动自带的测试程序直通、LUT、EOF、超时等开发者可以在编写自己的位变换程序时快速对照验证。【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考