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:
num_blocks.family_id::rp2xxx_absolute block that picotool prepends (see the
is_rp2350_e10_block value instance in the block type).file_size rather
than 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).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.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.
digraph {
rankdir=LR;
node [shape=plaintext];
subgraph cluster__uf2 {
label="Uf2";
graph[style=dotted];
uf2__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="first_block_pos">0</TD><TD PORT="first_block_size">516</TD><TD>Block</TD><TD PORT="first_block_type">first_block</TD></TR>
<TR><TD PORT="blocks_pos">516</TD><TD PORT="blocks_size">516</TD><TD>Block</TD><TD PORT="blocks_type">blocks</TD></TR>
<TR><TD COLSPAN="4" PORT="blocks__repeat">repeat first_block.num_blocks - 1 times</TD></TR>
</TABLE>>];
subgraph cluster__block {
label="Uf2::Block";
graph[style=dotted];
block__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="magic_pos">0</TD><TD PORT="magic_size">4</TD><TD>55 46 32 0A</TD><TD PORT="magic_type">magic</TD></TR>
<TR><TD PORT="second_magic_pos">4</TD><TD PORT="second_magic_size">4</TD><TD>57 51 5D 9E</TD><TD PORT="second_magic_type">second_magic</TD></TR>
<TR><TD PORT="flags_pos">8</TD><TD PORT="flags_size">4</TD><TD>Flags</TD><TD PORT="flags_type">flags</TD></TR>
<TR><TD PORT="target_address_pos">12</TD><TD PORT="target_address_size">4</TD><TD>u4le</TD><TD PORT="target_address_type">target_address</TD></TR>
<TR><TD COLSPAN="4" PORT="target_address__valid">must satisfy _ % 4 == 0</TD></TR>
<TR><TD PORT="len_payload_pos">16</TD><TD PORT="len_payload_size">4</TD><TD>u4le</TD><TD PORT="len_payload_type">len_payload</TD></TR>
<TR><TD COLSPAN="4" PORT="len_payload__valid">must satisfy _ % 4 == 0</TD></TR>
<TR><TD PORT="block_number_pos">20</TD><TD PORT="block_number_size">4</TD><TD>u4le</TD><TD PORT="block_number_type">block_number</TD></TR>
<TR><TD PORT="num_blocks_raw_pos">24</TD><TD PORT="num_blocks_raw_size">4</TD><TD>u4le</TD><TD PORT="num_blocks_raw_type">num_blocks_raw</TD></TR>
<TR><TD COLSPAN="4" PORT="num_blocks_raw__valid">must be at least block_number + 1</TD></TR>
<TR><TD PORT="file_size_pos">28</TD><TD PORT="file_size_size">4</TD><TD>u4le</TD><TD PORT="file_size_type">file_size</TD></TR>
<TR><TD COLSPAN="4" PORT="file_size__if">if !(flags.has_family_id)</TD></TR>
<TR><TD PORT="family_id_pos">32</TD><TD PORT="family_id_size">4</TD><TD>u4le→FamilyId</TD><TD PORT="family_id_type">family_id</TD></TR>
<TR><TD COLSPAN="4" PORT="family_id__if">if flags.has_family_id</TD></TR>
<TR><TD PORT="data_pos">36</TD><TD PORT="data_size">476</TD><TD>BlockData</TD><TD PORT="data_type">data</TD></TR>
<TR><TD PORT="final_magic_pos">512</TD><TD PORT="final_magic_size">4</TD><TD>30 6F B1 0A</TD><TD PORT="final_magic_type">final_magic</TD></TR>
</TABLE>>];
block__inst__is_rp2350_e10_block [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">id</TD><TD BGCOLOR="#E0FFE0">value</TD></TR>
<TR><TD>is_rp2350_e10_block</TD><TD> (( ((flags.value == 8192) || (flags.value == 40960)) ) && (family_id == :family_id_rp2xxx_absolute) && (num_blocks_raw == 2) && (block_number == 0) && (len_payload == 256) && (data.payload[0].ord == 239) && (data.payload[-1].ord == 239) && ( ((!(flags.has_extension_tags)) || (data.extension_tags[0].len_tag == 0) || ( ((data.extension_tags[0].len_tag == 4) && (data.extension_tags[0].tag_type == :extension_tag_type_rp2_ignore_block)) )) )) </TD></TR>
</TABLE>>];
block__inst__num_blocks [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">id</TD><TD BGCOLOR="#E0FFE0">value</TD></TR>
<TR><TD>num_blocks</TD><TD>(is_rp2350_e10_block ? 1 : num_blocks_raw)</TD></TR>
</TABLE>>];
}
subgraph cluster__block_data {
label="Uf2::BlockData";
graph[style=dotted];
block_data__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="payload_pos">0</TD><TD PORT="payload_size">_parent.len_payload</TD><TD></TD><TD PORT="payload_type">payload</TD></TR>
<TR><TD PORT="file_name_pos">...</TD><TD PORT="file_name_size">...</TD><TD>str(term=0, UTF-8)</TD><TD PORT="file_name_type">file_name</TD></TR>
<TR><TD COLSPAN="4" PORT="file_name__if">if _parent.flags.is_file_container</TD></TR>
<TR><TD PORT="extension_tags_pos">...</TD><TD PORT="extension_tags_size">...</TD><TD>ExtensionTag</TD><TD PORT="extension_tags_type">extension_tags</TD></TR>
<TR><TD COLSPAN="4" PORT="extension_tags__repeat">repeat until _.len_tag == 0</TD></TR>
<TR><TD COLSPAN="4" PORT="extension_tags__if">if _parent.flags.has_extension_tags</TD></TR>
</TABLE>>];
block_data__inst__md5_checksum [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="md5_checksum_pos">_io.size - 24</TD><TD PORT="md5_checksum_size">24</TD><TD>Md5Checksum</TD><TD PORT="md5_checksum_type">md5_checksum</TD></TR>
<TR><TD COLSPAN="4" PORT="md5_checksum__if">if _parent.flags.has_md5_checksum</TD></TR>
</TABLE>>];
}
subgraph cluster__extension_tag {
label="Uf2::ExtensionTag";
graph[style=dotted];
extension_tag__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="len_tag_pos">0</TD><TD PORT="len_tag_size">1</TD><TD>u1</TD><TD PORT="len_tag_type">len_tag</TD></TR>
<TR><TD COLSPAN="4" PORT="len_tag__valid">must satisfy ((_ == 0) || (_ >= min_len_tag)) </TD></TR>
<TR><TD PORT="tag_type_pos">1</TD><TD PORT="tag_type_size">3</TD><TD>b24le→ExtensionTagType</TD><TD PORT="tag_type_type">tag_type</TD></TR>
<TR><TD PORT="value_pos">4</TD><TD PORT="value_size">len_value</TD><TD></TD><TD PORT="value_type">value</TD></TR>
<TR><TD COLSPAN="4" PORT="value__if">if len_tag != 0</TD></TR>
<TR><TD PORT="padding_pos">...</TD><TD PORT="padding_size">-(len_tag) % 4</TD><TD></TD><TD PORT="padding_type">padding</TD></TR>
</TABLE>>];
extension_tag__inst__len_value [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">id</TD><TD BGCOLOR="#E0FFE0">value</TD></TR>
<TR><TD>len_value</TD><TD>(len_tag >= min_len_tag ? len_tag - min_len_tag : 0)</TD></TR>
</TABLE>>];
extension_tag__inst__min_len_tag [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">id</TD><TD BGCOLOR="#E0FFE0">value</TD></TR>
<TR><TD>min_len_tag</TD><TD>1 + 3</TD></TR>
</TABLE>>];
}
subgraph cluster__flags {
label="Uf2::Flags";
graph[style=dotted];
flags__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="value_pos">0</TD><TD PORT="value_size">4</TD><TD>u4le</TD><TD PORT="value_type">value</TD></TR>
<TR><TD COLSPAN="4" PORT="value__valid">must satisfy ((_ & ~61441 == 0) && (!( ((is_file_container) && (has_extension_tags)) ))) </TD></TR>
</TABLE>>];
flags__inst__has_extension_tags [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">id</TD><TD BGCOLOR="#E0FFE0">value</TD></TR>
<TR><TD>has_extension_tags</TD><TD>value & 32768 != 0</TD></TR>
</TABLE>>];
flags__inst__has_family_id [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">id</TD><TD BGCOLOR="#E0FFE0">value</TD></TR>
<TR><TD>has_family_id</TD><TD>value & 8192 != 0</TD></TR>
</TABLE>>];
flags__inst__has_md5_checksum [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">id</TD><TD BGCOLOR="#E0FFE0">value</TD></TR>
<TR><TD>has_md5_checksum</TD><TD>value & 16384 != 0</TD></TR>
</TABLE>>];
flags__inst__is_file_container [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">id</TD><TD BGCOLOR="#E0FFE0">value</TD></TR>
<TR><TD>is_file_container</TD><TD>value & 4096 != 0</TD></TR>
</TABLE>>];
flags__inst__not_main_flash [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">id</TD><TD BGCOLOR="#E0FFE0">value</TD></TR>
<TR><TD>not_main_flash</TD><TD>value & 1 != 0</TD></TR>
</TABLE>>];
}
subgraph cluster__md5_checksum {
label="Uf2::Md5Checksum";
graph[style=dotted];
md5_checksum__seq [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="#E0FFE0">pos</TD><TD BGCOLOR="#E0FFE0">size</TD><TD BGCOLOR="#E0FFE0">type</TD><TD BGCOLOR="#E0FFE0">id</TD></TR>
<TR><TD PORT="start_address_pos">0</TD><TD PORT="start_address_size">4</TD><TD>u4le</TD><TD PORT="start_address_type">start_address</TD></TR>
<TR><TD PORT="len_region_pos">4</TD><TD PORT="len_region_size">4</TD><TD>u4le</TD><TD PORT="len_region_type">len_region</TD></TR>
<TR><TD PORT="md5_pos">8</TD><TD PORT="md5_size">16</TD><TD></TD><TD PORT="md5_type">md5</TD></TR>
</TABLE>>];
}
}
uf2__seq:first_block_type -> block__seq [style=bold];
uf2__seq:blocks_type -> block__seq [style=bold];
block__inst__num_blocks:num_blocks_type -> uf2__seq:blocks__repeat [color="#404040"];
block__seq:flags_type -> flags__seq [style=bold];
block__seq:block_number_type -> block__seq:num_blocks_raw__valid [color="#404040"];
flags__inst__has_family_id:has_family_id_type -> block__seq:file_size__if [color="#404040"];
flags__inst__has_family_id:has_family_id_type -> block__seq:family_id__if [color="#404040"];
block__seq:data_type -> block_data__seq [style=bold];
flags__seq:value_type -> block__inst__is_rp2350_e10_block [color="#404040"];
block__seq:family_id_type -> block__inst__is_rp2350_e10_block [color="#404040"];
block__seq:num_blocks_raw_type -> block__inst__is_rp2350_e10_block [color="#404040"];
block__seq:block_number_type -> block__inst__is_rp2350_e10_block [color="#404040"];
block__seq:len_payload_type -> block__inst__is_rp2350_e10_block [color="#404040"];
block_data__seq:payload_type -> block__inst__is_rp2350_e10_block [color="#404040"];
flags__inst__has_extension_tags:has_extension_tags_type -> block__inst__is_rp2350_e10_block [color="#404040"];
extension_tag__seq:len_tag_type -> block__inst__is_rp2350_e10_block [color="#404040"];
extension_tag__seq:tag_type_type -> block__inst__is_rp2350_e10_block [color="#404040"];
block__inst__is_rp2350_e10_block:is_rp2350_e10_block_type -> block__inst__num_blocks [color="#404040"];
block__seq:num_blocks_raw_type -> block__inst__num_blocks [color="#404040"];
block__seq:len_payload_type -> block_data__seq:payload_size [color="#404040"];
flags__inst__is_file_container:is_file_container_type -> block_data__seq:file_name__if [color="#404040"];
block_data__seq:extension_tags_type -> extension_tag__seq [style=bold];
extension_tag__seq:len_tag_type -> block_data__seq:extension_tags__repeat [color="#404040"];
flags__inst__has_extension_tags:has_extension_tags_type -> block_data__seq:extension_tags__if [color="#404040"];
block_data__inst__md5_checksum:md5_checksum_type -> md5_checksum__seq [style=bold];
flags__inst__has_md5_checksum:has_md5_checksum_type -> block_data__inst__md5_checksum:md5_checksum__if [color="#404040"];
extension_tag__inst__min_len_tag:min_len_tag_type -> extension_tag__seq:len_tag__valid [color="#404040"];
extension_tag__inst__len_value:len_value_type -> extension_tag__seq:value_size [color="#404040"];
extension_tag__seq:len_tag_type -> extension_tag__seq:value__if [color="#404040"];
extension_tag__seq:len_tag_type -> extension_tag__seq:padding_size [color="#404040"];
extension_tag__seq:len_tag_type -> extension_tag__inst__len_value [color="#404040"];
extension_tag__inst__min_len_tag:min_len_tag_type -> extension_tag__inst__len_value [color="#404040"];
extension_tag__seq:len_tag_size -> extension_tag__inst__min_len_tag [color="#404040"];
extension_tag__seq:tag_type_size -> extension_tag__inst__min_len_tag [color="#404040"];
flags__inst__is_file_container:is_file_container_type -> flags__seq:value__valid [color="#404040"];
flags__inst__has_extension_tags:has_extension_tags_type -> flags__seq:value__valid [color="#404040"];
flags__seq:value_type -> flags__inst__has_extension_tags [color="#404040"];
flags__seq:value_type -> flags__inst__has_family_id [color="#404040"];
flags__seq:value_type -> flags__inst__has_md5_checksum [color="#404040"];
flags__seq:value_type -> flags__inst__is_file_container [color="#404040"];
flags__seq:value_type -> flags__inst__not_main_flash [color="#404040"];
}