比特调节器 (BitScrambler) 驱动
====================================

:link_to_translation:`en:[English]`

介绍
----

比特调节器 (BitScrambler) 是一个外设，能够基于用户提供的程序对 DMA 数据流执行多种类型的转换。ESP-IDF 提供了比特调节器程序的汇编器、构建系统和驱动支持。在 {IDF_TARGET_NAME} 中，比特调节器外设具有独立的 TX（发送）和 RX（接收）通道，可分别关联到相同或不同的外设。


功能概述
--------

.. list::

    -  :ref:`bitscrambler-assembly`：介绍比特调节器汇编程序的结构
    -  :ref:`bitscrambler-build`：介绍比特调节器程序如何与 ESP-IDF 构建系统集成
    -  :ref:`bitscrambler-load`：介绍如何分配比特调节器实例以及如何加载程序
    -  :ref:`bitscrambler-loopback`：介绍如何在回环模式下使用比特调节器

.. _bitscrambler-assembly:

比特调节器汇编程序
^^^^^^^^^^^^^^^^^^^^^^^

比特调节器对 DMA 数据流执行的操作由比特调节器程序定义。由于比特调节器程序是一个难以手动编写的二进制 blob，因此 ESP-IDF 提供了汇编器，将易于编写的文本文件转换为比特调节器二进制程序。

比特调节器汇编文件由注释、标签、指令包和元指令组成。对于注释，汇编器会直接忽略。标签用于定义程序中的位置。指令可以跳转到标签指定的位置。指令包是一组子指令，这些指令会被组装成一个 257 位的比特调节器指令。元指令用于定义比特调节器的全局配置，例如尾随字节数、预取模式或查找表 (LUT) 内容。

比特调节器汇编文件不区分大小写，也不依赖缩进。本文档中使用的大小写混合仅便于阅读。整数字段默认使用十进制，但也可使用十六进制（前缀 ``0x``）或二进制（前缀 ``0b``）。

注释
~~~~

注释以 ``#`` 开头，并持续到行末。注释可以出现在允许空格的任何位置，包括指令包内的子指令之间。

标签
~~~~~~

任何由非空白字符组成并以英文冒号结尾的字符串都是一个标签。标签是对汇编文件中下一个指令包的符号引用。标签不能放在指令包内部，而应位于指令包之前。

示例：

.. code:: asm

    loop_back:
        set 0..3 4..7,
        set 4..7 0..3,
        read 8,
        write 8,
        jmp loop_back

该指令包中的 ``jmp`` 指令会跳回自身的起始位置，反复执行整个指令包，形成一个紧密循环。

指令包
~~~~~~~~~~~~~~~~~~

指令包由以逗号分隔的子指令组成。整个指令包会被汇编成一条 257 位的指令，由比特调节器在单个时钟周期内执行完成。指令包中的所有子指令会并行执行，而不受它们在汇编源代码中的顺序影响。指令包在最后一个没有逗号的子指令后结束。

如需了解比特调节器的详细信息，请参阅 **{IDF_TARGET_NAME} 技术参考手册** > **比特调节器** [`PDF <{IDF_TARGET_TRM_CN_URL}#bitscrm>`__]。

总结来说，比特调节器包含一个 32 位的输出寄存器，其每一位可以取自任意一个源的任意一位。支持的源包括：

- 一个 64 位的输入寄存器，从传入的 DMA 数据流中获取数据
- 两个 16 位的计数器
- 一个 30 位寄存器，包含各种比较的输出结果
- 一个固定的高位和低位
- 一个查找表 (LUT) RAM 的输出
- 上一周期中输出寄存器的值

子指令
""""""""""""""

``set [output] [source_bits]``：将一个或多个源位路由到输出位。注意，可以使用 ``..`` 操作符路由多个位，例如 ``set 0..3 O4..O6`` 等效于 ``set 0 O4, set 1 O5, set 2 O6, set 3 O7``。第一个参数是输出位或输出位范围，输出位的编号范围为 0 到 31。第二个参数是一个或一组 `源位 (source_bit)`_。注意，在指令包中，如果某些输出位没有对应的 ``set`` 子指令，会默认将其设置为低逻辑电平。

``write [n]``：在路由所有输出位后，取寄存器的最低有效 ``n`` 位并推送到 DMA 的输出流中。``n`` 可以是 0、8、16 或 32。如果指令包中没有 ``write`` 子指令，则其效果等同于 ``write 0``。

``read [n]``：在路由所有输出位并将数据写入输出寄存器后，从输入 DMA 流中读取 ``n`` 位数据，并将其推入 64 位输入寄存器。``n`` 的取值可以是 0、8、16 或 32。这些新读取的位将从最高有效位开始依次进入 FIFO。例如，执行 ``read 16`` 将输入寄存器中原本的位 63～16 整体下移至位 47～0，而从 DMA 流读取的新 16 位数据会填充输入寄存器的位 63～48。如果一个指令包中没有 ``read`` 指令，其效果等同于 ``read 0``。

操作码 (opcode)
""""""""""""""""""

.. only:: esp32p4

    - ``LOOP(A|B) end_val ctr_add tgt``：如果选定的计数器（A 或 B）小于 end_val，将 ``ctr_add`` 添加到选定的计数器（A 或 B），并跳转到标签 ``tgt``。否则继续执行。
    - ``ADD(A|B)[H|L] val``：将 ``val`` 添加到选定的计数器。如果附加了 ``H`` 或 ``L``，则分别仅更新计数器的高 8 位或低 8 位。
    - ``IF[N] source_bit tgt``：如果源位 `source_bit` 为 1（对于 IF）或 0（对于 IFN），则跳转到标签 ``tgt``。
    - ``LDCTD(A|B)[H|L] val``：将 ``val`` 加载到指定的计数器中。如果附加了 ``H`` 或 ``L``，则分别仅更新计数器的高 8 位或低 8 位。
    - ``LDCTI(A|B)[H|L]``：将输出寄存器的 16-31 位加载到指定的计数器（A 或 B）中。如果附加了 H 或 L，则分别仅更新计数器的高 8 位或低 8 位。
    - ``JMP tgt``：无条件跳转到标签 ``tgt``，等同于 ``IF h tgt``。
    - ``NOP``：无操作，等同于 ``ADDA 0``。

.. only:: esp32c5

    - ``LOOP(A|B) end_val ctr_add tgt``：如果选定的计数器（A 或 B）小于 end_val，将 ``ctr_add`` 添加到选定的计数器（A 或 B），并跳转到标签 ``tgt``。否则继续执行。
    - ``ADD(A|B)[H|L] val``：将 ``val`` 添加到选定的计数器。如果附加了 ``H`` 或 ``L``，则分别仅更新计数器的高 8 位或低 8 位。
    - ``IF[N] source_bit tgt``：如果源位 `source_bit` 为 1（对于 IF）或 0（对于 IFN），则跳转到标签 ``tgt``。
    - ``LDCTD(A|B)[H|L] val``：将 ``val`` 加载到指定的计数器中。如果附加了 ``H`` 或 ``L``，则分别仅更新计数器的高 8 位或低 8 位。
    - ``LDCTI(A|B)[H|L]``：将发送到输出寄存器的 16～31 位加载到指定的计数器（A 或 B）。如果附加了 ``H`` 或 ``L``，则分别仅更新计数器的高 8 位或低 8 位。
    - ``ADDCTI(A|B)[H|L]``：将发送到输出寄存器的 16～31 位加到指定的计数器（A 或 B）上。如果附加了 ``H`` 或 ``L``，则分别仅评估并更新计数器的高 8 位或低 8 位。
    - ``JMP tgt``：无条件跳转到标签 ``tgt``，等同于 ``IF h tgt``。
    - ``NOP`` - 无操作，等同于 ``ADDA 0``。

.. note::

    注意，一个指令包中只能包含一个操作码、一个 ``read`` 指令和一个 ``write`` 指令，但可以包含多个 ``set`` 指令。多个 ``set`` 指令不能对同一输出位进行赋值。

源位 (source_bit)
""""""""""""""""""""

``set`` 和 ``if``/ ``ifn`` 指令包含一个 ``source_bit`` 字段，其取值范围如下：

- ``0`` ～ ``63``：选定的位来源于输入寄存器中对应的位。
- ``O0`` ～ ``O31``：选定的位来源于上一周期中输出寄存器的值。
- ``A0`` ～ ``A15``：选定的位来源于 A 计数器寄存器中对应的位。
- ``B0`` ～ ``B15``：选定的位来源于 B 计数器寄存器中对应的位。
- ``L0`` ～ ``L31``：选定的位来源于 LUT RAM 的输出。根据**{IDF_TARGET_NAME} 技术参考手册** [`PDF <{IDF_TARGET_TRM_CN_URL}>`__]，LUT RAM 的输出是 LUT 中某个项，该项的位置由上一周期路由到输出寄存器的最高有效 N 位决定。其中，N 的取值对应 32、16 和 8 位的 LUT，分别为 9、10、11。
- 将 B 计数器的部分值与上一周期传输至输出寄存器的位进行比较时，整个条件由三部分组成：

  1. 第一部分：指定比较的是 B 计数器的全部位，还是仅高 8 位或低 8 位：

    - ``B``：比较整个 B 计数器
    - ``BH``：比较 B 计数器的高 8 位
    - ``BL``：比较 B 计数器的低 8 位

  2. 第二部分：比较运算符，支持操作 ``<=``、 ``>`` 和 ``=``。
  3. 第三部分：指定输出寄存器中用于比较的位偏移：

    - 16 位比较时，可选 ``O0`` 或 ``O16``
    - 8 位比较时，可选 ``O0``、``O8``、``O16`` 或 ``O24``

- ``H`` 或 ``L``：这些源是固定的高逻辑电平或低逻辑电平。

.. note::

    注意，并非所有源都可以在同一指令中一起使用。例如，无法在同一指令包中同时使用来自两个计数器的某一位和来自输入 FIFO 高 32 位中的某个位。如果指令包尝试这样做，汇编器会生成错误。

示例
""""

如下是比特调节器的一个程序示例：

.. code:: asm

    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 位数据。最后，程序跳转回指令开始处继续执行。注意，这些操作都在一个比特调节器周期内执行，并且由于子指令都属于同一条指令，因此在指令内部可以按任何顺序指定。这个小型比特调节器程序的最终结果是：接收数据，例如 ``01 23 45 67``，并交换每个字节的高低半字节，输出结果为 ``10 32 54 76``。


元指令
~~~~~~~~

元指令用于设置全局的比特调节器配置。元指令可以出现在汇编文件的任何位置（指令包内部除外），并且由于其全局性质，可能会影响之前的汇编代码。目前定义了两条元指令：``cfg`` 用于全局比特调节器设置，``lut`` 定义查找表 (lookup table) RAM 的内容。


全局配置元指令
"""""""""""""""

- ``cfg prefetch true|false``：如果 ``prefetch`` 设置为 ``true``，则比特调节器启动时会从输入 DMA 流中读取 64 位数据到输入寄存器中。如果设置为 ``false``，输入寄存器将被初始化为零。默认为 ``true``。请注意，如果启用了 prefetch 但是输入流无法提供至少 64 位的数据，比特调节器会发生挂起。
- ``cfg eof_on upstream|downstream``：输入流结束后，比特调节器仍会计算一定量的“尾随”填充字节，以便清空其寄存器中可能存储的数据。此设置表示的是尾随字节的来源：如果设置为 ``upstream``，比特调节器从输入流中读取一定数量的填充字节，如果设置为 ``downstream``，比特调节器会等待写入足够的字节。默认为 ``upstream``。
- ``cfg trailing_bytes N``：该设置指示比特调节器在指示输出流结束之前，需要读取或写入（取决于 ``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`` 配置元语句所给定的值的范围内。

.. _bitscrambler-build:

构建系统集成
^^^^^^^^^^^^^^

比特调节器完全支持 ESP-IDF 构建系统。一个组件（包括主组件）可以在其源目录中直接包含比特调节器汇编源文件，这些文件通常具有后缀 ``.bsasm``。具体而言，需在组件的 CMakeLists.txt 文件中调用 ``target_bitscrambler_add_src("assembly_file.bsasm")``，从而将此类文件汇编并链接到主应用程序中。例如，对于名为 ``my_program.bsasm`` 的汇编文件，CMakeLists.txt 文件可能如下所示：

.. code:: cmake

    idf_component_register(SRCS "main.c" "some-file.c"
                    INCLUDE_DIRS "./include")

    target_bitscrambler_add_src("my_program.bsasm")

要使用汇编后的比特调节器程序，可以这样引用：

.. code:: c

    // 创建一个变量 'my_bitscrambler_program'，它解析为
    // 二进制的比特调节器程序。
    // 第二个参数与汇编文件的名称相同，但不包括 ".bsasm"
    BITSCRAMBLER_PROGRAM(my_bitscrambler_program, "my_program");

    [...]

    bitscrambler_handle_t bs;
    [...创建比特调节器实例]
    bitscrambler_enable(bs);
    bitscrambler_load_program(bs, my_bitscrambler_program);

    [...]

    bitscrambler_disable(bs);

.. _bitscrambler-loopback:

回环模式
^^^^^^^^^

比特调节器支持回环模式，适用于不涉及外设的数据转换任务。回环模式下，TX 和 RX 通道都会被占用，但实际上只有 TX 比特调节器执行代码。注意，即使回环模式不涉及外设，仍然需要选择一个外设。此外设无需初始化或使用，但如果使用，将无法使用其 DMA 功能。

.. _bitscrambler-load:

资源分配和程序加载
^^^^^^^^^^^^^^^^^^^^^

在回环模式下，使用 :cpp:func:`bitscrambler_loopback_create` 创建一个比特调节器对象。如果有一个与请求的特性匹配的比特调节器外设，该函数将返回此外设的句柄。然后，使用 :cpp:func:`bitscrambler_load_program` 将比特调节器程序加载到创建的对象中，再调用 :cpp:func:`bitscrambler_loopback_run` 使用此加载的程序进行内存缓冲区的比特转换。可以多次调用 :cpp:func:`bitscrambler_loopback_run`，也可以在调用之间使用 :cpp:func:`bitscrambler_load_program` 更改程序。最后，调用 :cpp:func:`bitscrambler_free` 释放硬件资源并清理内存。

在外设驱动中集成比特调节器
--------------------------

比特调节器可以与其他外设模块（须支持 GDMA 接口）配合使用，以实现数据转换和传输任务。当前，以下外设模块已经完成了与 BitScrambler 的对接：

.. list::

    :SOC_PARLIO_SUPPORTED: - **Parlio TX 驱动**：比特调节器被设计成了传输层的装饰器函数，可以在运行期间动态启用，详情请参考 :ref:`parlio-tx-bitscrambler-decorator`
    :SOC_RMT_SUPPORT_DMA: - **RMT TX 驱动**：比特调节器的作用和 RMT 编码器的功能类似，因此我们利用比特调节器设计了一个专门的编码器，详情请参考 :ref:`rmt-bitscrambler-encoder`

应用示例
--------

* :example:`peripherals/bitscrambler` 演示了如何使用比特调节器回环模式将数据包转换为不同的格式。

API 参考
--------

.. include-build-file:: inc/bitscrambler.inc
.. include-build-file:: inc/bitscrambler_loopback.inc
.. include-build-file:: inc/bitscrambler_peri_select.inc
