UF2 (USB Flashing Format): format specification

UF2 is a file format, developed by Microsoft for PXT (also known as Microsoft MakeCode), that is particularly suitable for flashing microcontrollers over MSC (Mass Storage Class; aka removable flash drive).

The list of family IDs is stored in a separate JSON file:

https://github.com/microsoft/uf2/blob/master/utils/uf2families.json

This JSON file is regularly updated. The family_id enum should be kept in sync with it. For simplicity and consistency, it's strongly recommended to auto-generate this enum from uf2families.json directly. This was last done using the following commands:

$ curl -fsSLO https://github.com/microsoft/uf2/raw/90e9741f217f5a40c98ba74d663e408041037578/utils/uf2families.json
$ jq -r '
  .[]
  | "    \(.id | ascii_downcase):\n"
  + "      id: \(.short_name | ascii_downcase | gsub("-"; "_"))\n"
  + "      doc: \(.description | tojson)"
' uf2families.json

Test files, picked to cover as many different shapes as possible:

File extension

uf2

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of UF2 (USB Flashing Format) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Block diagram

Format specification in Kaitai Struct YAML

meta:
  id: uf2
  title: UF2 (USB Flashing Format)
  file-extension: uf2
  xref:
    wikidata: Q105856672
  license: CC0-1.0
  endian: le
doc: |
  UF2 is a file format, developed by Microsoft for PXT (also known as
  Microsoft MakeCode), that is particularly suitable for flashing
  microcontrollers over MSC (Mass Storage Class; aka removable flash drive).

  The list of family IDs is stored in a separate JSON file:

  <https://github.com/microsoft/uf2/blob/master/utils/uf2families.json>

  This JSON file is regularly updated. The `family_id` enum should be kept in
  sync with it. For simplicity and consistency, it's strongly recommended to
  auto-generate this enum from `uf2families.json` directly. This was last done
  using the following commands:

  ```bash
  $ curl -fsSLO https://github.com/microsoft/uf2/raw/90e9741f217f5a40c98ba74d663e408041037578/utils/uf2families.json
  $ jq -r '
    .[]
    | "    \(.id | ascii_downcase):\n"
    + "      id: \(.short_name | ascii_downcase | gsub("-"; "_"))\n"
    + "      doc: \(.description | tojson)"
  ' uf2families.json
  ```

  Test files, picked to cover as many different shapes as possible:

  * <https://micropython.org/download/RPI_PICO/> - a typical case: all blocks
    have the same family ID and `num_blocks`.
  * <https://micropython.org/download/RPI_PICO2/> - these .uf2 files are
    actually two UF2 files concatenated. The first UF2 file is the standalone
    `family_id::rp2xxx_absolute` block that `picotool` prepends (see the
    `is_rp2350_e10_block` value instance in the `block` type).
  * <https://circuitpython.org/downloads> - the builds for SAMD boards (for
    example
    [Feather M0 Express](https://circuitpython.org/board/feather_m0_express/))
    set no flags at all, so the field at offset 28 is read as `file_size` rather
    than `family_id`.
  * <https://github.com/raspberrypi/pico-sdk-prebuilts/releases> -
    [Universal UF2](https://github.com/raspberrypi/pico-examples/blob/c81c855ffdedc825975a40ba357723a71358ddf0/universal/README.md#universal-binary-vs-universal-uf2)
    files, which are again two UF2 files concatenated. What's interesting about
    these is that the last block of the first file (with the family ID
    `family_id::rp2040`) is moved to the end of the second file, which was
    intended for `family_id::rp2xxx_absolute` (see
    <https://github.com/raspberrypi/pico-examples/blob/c81c855ffdedc825975a40ba357723a71358ddf0/universal/CMakeLists.txt#L159-L169>).
  * <https://github.com/microsoft/pxt-microsoft-boot-sequence/releases> - some
    files contain blocks with the `flags.is_file_container` flag set (for
    example
    <https://github.com/microsoft/pxt-microsoft-boot-sequence/releases/download/v0.0.4/arcade-p0.uf2>
    is a pure file container for
    `file_name == "Projects/microsoft-boot-sequence.elf"`). Generated by
    MakeCode, the only known producer of file container UF2s.
  * <https://github.com/umi-eng/uftwo/tree/35bccf75b4f81c43f088696a8c4a9912f1f4104e/uftwo/tests> -
    synthetic test files for features that real firmware doesn't seem to use
    (e.g. MD5 checksums).

doc-ref: https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md
seq:
  - id: first_block
    type: block
  - id: blocks
    type: block
    repeat: expr
    repeat-expr: first_block.num_blocks - 1
types:
  block:
    -orig-id: UF2_Block
    -webide-representation: 'Block {block_number:dec} - {flags:flags} {family_id}'
    seq:
      - id: magic
        -orig-id: magicStart0
        contents: "UF2\n"
      - id: second_magic
        -orig-id: magicStart1
        contents: [0x57, 0x51, 0x5d, 0x9e]
      - id: flags
        type: flags
      - id: target_address
        -orig-id: targetAddr
        type: u4
        valid:
          expr: _ % 4 == 0
        doc: |
          Address in flash where `data.payload` should be written, or an offset
          in the file specified by `data.file_name` if `flags.is_file_container`
          is set.

          The [official
          spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#payload-sizes)
          says:

          > In any event, payload size and target address should always be
          > 4-byte aligned.
      - id: len_payload
        -orig-id: payloadSize
        type: u4
        valid:
          expr: _ % 4 == 0
        doc: |
          The [official
          spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#payload-sizes)
          says:

          > In any event, payload size and target address should always be
          > 4-byte aligned.
      - id: block_number
        -orig-id: blockNo
        type: u4
      - id: num_blocks_raw
        -orig-id: numBlocks
        type: u4
        valid:
          min: block_number + 1
        doc: |
          Number of blocks that make up the UF2 file to which this block
          belongs. Every block of a file has the same value. It is at least 1
          and every `block_number` in the file must be less than this value
          (which is validated by this Kaitai Struct implementation).
      - id: file_size
        -orig-id: fileSize
        type: u4
        if: not flags.has_family_id
        doc: |
          Size of the file this block belongs to, but only if
          `flags.is_file_container` is true. Otherwise, the official spec allows
          this field to be set to anything - though in practice, it's always
          zero.
      - id: family_id
        -orig-id: familyID
        type: u4
        enum: family_id
        if: flags.has_family_id
      - id: data
        size: 476
        type: block_data
      - id: final_magic
        -orig-id: magicEnd
        contents: [0x30, 0x6f, 0xb1, 0x0a]
    instances:
      num_blocks:
        value: 'is_rp2350_e10_block ? 1 : num_blocks_raw'
      is_rp2350_e10_block:
        value: |
          (flags.value == 0x2000 or flags.value == 0xa000)
          and family_id == family_id::rp2xxx_absolute
          and num_blocks_raw == 2
          and block_number == 0
          and len_payload == 256
          and data.payload.first == 0xef
          and data.payload.last == 0xef
          and (
            not flags.has_extension_tags
            or data.extension_tags[0].len_tag == 0
            or (
              data.extension_tags[0].len_tag == 4
              and data.extension_tags[0].tag_type == extension_tag_type::rp2_ignore_block
            )
          )
        doc: |
          Determines whether this is a block that `picotool` prepends to RP2350
          flash images as a workaround for erratum RP2350-E10 (i.e. a hardware
          bug in the A2 version of the RP2350 boot ROM).

          Such a block is always written on its own, but its `num_blocks_raw` is
          set to 2. If we trusted this value, we would attempt to read one block
          too many (which would most likely fail). Therefore, we must correct it
          to 1 before using it.

          The conditions for detecting this block come from the
          [`check_abs_block()`](https://github.com/raspberrypi/picotool/blob/6f6458d792b93685a11423b244a585eaa99eafcf/elf2uf2/elf2uf2.cpp#L147)
          function in `picotool`. However, there are some differences:

          1. In Kaitai Struct, we cannot easily check whether all 256 payload
             bytes are set to `0xef`, so we only check the first and last bytes.
          2. There are .uf2 files in the wild where `flags.has_extension_tags`
             is true, but there are actually no extension tags (the first and
             only tag has a size of 0, which is just a terminator), so our
             condition allows for this case. You can download an example of such
             a .uf2 file here:
             <https://github.com/neednotapply/DC32-cfw/releases/tag/1.69.13.37>

          It's worth noting that we cannot require the presence of the
          `extension_tag_type::rp2_ignore_block`
          (`UF2_EXTENSION_RP2_IGNORE_BLOCK`) tag because the UF2 files generated
          by `picotool` prior to
          <https://github.com/raspberrypi/picotool/commit/78c9bd121b09399823b67ee7ea89003ca0d3315f>
          don't have it. Therefore, we check whether this tag is present only if
          `flags.has_extension_tags` is set.

          Test .uf2 files with this special block can be downloaded from
          <https://micropython.org/download/RPI_PICO2/>. Note that all the .uf2
          files there are actually two UF2 (sub)files concatenated, and this
          Kaitai Struct implementation parses only one at a time (so in order to
          parse both, you need something like the helper spec `uf2_files.ksy`
          from
          <https://github.com/kaitai-io/kaitai_struct_formats/pull/542#discussion_r3906386820>).
          v1.24.x releases predate the `extension_tag_type::rp2_ignore_block`
          tag, while releases v1.25.0 and later include it.
        doc-ref:
          - https://github.com/raspberrypi/picotool/blob/6f6458d792b93685a11423b244a585eaa99eafcf/elf2uf2/elf2uf2.cpp#L147 Git tag "2.3.0"
          - https://github.com/raspberrypi/picotool/commit/78c9bd121b09399823b67ee7ea89003ca0d3315f
  block_data:
    seq:
      - id: payload
        size: _parent.len_payload
        doc: |
          The bytes to be written to `_parent.target_address`, which is either
          an address in flash, or an offset in the file specified by `file_name`
          if `_parent.flags.is_file_container` is set.
      - id: file_name
        type: strz
        encoding: UTF-8
        if: _parent.flags.is_file_container
      - id: extension_tags
        type: extension_tag
        repeat: until
        repeat-until: _.len_tag == 0
        if: _parent.flags.has_extension_tags
    instances:
      md5_checksum:
        pos: _io.size - 24
        type: md5_checksum
        if: _parent.flags.has_md5_checksum
        doc: |
          Describes a region that doesn't need to be flashed again if the
          checksum matches.

          The [official
          spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#md5-checksum)
          says that "This is currently only used on ESP32", but no real-world
          firmware sample has been found (at least none of the .uf2 files from
          <https://github.com/adafruit/tinyuf2/releases>,
          [MicroPython](https://micropython.org/download/) or
          [CircuitPython](https://circuitpython.org/downloads) contain it). It
          was found only in some synthetic test files, e.g.
          <https://github.com/umi-eng/uftwo/blob/35bccf75b4f81c43f088696a8c4a9912f1f4104e/uftwo/tests/checksum_256.uf2>.
  extension_tag:
    doc-ref: https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#extension-tags
    -webide-representation: '{tag_type}: {len_value:dec} bytes ({len_tag:dec} total)'
    seq:
      - id: len_tag
        type: u1
        valid:
          expr: _ == 0 or _ >= min_len_tag
        doc: |
          Total size of the tag in bytes, including this byte and `tag_type`, so
          at least 4. The exception is the last tag which terminates the list -
          it specifies a total size of 0.
      - id: tag_type
        type: b24le
        enum: extension_tag_type
      - id: value
        size: len_value
        if: len_tag != 0
      - id: padding
        size: -len_tag % 4
        doc: |
          Tags are 4-byte aligned, so a tag whose size is not a multiple
          of 4 is followed by padding.
    instances:
      min_len_tag:
        value: len_tag._sizeof + tag_type._sizeof
      len_value:
        value: 'len_tag >= min_len_tag ? len_tag - min_len_tag : 0'
  md5_checksum:
    doc-ref: https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#md5-checksum
    seq:
      - id: start_address
        type: u4
      - id: len_region
        type: u4
      - id: md5
        size: 16
  flags:
    doc-ref:
      - https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/uf2.h#L43-L47
      - https://github.com/raspberrypi/pico-sdk/blob/98a542c1a62fb549ffb5d66a3e5892b06276b670/src/common/boot_uf2_headers/include/boot/uf2.h#L23-L27 Git tag "2.3.0"
    -webide-representation: '{value}'
    seq:
      - id: value
        type: u4
        valid:
          expr: |
            (_ & ~0x0000_f001) == 0 and
            not (is_file_container and has_extension_tags)
        doc: |
          Only the five bits that we cover in value instances below are defined,
          and no other bit may be set. The [official
          spec](https://github.com/microsoft/uf2/blob/90e9741f217f5a40c98ba74d663e408041037578/README.md#flags)
          says "Currently, there are five flags defined". If any other flags are
          added in the future, this .ksy spec will need to be updated.

          The `is_file_container` and `has_extension_tags` flags disagree about
          what follows the payload in `data` (a file name or a list of extension
          tags), so this Kaitai Struct implementation treats them as mutually
          exclusive and will reject a block that sets both. The official spec
          does not specify how this should be handled, but logically there's a
          conflict, so we've decided to strictly treat it as an error.
    instances:
      not_main_flash:
        -orig-id:
          - UF2_FLAG_NOFLASH # microsoft/uf2
          - UF2_FLAG_NOT_MAIN_FLASH # raspberrypi/pico-sdk
        value: value & 0x0000_0001 != 0
        doc: |
          Indicates that this block should be skipped when writing the device
          flash. It can be used to store data that does not fit on the device,
          typically embedded source code or debug info.
      is_file_container:
        -orig-id:
          - UF2_FLAG_FILECONTAINER # microsoft/uf2
          - UF2_FLAG_FILE_CONTAINER # raspberrypi/pico-sdk
        value: value & 0x0000_1000 != 0
        doc: |
          When set, the UF2 format is used as a container for regular files
          (akin to a TAR file, or ZIP archive without compression).

          `target_address` is the offset in the file where the payload is to be
          written, and `file_size` is the size of that file. The name of the
          destination file is stored in `data.file_name`.
      has_family_id:
        -orig-id:
          - UF2_FLAG_FAMILY_ID # microsoft/uf2
          - UF2_FLAG_FAMILY_ID_PRESENT # raspberrypi/pico-sdk
        value: value & 0x0000_2000 != 0
        doc: |
          The field at offset 28 in the block is `family_id` instead of
          `file_size`.
      has_md5_checksum:
        -orig-id:
          - UF2_FLAG_MD5_CHKSUM # microsoft/uf2
          - UF2_FLAG_MD5_PRESENT # raspberrypi/pico-sdk
        value: value & 0x0000_4000 != 0
        doc: Indicates whether `md5_checksum` is present at the end of `data`.
      has_extension_tags:
        -orig-id:
          - UF2_FLAG_EXTENSION_TAGS # microsoft/uf2
          - UF2_FLAG_EXTENSION_FLAGS_PRESENT # raspberrypi/pico-sdk
        value: value & 0x0000_8000 != 0
        doc: Indicates whether extension tags are present after the payload.
enums:
  extension_tag_type:
    0x000000:
      id: end
      doc: |
        According to the official spec, this type should be used in the last tag
        with `len_tag == 0`, which terminates the list of extension tags.
    0x9fc7bc:
      id: version
      doc: Version of the firmware file, as a UTF-8 semver string
    0x650d9d:
      id: description
      doc: |
        Description of the device for which the firmware file is destined
        (UTF-8)
    0x0be9f7:
      id: page_size
      doc: Page size of the target device, as a 32-bit unsigned number
    0xb46db0:
      id: sha2_checksum
      doc: SHA-2 checksum of firmware (can be of various sizes)
    0xc8a729:
      id: device_type_id
      doc: |
        Device type identifier, a refinement of `family_id` meant to identify a
        kind of device rather than only an MCU. 32-bit or 64-bit number. Can be
        a hash of the `extension_tag_type::description` tag.
    0x9957e3:
      id: rp2_ignore_block
      -orig-id: UF2_EXTENSION_RP2_IGNORE_BLOCK
      doc: |
        The block is to be ignored. This value doesn't come from the official
        spec, but from the Raspberry Pi Pico SDK. It's written with no value, so
        the whole tag is the 4 bytes `04 e3 57 99`. See the
        `is_rp2350_e10_block` value instance in the `block` type.
      doc-ref: https://github.com/raspberrypi/pico-sdk/blob/98a542c1a62fb549ffb5d66a3e5892b06276b670/src/common/boot_uf2_headers/include/boot/uf2.h#L46 Git tag "2.3.0"
  family_id:
    0x16573617:
      id: atmega32
      doc: "Microchip (Atmel) ATmega32"
    0x1851780a:
      id: saml21
      doc: "Microchip (Atmel) SAML21"
    0x1b57745f:
      id: nrf52
      doc: "Nordic NRF52"
    0x1c5f21b0:
      id: esp32
      doc: "ESP32"
    0x1e1f432d:
      id: stm32l1
      doc: "ST STM32L1xx"
    0x202e3a91:
      id: stm32l0
      doc: "ST STM32L0xx"
    0x21460ff0:
      id: stm32wl
      doc: "ST STM32WLxx"
    0x22e0d6fc:
      id: rtl8710b
      doc: "Realtek AmebaZ RTL8710B"
    0x2abc77ec:
      id: lpc55
      doc: "NXP LPC55xx"
    0x300f5633:
      id: stm32g0
      doc: "ST STM32G0xx"
    0x31d228c6:
      id: gd32f350
      doc: "GD32F350"
    0x3379cfe2:
      id: rtl8720d
      doc: "Realtek AmebaD RTL8720D"
    0x04240bdf:
      id: stm32l5
      doc: "ST STM32L5xx"
    0x4c71240a:
      id: stm32g4
      doc: "ST STM32G4xx"
    0x4fb2d5bd:
      id: mimxrt10xx
      doc: "NXP i.MX RT10XX"
    0x51e903a8:
      id: xr809
      doc: "Xradiotech 809"
    0x53b80f00:
      id: stm32f7
      doc: "ST STM32F7xx"
    0x55114460:
      id: samd51
      doc: "Microchip (Atmel) SAMD51"
    0x57755a57:
      id: stm32f4
      doc: "ST STM32F4xx"
    0x5a18069b:
      id: fx2
      doc: "Cypress FX2"
    0x5d1a0a2e:
      id: stm32f2
      doc: "ST STM32F2xx"
    0x5ee21072:
      id: stm32f1
      doc: "ST STM32F103"
    0x621e937a:
      id: nrf52833
      doc: "Nordic NRF52833"
    0x647824b6:
      id: stm32f0
      doc: "ST STM32F0xx"
    0x675a40b0:
      id: bk7231u
      doc: "Beken 7231U/7231T"
    0x68ed2b88:
      id: samd21
      doc: "Microchip (Atmel) SAMD21"
    0x6a82cc42:
      id: bk7251
      doc: "Beken 7251/7252"
    0x6b846188:
      id: stm32f3
      doc: "ST STM32F3xx"
    0x6d0922fa:
      id: stm32f407
      doc: "ST STM32F407"
    0x4e8f1c5d:
      id: stm32h5
      doc: "ST STM32H5xx"
    0x6db66082:
      id: stm32h7
      doc: "ST STM32H7xx"
    0x70d16653:
      id: stm32wb
      doc: "ST STM32WBxx"
    0x7b3ef230:
      id: bk7231n
      doc: "Beken 7231N"
    0x7eab61ed:
      id: esp8266
      doc: "ESP8266"
    0x7f83e793:
      id: kl32l2
      doc: "NXP KL32L2x"
    0x8fb060fe:
      id: stm32f407vg
      doc: "ST STM32F407VG"
    0x9fffd543:
      id: rtl8710a
      doc: "Realtek Ameba1 RTL8710A"
    0xada52840:
      id: nrf52840
      doc: "Nordic NRF52840"
    0x820d9a5f:
      id: nrf52820
      doc: "Nordic NRF52820_xxAA"
    0xbfdd4eee:
      id: esp32s2
      doc: "ESP32-S2"
    0xc47e5767:
      id: esp32s3
      doc: "ESP32-S3"
    0xd42ba06c:
      id: esp32c3
      doc: "ESP32-C3"
    0x2b88d29c:
      id: esp32c2
      doc: "ESP32-C2"
    0x332726f6:
      id: esp32h2
      doc: "ESP32-H2"
    0x540ddf62:
      id: esp32c6
      doc: "ESP32-C6"
    0x3d308e94:
      id: esp32p4
      doc: "ESP32-P4"
    0xf71c0343:
      id: esp32c5
      doc: "ESP32-C5"
    0x77d850c4:
      id: esp32c61
      doc: "ESP32-C61"
    0xb6dd00af:
      id: esp32h21
      doc: "ESP32-H21"
    0x9e0baa8a:
      id: esp32h4
      doc: "ESP32-H4"
    0x3101f7c1:
      id: esp32s31
      doc: "ESP32-S31"
    0xde1270b7:
      id: bl602
      doc: "Boufallo 602"
    0xe08f7564:
      id: rtl8720c
      doc: "Realtek AmebaZ2 RTL8720C"
    0xe48bff56:
      id: rp2040
      doc: "Raspberry Pi RP2040"
    0xe48bff57:
      id: rp2xxx_absolute
      doc: "Raspberry Pi Microcontrollers: Absolute (unpartitioned) download"
    0xe48bff58:
      id: rp2xxx_data
      doc: "Raspberry Pi Microcontrollers: Data partition download"
    0xe48bff59:
      id: rp2350_arm_s
      doc: "Raspberry Pi RP2350, Secure Arm image"
    0xe48bff5a:
      id: rp2350_riscv
      doc: "Raspberry Pi RP2350, RISC-V image"
    0xe48bff5b:
      id: rp2350_arm_ns
      doc: "Raspberry Pi RP2350, Non-secure Arm image"
    0x00ff6919:
      id: stm32l4
      doc: "ST STM32L4xx"
    0x9af03e33:
      id: gd32vf103
      doc: "GigaDevice GD32VF103"
    0x4f6ace52:
      id: csk4
      doc: "LISTENAI CSK300x/400x"
    0x6e7348a8:
      id: csk6
      doc: "LISTENAI CSK60xx"
    0x11de784a:
      id: m0sense
      doc: "M0SENSE BL702"
    0x4b684d71:
      id: maixplay_u4
      doc: "Sipeed MaixPlay-U4(BL618)"
    0x9517422f:
      id: rza1lu
      doc: "Renesas RZ/A1LU (R7S7210xx)"
    0x2dc309c5:
      id: stm32f411xe
      doc: "ST STM32F411xE"
    0x06d1097b:
      id: stm32f411xc
      doc: "ST STM32F411xC"
    0x72721d4e:
      id: nrf52832xxaa
      doc: "Nordic NRF52832xxAA"
    0x6f752678:
      id: nrf52832xxab
      doc: "Nordic NRF52832xxAB"
    0xa0c97b8e:
      id: at32f415
      doc: "ArteryTek AT32F415"
    0x699b62ec:
      id: ch32v
      doc: "WCH CH32V2xx and CH32V3xx"
    0x7be8976d:
      id: ra4m1
      doc: "Renesas RA4M1"
    0x7410520a:
      id: max32690
      doc: "Analog Devices MAX32690"
    0xd63f8632:
      id: max32650
      doc: "Analog Devices MAX32650/1/2"
    0xf0c30d71:
      id: max32666
      doc: "Analog Devices MAX32665/6"
    0x91d3fd18:
      id: max78002
      doc: "Analog Devices MAX78002"
    0x7d7a66ef:
      id: py32f071_uvk5_v3
      doc: "Quansheng UV-K5 V3 amateur radio based on Puya Semiconductor PY32F071"