28 个版本

0.3.18 2024 年 8 月 10 日
0.3.17 2024 年 7 月 7 日
0.3.14 2024 年 4 月 4 日
0.3.13 2024 年 3 月 29 日
0.1.0 2021 年 12 月 9 日

#100 in 嵌入式开发

Download history 5/week @ 2024-05-13 3/week @ 2024-05-20 10/week @ 2024-05-27 624/week @ 2024-06-10 459/week @ 2024-07-01 81/week @ 2024-07-08 14/week @ 2024-07-15 39/week @ 2024-07-22 39/week @ 2024-07-29 109/week @ 2024-08-05 63/week @ 2024-08-12

每月 250 次下载

MIT/Apache

240KB
5.5K SLoC

GitHub top language Minimum Supported Rust Version crates.io crates.io Released API docs Crates.io dependency status Continuous integration

svdtools

svdtools 是一组用于修改供应商提供的、经常出现错误的 SVD 文件的工具。它可以作为库导入到其他应用程序中,也可以通过包含的 svdtools 命令行工具直接运行。

一个常见的用例是修复供应商提供的 SVD 文件,然后将 svd2rust 应用到修复后的 SVD。

该项目由 工具团队 开发和维护。

使用 Python 版本入门

需要 Python 3.6 或更高版本才能安装和使用 svdtools。要安装

$ pip3 install --upgrade --user svdtools

安装完成后,可以从命令行调用 svd 工具。

以下是一个示例,使用 make example 调用 svd patch example/incomplete-stm32l4x2.yaml 并生成一个修复后的 SVD 文件 example/stm32l4x2.svd.patched

有关创建补丁的更多信息,请参阅 设备和外设 YAML 格式

使用 Rust 版本入门

本软件包保证在稳定的 Rust 1.58.0 及更高版本上编译。要安装

$ cargo install svdtools

安装完成后,可以从命令行调用 svdtools 工具。命令行界面与 Python 版本的 CLI 相同。

开发

各有各的方式,但预期的流程如下

  1. 通过 make setup 设置虚拟环境;这还会安装 svd 命令行工具
  2. 通过运行 source venv/bin/activate (或使用 direnv) 激活虚拟环境。
  3. 迭代执行,根据需要运行 make checkmake fix

设备和外围设备 YAML 格式

补丁规范使用 YAML,具有以下通用格式

# Path to the SVD file we're targeting. Relative to this file.
# This must be included only in the device YAML file.
_svd: "../svd/STM32F0x0.svd"

# Include other YAML files. Path relative to this file.
_include:
    - "../peripherals/gpio_v2.yaml"

# Alter top-level information and peripherals for this device
_modify:
    version: 1.1
    description: bla bla
    addressUnitBits: 8
    width: 32
    cpu:
        revision: r1p2
        mpuPresent: true
    # Peripherals can either live directly at this level (but other top-level
    # fields will name match first)
    C_ADC:
        name: ADC_Common
    # Or they can be inside a _peripherals block, to avoid name conflicts.
    _peripherals:
        FSMC:
            description: Flexible static memory controller

            # Multiple address blocks are supported via the addressBlocks list
            # use either addressBlock or addressBlocks, but not both
            addressBlocks:
                -   offset: 0x0
                    size: 0x400
                    usage: "ADC base registers"
                -   offset: 0x1000
                    size: 0x400
                    usage: "ADC extra registers"



# Add whole new peripherals to this device.
# Incredibly this feature is required.
_add:
    ADC_Common:
        description: ADC Common registers
        groupName: ADC
        baseAddress: 0x40012300
        addressBlock:
            offset: 0x0
            size: 0x400
            usage: "All ADC registers"
        # Multiple address blocks are supported via the addressBlocks list
        addressBlocks:
            -   offset: 0x0
                size: 0x400
                usage: "ADC base registers"
            -   offset: 0x1000
                size: 0x400
                usage: "ADC extra registers"
        registers:
            CSR:
                description: ADC Common status register
                addressOffset: 0x0
                access: read-only
                resetValue: 0x00000000
                fields:
                    OVR3:
                        description: Overrun flag of ADC3
                        bitOffset: 21
                        bitWidth: 1
        interrupts:
            ADC1_2:
                description: ADC global interrupt
                value: 18

# A whole new peripheral can also be created as derivedFrom another peripheral.
_add:
    USART3:
        derivedFrom: USART1
        baseAddress: "0x40004800"
        interrupts:
            USART3:
                description: USART3 global interrupt
                value: 39

# A new peripheral can have all its registers copied from another, in case
# it cannot quite be derivedFrom (e.g. some fields need different enumerated
# values) but it's otherwise almost exactly the same.
# The registers are copied but not name or address or interrupts, which are
# preserved if the target already exists.
_copy:
    ADC3:
        from: ADC2

# The new peripheral can also be copied from another svd file for a different
# device. This is useful when a peripheral is missing in a device but the exact
# same peripheral already exist in another device.
# When copying from another file, all fields including interrupts are copied.
_copy:
    TIM1:
        from: ../svd/stm32f302.svd:TIM1

# Replace peripheral registers by a 'deriveFrom'.
# This is used when e.g. UART4 and UART5 are both independently defined,
# but you'd like to make UART5 be defined as derivedFrom UART4 instead.
_derive:
    # The KEY peripheral looses all its elements but 'interrupt', 'name',
    # and 'baseAddress', and it is derivedFrom the VALUE peripheral.
    # Peripherals that were 'deriveFrom="KEY"' are now 'deriveFrom="VALUE"'.
    UART5: UART4

# Reorder the hierarchy of peripherals with 'deriveFrom'.
# This is used when e.g. I2C1 is marked as derivedFrom I2C3,
# but you'd like to swap that so that I2C3 becomes derivedFrom I2C1.
_rebase:
    # The KEY peripheral steals everything but 'interrupt', 'name',
    # and 'baseAddress' elements from the VALUE peripheral.
    # Peripherals that were 'deriveFrom="VALUE"' are now 'deriveFrom="KEY"'.
    # The VALUE peripheral is marked as derivedFrom the updated KEY.
    I2C1: I2C3

# An STM32 peripheral, matches an SVD <peripheral> tag.
# Does not match any tag with derivedFrom attribute set.
"GPIO*":
    # We can include other YAML files inside this peripheral
    _include:
        - "path/to/file.yaml"

    # Alter fields on existing registers inside this peripheral
    _modify:
        # Rename this badly named register. Takes effect before anything else.
        # Don't use wildcard matches if you are changing the name!
        # We could have specified name or description or other tags to update.
        GPIOB_OSPEEDR:
          name: OSPEEDR
        # Equivalently the register could go in a '_registers' block
        _registers:
            GPIOB_OSPEEDR:
                name: OSPEEDR
        # Change the value of an interrupt in this peripheral
        _interrupts:
            EXTI0:
                value: 101


    # Add new registers and interrupts to this peripheral.
    # Entries are registers by default, which can also go inside a '_registers'
    # block, or interrupts go in an '_interrupts' block.
    _add:
        EXAMPLER:
            description: An example register
            addressOffset: 0x04
            access: read-write
            fields:
                EXR1:
                    description: Example field
                    bitOffset: 16
                    bitWidth: 4
        _registers:
            EXAMPLR2:
                description: Another example register
        _interrupts:
            EXAMPLEI:
                description: An example interrupt
                value: 100

    # Anywhere you can '_add' something, you can also '_delete' it.
    # Wildcards are supported. The value here can be a YAML list of registers
    # to delete (supported for backwards compatibility), or a YAML mapping
    # of lists of registers or interrupts.
    _delete:
        GPIO*_EXTRAR:
        _registers:
            - GPIO*_EXAMPLER
        _interrupts:
            - USART1

    # If registers have unnecessary common prefix/postfix,
    # you can clean it in all registers in peripheral by:
    _strip:
        - "PREFIX_*_"
    _strip_end:
        - "_POSTFIX_"

    # You can collect several same registers into one register array
    # that will be represented with svd2rust as array or elements
    # with one type
    # Minimal version:
    _array:
        ARRAY*: {}

    # You can also use the modifiers shown below:
    _array:
        ARRAY*:
            name: NEW_NAME%s
            _modify:
                FIELD: [MINIMUM, MAXIMUM]
                FIELD:
                  description: NEWDESC
        OTHER_ARRAY*: {}

    # If you have registers that make up a group and can be repeated,
    # you can collect them into cluster like this:
    _cluster:
        CLUSTER%s:
            FIRST_REG: {}
            SECOND_REG: {}

    # A register on this peripheral, matches an SVD <register> tag
    MODER:
        # As in the peripheral scope, rename or redescribe a field.
        # Don't use wildcard matches if you are changing the name!
        _modify:
            FIELD:
              description: NEWDESC

              # Change the writeConstraint of a field to enumerateValues
              _write_constraint: "enum"

              # Remove any writeConstraint from this field
              _write_constraint: "none"

              # Change the writeConstraint of a field to a range of values
              _write_constraint: [MINIMUM, MAXIMUM]

        # Add new fields to this register
        _add:
            NEWFIELD:
              description: DESCRIPTION
              bitOffset: 12
              bitWidth: 4
              access: read-write

        # Often fields that should be one contiguous integer are specified
        # as a number of individual bits instead. This merges any matching
        # registers into a single field with the combined bitwidth and lowest
        # bit offset, and the shared description and access.
        _merge:
            - "FIELD*"

        # You can also merge fields with different base name like this:
        _merge:
            FIELD: [FIELD1, FIELD_?]
        # Or like this:
        _merge:
            FIELD:
                - FIELD1
                - FIELD_?
        # Or even like this:
        _merge:
            NEW_FIELD: "FIELD*"

        # A field in this register, matches an SVD <field> tag
        FIELD:
            # You can optionally specify name for `enumeratedValues`
            _name: NAME
            # By giving the field a dictionary we construct an enumerateValues
            VARIANT: [VALUE, DESCRIPTION]
            VARIANT: [VALUE, DESCRIPTION]
            # Use `-1` for "default" variant which will be consider
            # for all other values that are not listed explicitly
            # usually datasheet marks them `0b0xxx`, `0b1x`, etc.
            VARIANT: [-1, DESCRIPTION]

        FIELD:
            # If a field already has enumerateValues, drop them and
            # replace them with entirely new ones.
            _replace_enum:
                VARIANT: [VALUE, DESCRIPTION]
                VARIANT: [VALUE, DESCRIPTION]

        # Another field. A list of two numbers gives a range writeConstraint.
        FIELD: [MINIMUM, MAXIMUM]

        # Another field with separate enumerated values for read and write
        FIELD:
            _read:
                VARIANT: [VALUE, DESCRIPTION]
                VARIANT: [VALUE, DESCRIPTION]
            _write:
                VARIANT: [VALUE, DESCRIPTION]
                VARIANT: [VALUE, DESCRIPTION]
        # Sometimes fields are to big so we need to split them into smaller fields
        EXTI:
          IMR:
            # This would split MR into MRi where i = 0 ... bitlength
            _split: [MR]
            # This would split CHxFM into CHiFM where i = 0 ... bitlength
            # and use the current bit for the description in each field
            _split:
              CHxFM:
                name: CH%sFM
                description: Processor 2 transmit channel %s free interrupt mask

            # If fields have unnecessary common prefix/postfix,
            # you can clean it in all registers in peripheral by:
            _strip:
                - "PREFIX_*_"
            _strip_end:
                - "_POSTFIX_"

# You can list glob-like rules separated by commas to cover more periperals or registers at time.
# If rule is optional (peripheral may be missing in some devices) add `?~` in the header.
# Don't abuse it. First test not optional rule.
"?~TIM[18],TIM20":
  CR2:
    # Fields also support collecting in arrays
    _array:
      OIS?:
        description: Output Idle state (OC%s output)
      # Optional rules are supported here too
      "?~OIS?N":
        description: Output Idle state (OC%sN output)

名称匹配

可以指定外围设备、寄存器和字段名称

  • 直接指定(例如,外围设备/寄存器/字段的完整名称)
  • 使用 ?* 作为单字符和多字符通配符
  • 使用 [ABC] 给出可能匹配字符的列表
  • 使用逗号分隔可能匹配的列表

如果在 YAML 中使用任何特殊字符,则必须引用名称。

枚举值 OnOff 在 YAML 和 Python 中被视为布尔值,Python 将抛出错误:AttributeError: 'bool' 对象没有属性 'startswith',这不会提供太多关于错误发生位置的信息。为了避免这种情况,像其他特殊字符一样用引号包围值。

风格指南

  • 枚举值应使用过去式命名(例如,启用屏蔽等)
  • 描述应以大写字母开头,且不应以句号结尾

许可

svdtools 许可协议为以下之一

任选其一。

贡献

非常欢迎拉取请求!

在提交之前,请应用 blackisort。这可以通过以下方式实现:

  • 运行 make fix
  • 运行 black svdtools/isort -y --recursive svdtools/
  • 安装编辑器/IDE 插件

这可以避免在格式化问题上的争议 :)

除非你明确说明,否则任何有意提交并由你包含在工作中的贡献,如 Apache-2.0 许可协议中定义的,将根据上述协议双重许可,没有任何附加条款或条件。

行为准则

对本软件包的贡献是在 Rust 行为准则 下组织的,该软件包的维护者、工具团队承诺将介入以维护该行为准则。

依赖关系

~15–25MB
~363K SLoC