Compare commits
62 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89839723d8 | ||
|
|
84da66ce62 | ||
|
|
b23250077a | ||
|
|
2a8c0e4984 | ||
|
|
5ec3ddbd28 | ||
|
|
df9920a8e9 | ||
|
|
a307533182 | ||
|
|
9409e62d43 | ||
|
|
5a0f284ce3 | ||
|
|
a4c3164f5c | ||
|
|
b0fdc475c6 | ||
|
|
3e6f8d6d40 | ||
|
|
84490705bc | ||
|
|
c5488ec20b | ||
|
|
ad50c9a828 | ||
|
|
e038180158 | ||
|
|
0a6e597ce6 | ||
|
|
31117082f0 | ||
|
|
6e8bab5245 | ||
|
|
1961540772 | ||
|
|
9acf6a0fbd | ||
|
|
956b6e1386 | ||
|
|
7b2590b9d8 | ||
|
|
e24284da0a | ||
|
|
ad8bd69fd7 | ||
|
|
2b398d08f3 | ||
|
|
0d0317634c | ||
|
|
b31f82edf6 | ||
|
|
37e28ddf8e | ||
|
|
4b3a349d90 | ||
|
|
9de0d3a5ca | ||
|
|
7e6306cf08 | ||
|
|
4a97cb50d2 | ||
|
|
62967d8fe8 | ||
|
|
d76c4f93a7 | ||
|
|
839623d9ec | ||
|
|
e77baf9e62 | ||
|
|
489638255a | ||
|
|
86e101e3a2 | ||
|
|
197180bb25 | ||
|
|
f2217cd8ff | ||
|
|
881bc93ca6 | ||
|
|
cc0e31aa20 | ||
|
|
206825a0ef | ||
|
|
6b57b35d16 | ||
|
|
f294980449 | ||
|
|
79e82bed56 | ||
|
|
b0562d349f | ||
|
|
ffaf0779d5 | ||
|
|
4a32ffc6a5 | ||
|
|
f78c13c3be | ||
|
|
f7bdf490af | ||
|
|
86cb62be3a | ||
|
|
73afad0a4b | ||
|
|
0985f37d40 | ||
|
|
80e72b9640 | ||
|
|
6a68ca700b | ||
|
|
961e06ac5c | ||
|
|
88a34269ea | ||
|
|
968716efd3 | ||
|
|
59876b233c | ||
|
|
68090735cd |
8 changed files with 1477 additions and 85 deletions
66
README.md
66
README.md
|
|
@ -5,8 +5,7 @@ UF2 is a file format, developed by Microsoft for [PXT](https://github.com/Micros
|
||||||
flashing microcontrollers over MSC (Mass Storage Class; aka removable flash drive).
|
flashing microcontrollers over MSC (Mass Storage Class; aka removable flash drive).
|
||||||
|
|
||||||
For a more friendly explanation, check out [this blog post](https://makecode.com/blog/one-chip-to-flash-them-all).
|
For a more friendly explanation, check out [this blog post](https://makecode.com/blog/one-chip-to-flash-them-all).
|
||||||
|
Also, take a look at the list of implementations at the bottom of this document.
|
||||||
For source code, see the implementation of [UF2 in a SAMD21 bootloader](https://github.com/Microsoft/uf2-samd21)
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|
@ -67,7 +66,7 @@ struct UF2_Block {
|
||||||
|
|
||||||
### Flags
|
### Flags
|
||||||
|
|
||||||
Currently, there are two flags is defined:
|
Currently, there are three flags defined:
|
||||||
|
|
||||||
* `0x00000001` - **not main flash** - this block should be skipped when writing the
|
* `0x00000001` - **not main flash** - this block should be skipped when writing the
|
||||||
device flash; it can be used to store "comments" in the file, typically
|
device flash; it can be used to store "comments" in the file, typically
|
||||||
|
|
@ -75,6 +74,7 @@ Currently, there are two flags is defined:
|
||||||
* `0x00001000` - **file container** - see below
|
* `0x00001000` - **file container** - see below
|
||||||
* `0x00002000` - **familyID present** - when set, the `fileSize/familyID` holds a value
|
* `0x00002000` - **familyID present** - when set, the `fileSize/familyID` holds a value
|
||||||
identifying the board family (usually corresponds to an MCU)
|
identifying the board family (usually corresponds to an MCU)
|
||||||
|
* `0x00004000` - **MD5 checksum present** - see below
|
||||||
|
|
||||||
### Family ID
|
### Family ID
|
||||||
|
|
||||||
|
|
@ -85,21 +85,37 @@ If you're developing your own bootloader, and your
|
||||||
board family isn't listed here, pick a new family ID at random. It's good
|
board family isn't listed here, pick a new family ID at random. It's good
|
||||||
to also send a PR here, so your family can be listed.
|
to also send a PR here, so your family can be listed.
|
||||||
|
|
||||||
|
If the `familyID` doesn't match, the bootloader should disregard the
|
||||||
|
entire block, including `blockNo` and `numBlocks` fields.
|
||||||
|
In particular, writing a full UF2 file with non-matching `familyID`
|
||||||
|
should not reset the board.
|
||||||
|
This also allows for several files with different `familyID` to be
|
||||||
|
simply concatenated together, and the whole resulting file to be copied
|
||||||
|
to the device with only one actually being written to flash.
|
||||||
|
|
||||||
#### Picking numbers at random
|
#### Picking numbers at random
|
||||||
|
|
||||||
The reason to pick numbers at random is to minimize risk of collisions
|
The reason to pick numbers at random is to minimize risk of collisions
|
||||||
in the wild. Do not pick random numbers by banging on keyboard, or by using
|
in the wild. Do not pick random numbers by banging on keyboard, or by using
|
||||||
`0xdeadf00d`, `0x42424242` etc. A good way is to use the following
|
`0xdeadf00d`, `0x42424242` etc. A good way is to use the following
|
||||||
shell command: `printf "0x%04x%04x\n" $RANDOM $RANDOM`
|
shell command: `printf "0x%04x%04x\n" $RANDOM $RANDOM`
|
||||||
|
Another good way is the link at the bottom of https://microsoft.github.io/uf2/patcher/
|
||||||
|
This procedure was unfortunately not used for the SAMD51 and NRF52840 below.
|
||||||
|
|
||||||
#### Family list
|
#### Family list
|
||||||
|
|
||||||
* SAMD21 - 0x68ed2b88
|
* Microchip (Atmel) SAMD21 - 0x68ed2b88
|
||||||
* SAMD51 - 0x55114460
|
* Microchip (Atmel) SAML21 - 0x1851780a
|
||||||
* NRF52 - 0x1b57745f
|
* Microchip (Atmel) SAMD51 - 0x55114460
|
||||||
* STM32F1 - 0x5ee21072
|
* Nordic NRF52840 - 0xada52840
|
||||||
* STM32F4 - 0x57755a57
|
* ST STM32F103 - 0x5ee21072
|
||||||
* ATmega32 - 0x16573617
|
* ST STM32F401 - 0x57755a57
|
||||||
|
* ST STM32F407 - 0x6d0922fa
|
||||||
|
* ST STM32F407VG - 0x8fb060fe
|
||||||
|
* Microchip (Atmel) ATmega32 - 0x16573617
|
||||||
|
* Cypress FX2 - 0x5a18069b
|
||||||
|
* ESP32 - 0x1c5f21b0
|
||||||
|
* NXP i.MX RT10XX - 0x4fb2d5bd
|
||||||
|
|
||||||
### Rationale
|
### Rationale
|
||||||
|
|
||||||
|
|
@ -249,20 +265,44 @@ Typical writing procedure is as follows:
|
||||||
The fields `blockNo` and `numBlocks` refer to the entire UF2 file, not the current
|
The fields `blockNo` and `numBlocks` refer to the entire UF2 file, not the current
|
||||||
file.
|
file.
|
||||||
|
|
||||||
|
## MD5 checksum
|
||||||
|
|
||||||
|
When the `0x4000` flag is set, the last 24 bytes of `data[]` hold the following structure:
|
||||||
|
|
||||||
|
| Offset | Size | Value |
|
||||||
|
|--------|------|---------------------------------------------------|
|
||||||
|
| 0 | 4 | Start address of region |
|
||||||
|
| 4 | 4 | Length of region in bytes |
|
||||||
|
| 8 | 16 | MD5 checksum in binary format |
|
||||||
|
|
||||||
|
The flashing program should compute the MD5 sum of the specified region.
|
||||||
|
If the region checksum matches, flashing of the current block can be skipped.
|
||||||
|
Typically, many blocks in sequence will have the same region specified,
|
||||||
|
and can all be skipped, if the matching succeeded.
|
||||||
|
The position of the current block will typically be inside of the region.
|
||||||
|
The position and size of the region should be multiple of page erase size
|
||||||
|
(4k or 64k on typical SPI flash).
|
||||||
|
|
||||||
|
This is currently only used on ESP32, which is also why MD5 checksum is used.
|
||||||
|
|
||||||
|
|
||||||
## Implementations
|
## Implementations
|
||||||
|
|
||||||
### Bootloaders
|
### Bootloaders
|
||||||
|
|
||||||
* [SAMD21](https://github.com/Microsoft/uf2-samd21)
|
* [Microchip ATSAMD21 and ATSAMD51](https://github.com/Microsoft/uf2-samdx1)
|
||||||
* [Arduino UNO](https://github.com/mmoskal/uf2-uno)
|
* [Arduino UNO](https://github.com/mmoskal/uf2-uno)
|
||||||
* [STM32](https://github.com/mmoskal/uf2-stm32f)
|
* [STM32F103](https://github.com/mmoskal/uf2-stm32)
|
||||||
* [NRF52840](https://github.com/adafruit/Adafruit_nRF52840_Bootloader)
|
* [STM32F4](https://github.com/mmoskal/uf2-stm32f)
|
||||||
|
* [Nordic NRF52840](https://github.com/adafruit/Adafruit_nRF52840_Bootloader)
|
||||||
* [Linux (RPi Zero)](https://github.com/microsoft/uf2-linux)
|
* [Linux (RPi Zero)](https://github.com/microsoft/uf2-linux)
|
||||||
|
* [Cypress FX2](https://github.com/whitequark/libfx2/tree/master/firmware/boot-uf2)
|
||||||
|
|
||||||
There's an ongoing effort to implement UF2 in [Codal](https://github.com/lancaster-university/codal-core), see `msc` branch.
|
There's an ongoing effort to implement UF2 in [Codal](https://github.com/lancaster-university/codal-core).
|
||||||
|
|
||||||
### Editors
|
### Editors
|
||||||
|
|
||||||
|
* https://arcade.makecode.com
|
||||||
* https://makecode.adafruit.com
|
* https://makecode.adafruit.com
|
||||||
* https://makecode.seeedstudio.com
|
* https://makecode.seeedstudio.com
|
||||||
* https://maker.makecode.com
|
* https://maker.makecode.com
|
||||||
|
|
|
||||||
1
_config.yml
Normal file
1
_config.yml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
theme: jekyll-theme-cayman
|
||||||
126
cf2.md
Normal file
126
cf2.md
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
# Binary Bootloader-embedded Config (CF2)
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
The goal of CF2 is to run the same binary on a number of devices with different
|
||||||
|
peripheral configuration, but the same MCU.
|
||||||
|
Typically, this has been achieved by embedding identification components in hardware
|
||||||
|
(eg. connecting certain MCU pins to GND or 3V) and having the firmware dynamically
|
||||||
|
detect these.
|
||||||
|
While simple, this only allows a few bits of configuration data to be stored on device.
|
||||||
|
|
||||||
|
Instead, we use the bootloader area for storing device specific configuration data.
|
||||||
|
Such data is not altered when the device is programmed with new firmware,
|
||||||
|
and the said firmware can refer to the configuration data in the bootloader area to
|
||||||
|
alter its behavior.
|
||||||
|
|
||||||
|
For example, MakeCode Arcade requires a device with 7 buttons (directions, A, B, and MENU),
|
||||||
|
a screen from a specific family, and one of the few supported accelerometers.
|
||||||
|
The exact configuration depends on the manufacturer, but the binary produced by MakeCode
|
||||||
|
will work on any device with the right MCU, provided it has the right configuration
|
||||||
|
data in the bootloader area.
|
||||||
|
|
||||||
|
Thus, the user only needs to select the type of MCU (which is color coded,
|
||||||
|
and is assigned a simple user-facing name), and the resulting binary will work
|
||||||
|
on their device.
|
||||||
|
Moreover, they can drag the UF2 file between devices, provided they run the same MCU.
|
||||||
|
Once bootloaders are updated with networking support (wired or wireless), one device with
|
||||||
|
a given MCU will be able to flash another.
|
||||||
|
|
||||||
|
## Configuration data format
|
||||||
|
|
||||||
|
Data comes as pairs of 32 bit unsigned integers (in machine byte order).
|
||||||
|
* first pair is `0x1e9e10f1, 0x20227a79`
|
||||||
|
* second pair consists of the number of configuration entries, and a zero (reserved)
|
||||||
|
* then configuration entries follow, where the first element is the configuration
|
||||||
|
entry key number, and the second is the value (these entries are sorted by key number)
|
||||||
|
* finally, a number of zeroes follows (typically at least a few hundred),
|
||||||
|
to store any additional configuration data in future
|
||||||
|
|
||||||
|
The file `configkeys.h` with definitions of key names can be downloaded from the
|
||||||
|
patcher website (see below).
|
||||||
|
|
||||||
|
If you require your own key names, please use
|
||||||
|
[random large key names](https://github.com/Microsoft/uf2#picking-numbers-at-random).
|
||||||
|
|
||||||
|
## The Patcher
|
||||||
|
|
||||||
|
At https://microsoft.github.io/uf2/patcher/ we provide a tool, which given a UF2 or BIN file
|
||||||
|
and a configuration definition, binary-patches the UF2/BIN file and lets the user download it.
|
||||||
|
The tool also parses existing configuration information from the UF2/BIN file to show it.
|
||||||
|
|
||||||
|
Such tool is to be used by makers of devices.
|
||||||
|
For example, for MakeCode Arcade, this includes both factories and users who take an existing
|
||||||
|
multi-purpose board and connect keys and screen.
|
||||||
|
Such user would then download generic MakeCode Arcade bootloader (either a UF2 file,
|
||||||
|
which upgrades the bootloader, or a .BIN file with the bootloader) for the given MCU,
|
||||||
|
and binary patch it with their configuration.
|
||||||
|
Then, they would update the bootloader, and have a Arcade-compatible device.
|
||||||
|
|
||||||
|
## Configuration file syntax
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# This is comment, which is ignored
|
||||||
|
// this is also a comment
|
||||||
|
|
||||||
|
# Configuration values for display registers and size
|
||||||
|
DISPLAY_CFG0 = 0x80
|
||||||
|
DISPLAY_CFG1 = 0x603
|
||||||
|
DISPLAY_CFG2 = 0x16
|
||||||
|
DISPLAY_HEIGHT = 128
|
||||||
|
DISPLAY_WIDTH = 160
|
||||||
|
|
||||||
|
PINS_PORT_SIZE = PA_32 # see below
|
||||||
|
|
||||||
|
# pin headers (if any)
|
||||||
|
PIN_A0 = PA02
|
||||||
|
PIN_A1 = PA05
|
||||||
|
PIN_A2 = PB08
|
||||||
|
PIN_D2 = PA07
|
||||||
|
PIN_D3 = PB22
|
||||||
|
PIN_SCK = PA01
|
||||||
|
PIN_MISO = PB23
|
||||||
|
PIN_MOSI = PA00
|
||||||
|
|
||||||
|
# pin functions
|
||||||
|
PIN_BTN_LEFT = PIN_D2 # use pin header name
|
||||||
|
PIN_BTN_UP = PB13 # use pin directly
|
||||||
|
PIN_DISPLAY_CS = 18 # can even just use a number for the pin
|
||||||
|
PIN_DISPLAY_SCK = PIN_SCK
|
||||||
|
PIN_DISPLAY_MOSI = PIN_MOSI
|
||||||
|
|
||||||
|
# custom configuration keys
|
||||||
|
_679732427 = 123
|
||||||
|
_815320287 = 0x80192
|
||||||
|
|
||||||
|
# remove an existing config entry
|
||||||
|
PIN_BTN_MENU2 = null
|
||||||
|
```
|
||||||
|
|
||||||
|
The keys are either key names, or underscore followed by a decimal number.
|
||||||
|
The values are numbers in either decimal or hexadecimal,
|
||||||
|
references to other keys, or pin numbers.
|
||||||
|
The way pin numbers are parsed depends on the `PINS_PORT_SIZE`:
|
||||||
|
* `PA_16` - pins are `PA00`-`PA15`, `PB00-PB15`, ..., used on STM32
|
||||||
|
* `PA_32` - pins are `PA00`-`PA31`, ... - used on Microchip ATSAMD
|
||||||
|
* `P0_16` - pins are `P0_0`-`P0_15`, `P1_0-P1_15`, ... - not used?
|
||||||
|
* `P0_32` - pins are `P0_0-P0_31`, ... - used on Nordic NRF
|
||||||
|
* `0` or missing - pins are `P_0`, `P_1`, ..., `P_1000`
|
||||||
|
|
||||||
|
Certain keys (like `PINS_PORT_SIZE`) have a number of pre-defined values,
|
||||||
|
which can be used instead of integers.
|
||||||
|
|
||||||
|
Because you're usually use this syntax to patch an existing configuration,
|
||||||
|
you sometimes may want to remove an entry that's already there.
|
||||||
|
To do that, use `null` as the value of the key.
|
||||||
|
|
||||||
|
For list of keys and predefined values, see `configkeys.h` which can be downloaded
|
||||||
|
from the patcher website.
|
||||||
|
|
||||||
|
## Running from node.js
|
||||||
|
|
||||||
|
The patcher tool can be also run from command line.
|
||||||
|
Download [patcher.js](https://raw.githubusercontent.com/Microsoft/uf2/master/patcher/patcher.js)
|
||||||
|
and run it with node. It will print out help.
|
||||||
5
hf2.md
5
hf2.md
|
|
@ -14,8 +14,8 @@ ones, but be less efficient.
|
||||||
In particular, it is suitable for running over USB HID (Human Interface Device),
|
In particular, it is suitable for running over USB HID (Human Interface Device),
|
||||||
which is widely supported in various operating systems without the need for kernel-space
|
which is widely supported in various operating systems without the need for kernel-space
|
||||||
drivers. It is also possible to run the protocol over a WebUSB link with either a single
|
drivers. It is also possible to run the protocol over a WebUSB link with either a single
|
||||||
interrupt endpoint or two bulk endpoints, allowing direct access from supported
|
interrupt endpoint or two bulk endpoints, as well as using just the control pipe,
|
||||||
browsers.
|
allowing direct access from supported browsers.
|
||||||
|
|
||||||
## Raw message format
|
## Raw message format
|
||||||
|
|
||||||
|
|
@ -142,6 +142,7 @@ struct HF2_BININFO_Result {
|
||||||
uint32_t flash_page_size;
|
uint32_t flash_page_size;
|
||||||
uint32_t flash_num_pages;
|
uint32_t flash_num_pages;
|
||||||
uint32_t max_message_size;
|
uint32_t max_message_size;
|
||||||
|
uint32_t family_id; // optional
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
83
patcher/index.html
Normal file
83
patcher/index.html
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<title>CF2 binary patcher</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="theme-color" content="#157878">
|
||||||
|
<link rel="stylesheet" href="https://microsoft.github.io/uf2/assets/css/style.css">
|
||||||
|
<script src="patcher.js"></script>
|
||||||
|
<script src="web.js"></script>
|
||||||
|
<style>
|
||||||
|
#patch {
|
||||||
|
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
width: 100%;
|
||||||
|
height: 12em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
padding: 2rem 2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-dl,
|
||||||
|
.btn-dl:hover {
|
||||||
|
background-color: green;
|
||||||
|
float: right;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 5em;
|
||||||
|
}
|
||||||
|
footer a {
|
||||||
|
margin-right: 2em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body ondrop="dropHandler(event);" ondragover="dragOverHandler(event);">
|
||||||
|
<section class="page-header">
|
||||||
|
<h1 class="project-name">CF2 patcher</h1>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="main-content">
|
||||||
|
<h2>Your patch</h2>
|
||||||
|
<textarea id="patch" spellcheck=false onchange="savePatch(event);"></textarea>
|
||||||
|
<a href="#" id="apply" class="btn btn-dl">Apply my patch</a>
|
||||||
|
|
||||||
|
<h2>Information</h2>
|
||||||
|
<pre><code id="currconfig">Drop UF2 or BIN file above to see its config.</code></pre>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/Microsoft/uf2/blob/master/cf2.md">Learn more</a>
|
||||||
|
or
|
||||||
|
<a href="#" onclick="defines()">download configkeys.h</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="#" id="rnd">Generate random number</a>
|
||||||
|
<span id="rnd-res"></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="https://makecode.com/privacy" target="_blank" rel="noopener">Privacy & Cookies</a>
|
||||||
|
<a href="https://makecode.com/termsofuse" target="_blank" rel="noopener"> Terms Of Use</a>
|
||||||
|
<a href="https://makecode.com/trademarks" target="_blank" rel="noopener">Trademarks</a>
|
||||||
|
<span>© 2019 Microsoft</span>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
restorePatch()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
1001
patcher/patcher.js
Normal file
1001
patcher/patcher.js
Normal file
File diff suppressed because it is too large
Load diff
109
patcher/web.js
Normal file
109
patcher/web.js
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function savePatch(ev) {
|
||||||
|
let text = document.getElementById("patch")
|
||||||
|
localStorage["UF2_PATCH"] = text.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function genRnd(ev) {
|
||||||
|
ev.preventDefault()
|
||||||
|
let vals = new Uint32Array(1)
|
||||||
|
window.crypto.getRandomValues(vals)
|
||||||
|
document.getElementById("rnd-res").textContent = "Random number: 0x" +
|
||||||
|
("000000000" + vals[0].toString(16)).slice(-8)
|
||||||
|
}
|
||||||
|
|
||||||
|
function restorePatch() {
|
||||||
|
let text = document.getElementById("patch")
|
||||||
|
text.value = localStorage["UF2_PATCH"] || ""
|
||||||
|
document.getElementById("apply").onclick = applyPatch
|
||||||
|
document.getElementById("rnd").onclick = genRnd
|
||||||
|
}
|
||||||
|
|
||||||
|
function download(buf, name) {
|
||||||
|
let blob = new Blob([buf], {
|
||||||
|
type: "application/x-uf2"
|
||||||
|
});
|
||||||
|
let url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
let a = document.createElement("a");
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.style = "display: none";
|
||||||
|
a.href = url;
|
||||||
|
a.download = name;
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
let currUF2 = null
|
||||||
|
let currUF2Name = ""
|
||||||
|
|
||||||
|
function showMSG() {
|
||||||
|
if (infoMsg)
|
||||||
|
document.getElementById("currconfig").textContent = infoMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrap(f) {
|
||||||
|
try {
|
||||||
|
infoMsg = ""
|
||||||
|
f()
|
||||||
|
showMSG()
|
||||||
|
} catch (e) {
|
||||||
|
log("Exception: " + e.message)
|
||||||
|
showMSG()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function defines() {
|
||||||
|
download(configkeysH(), "configkeys.h")
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPatch() {
|
||||||
|
wrap(() => {
|
||||||
|
let text = document.getElementById("patch")
|
||||||
|
let newcfg = text.value.trim()
|
||||||
|
if (!currUF2)
|
||||||
|
log("You have to drop a UF2 or BIN file with bootloader above before applying patches.")
|
||||||
|
else if (!newcfg)
|
||||||
|
log("You didn't give any patch to apply.")
|
||||||
|
else {
|
||||||
|
let buf = currUF2.slice()
|
||||||
|
let r = patchConfig(buf, newcfg)
|
||||||
|
if (!r.changes) {
|
||||||
|
log("No changes.")
|
||||||
|
} else {
|
||||||
|
log("\nChanges:\n" + r.changes)
|
||||||
|
}
|
||||||
|
log("Downloading " + currUF2Name)
|
||||||
|
download(r.patched, currUF2Name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function dropHandler(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
for (let i = 0; i < ev.dataTransfer.items.length; i++) {
|
||||||
|
if (ev.dataTransfer.items[i].kind === 'file') {
|
||||||
|
let file = ev.dataTransfer.items[i].getAsFile();
|
||||||
|
let reader = new FileReader();
|
||||||
|
infoMsg = ""
|
||||||
|
reader.onload = e => {
|
||||||
|
wrap(() => {
|
||||||
|
let buf = new Uint8Array(reader.result)
|
||||||
|
let cfg = readConfig(buf)
|
||||||
|
currUF2 = buf
|
||||||
|
infoMsg += "\n" + cfg
|
||||||
|
currUF2Name = file.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragOverHandler(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
153
utils/uf2conv.py
Executable file → Normal file
153
utils/uf2conv.py
Executable file → Normal file
|
|
@ -1,5 +1,4 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
@ -8,17 +7,20 @@ import os
|
||||||
import os.path
|
import os.path
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
|
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
|
||||||
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
|
UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
|
||||||
UF2_MAGIC_END = 0x0AB16F30 # Ditto
|
UF2_MAGIC_END = 0x0AB16F30 # Ditto
|
||||||
|
|
||||||
families = {
|
families = {
|
||||||
'SAMD21': 0x68ed2b88,
|
'SAMD21': 0x68ed2b88,
|
||||||
|
'SAML21': 0x1851780a,
|
||||||
'SAMD51': 0x55114460,
|
'SAMD51': 0x55114460,
|
||||||
'NRF52': 0x1b57745f,
|
'NRF52': 0x1b57745f,
|
||||||
'STM32F1': 0x5ee21072,
|
'STM32F1': 0x5ee21072,
|
||||||
'STM32F4': 0x57755a57,
|
'STM32F4': 0x57755a57,
|
||||||
'ATMEGA32': 0x16573617,
|
'ATMEGA32': 0x16573617,
|
||||||
|
'MIMXRT10XX': 0x4FB2D5BD
|
||||||
}
|
}
|
||||||
|
|
||||||
INFO_FILE = "/INFO_UF2.TXT"
|
INFO_FILE = "/INFO_UF2.TXT"
|
||||||
|
|
@ -26,27 +28,31 @@ INFO_FILE = "/INFO_UF2.TXT"
|
||||||
appstartaddr = 0x2000
|
appstartaddr = 0x2000
|
||||||
familyid = 0x0
|
familyid = 0x0
|
||||||
|
|
||||||
def isUF2(buf):
|
|
||||||
|
def is_uf2(buf):
|
||||||
w = struct.unpack("<II", buf[0:8])
|
w = struct.unpack("<II", buf[0:8])
|
||||||
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
|
return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
|
||||||
|
|
||||||
def isHEX(buf):
|
def is_hex(buf):
|
||||||
w = buf[0:30]
|
try:
|
||||||
if w[0] == ':' and re.match("^[:0-9a-fA-F\r\n]+$", buf):
|
w = buf[0:30].decode("utf-8")
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return False
|
||||||
|
if w[0] == ':' and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def convertFromUF2(buf):
|
def convert_from_uf2(buf):
|
||||||
global appstartaddr
|
global appstartaddr
|
||||||
numblocks = len(buf) / 512
|
numblocks = len(buf) // 512
|
||||||
curraddr = None
|
curraddr = None
|
||||||
outp = ""
|
outp = b""
|
||||||
for blockno in range(0, numblocks):
|
for blockno in range(numblocks):
|
||||||
ptr = blockno * 512
|
ptr = blockno * 512
|
||||||
block = buf[ptr:ptr + 512]
|
block = buf[ptr:ptr + 512]
|
||||||
hd = struct.unpack("<IIIIIIII", block[0:32])
|
hd = struct.unpack(b"<IIIIIIII", block[0:32])
|
||||||
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
|
if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
|
||||||
print "Skipping block at " + ptr + "; bad magic"
|
print("Skipping block at " + ptr + "; bad magic")
|
||||||
continue
|
continue
|
||||||
if hd[2] & 1:
|
if hd[2] & 1:
|
||||||
# NO-flash flag set; skip block
|
# NO-flash flag set; skip block
|
||||||
|
|
@ -67,30 +73,39 @@ def convertFromUF2(buf):
|
||||||
assert False, "Non-word padding size at " + ptr
|
assert False, "Non-word padding size at " + ptr
|
||||||
while padding > 0:
|
while padding > 0:
|
||||||
padding -= 4
|
padding -= 4
|
||||||
outp += "\x00\x00\x00\x00"
|
outp += b"\x00\x00\x00\x00"
|
||||||
outp += block[32 : 32 + datalen]
|
outp += block[32 : 32 + datalen]
|
||||||
curraddr = newaddr + datalen
|
curraddr = newaddr + datalen
|
||||||
return outp
|
return outp
|
||||||
|
|
||||||
def convertToUF2(fileContent):
|
def convert_to_carray(file_content):
|
||||||
|
outp = "const unsigned char bindata[] __attribute__((aligned(16))) = {"
|
||||||
|
for i in range(len(file_content)):
|
||||||
|
if i % 16 == 0:
|
||||||
|
outp += "\n"
|
||||||
|
outp += "0x%02x, " % ord(file_content[i])
|
||||||
|
outp += "\n};\n"
|
||||||
|
return outp
|
||||||
|
|
||||||
|
def convert_to_uf2(file_content):
|
||||||
global familyid
|
global familyid
|
||||||
datapadding = ""
|
datapadding = b""
|
||||||
while len(datapadding) < 512 - 256 - 32 - 4:
|
while len(datapadding) < 512 - 256 - 32 - 4:
|
||||||
datapadding += "\x00\x00\x00\x00"
|
datapadding += b"\x00\x00\x00\x00"
|
||||||
numblocks = (len(fileContent) + 255) / 256
|
numblocks = (len(file_content) + 255) // 256
|
||||||
outp = ""
|
outp = b""
|
||||||
for blockno in range(0, numblocks):
|
for blockno in range(numblocks):
|
||||||
ptr = 256 * blockno
|
ptr = 256 * blockno
|
||||||
chunk = fileContent[ptr:ptr + 256]
|
chunk = file_content[ptr:ptr + 256]
|
||||||
flags = 0x0
|
flags = 0x0
|
||||||
if familyid:
|
if familyid:
|
||||||
flags |= 0x2000
|
flags |= 0x2000
|
||||||
hd = struct.pack("<IIIIIIII",
|
hd = struct.pack(b"<IIIIIIII",
|
||||||
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
||||||
flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
|
flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
|
||||||
while len(chunk) < 256:
|
while len(chunk) < 256:
|
||||||
chunk += "\x00"
|
chunk += b"\x00"
|
||||||
block = hd + chunk + datapadding + struct.pack("<I", UF2_MAGIC_END)
|
block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
|
||||||
assert len(block) == 512
|
assert len(block) == 512
|
||||||
outp += block
|
outp += block
|
||||||
return outp
|
return outp
|
||||||
|
|
@ -98,9 +113,7 @@ def convertToUF2(fileContent):
|
||||||
class Block:
|
class Block:
|
||||||
def __init__(self, addr):
|
def __init__(self, addr):
|
||||||
self.addr = addr
|
self.addr = addr
|
||||||
self.bytes = []
|
self.bytes = bytearray(256)
|
||||||
for i in range(0, 256):
|
|
||||||
self.bytes.append(0)
|
|
||||||
|
|
||||||
def encode(self, blockno, numblocks):
|
def encode(self, blockno, numblocks):
|
||||||
global familyid
|
global familyid
|
||||||
|
|
@ -110,14 +123,13 @@ class Block:
|
||||||
hd = struct.pack("<IIIIIIII",
|
hd = struct.pack("<IIIIIIII",
|
||||||
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
UF2_MAGIC_START0, UF2_MAGIC_START1,
|
||||||
flags, self.addr, 256, blockno, numblocks, familyid)
|
flags, self.addr, 256, blockno, numblocks, familyid)
|
||||||
for i in range(0, 256):
|
hd += self.bytes[0:256]
|
||||||
hd += chr(self.bytes[i])
|
|
||||||
while len(hd) < 512 - 4:
|
while len(hd) < 512 - 4:
|
||||||
hd += "\x00"
|
hd += b"\x00"
|
||||||
hd += struct.pack("<I", UF2_MAGIC_END)
|
hd += struct.pack("<I", UF2_MAGIC_END)
|
||||||
return hd
|
return hd
|
||||||
|
|
||||||
def convertFromHexToUF2(buf):
|
def convert_from_hex_to_uf2(buf):
|
||||||
global appstartaddr
|
global appstartaddr
|
||||||
appstartaddr = None
|
appstartaddr = None
|
||||||
upper = 0
|
upper = 0
|
||||||
|
|
@ -152,17 +164,21 @@ def convertFromHexToUF2(buf):
|
||||||
addr += 1
|
addr += 1
|
||||||
i += 1
|
i += 1
|
||||||
numblocks = len(blocks)
|
numblocks = len(blocks)
|
||||||
resfile = ""
|
resfile = b""
|
||||||
for i in range(0, numblocks):
|
for i in range(0, numblocks):
|
||||||
resfile += blocks[i].encode(i, numblocks)
|
resfile += blocks[i].encode(i, numblocks)
|
||||||
return resfile
|
return resfile
|
||||||
|
|
||||||
|
def to_str(b):
|
||||||
|
return b.decode("utf-8")
|
||||||
|
|
||||||
def getdrives():
|
def get_drives():
|
||||||
drives = []
|
drives = []
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk", "get", "DeviceID,", "VolumeName,", "FileSystem,", "DriveType"])
|
r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
|
||||||
for line in r.split('\n'):
|
"get", "DeviceID,", "VolumeName,",
|
||||||
|
"FileSystem,", "DriveType"])
|
||||||
|
for line in to_str(r).split('\n'):
|
||||||
words = re.split('\s+', line)
|
words = re.split('\s+', line)
|
||||||
if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
|
if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
|
||||||
drives.append(words[0])
|
drives.append(words[0])
|
||||||
|
|
@ -177,32 +193,37 @@ def getdrives():
|
||||||
for d in os.listdir(rootpath):
|
for d in os.listdir(rootpath):
|
||||||
drives.append(os.path.join(rootpath, d))
|
drives.append(os.path.join(rootpath, d))
|
||||||
|
|
||||||
def hasInfo(d):
|
|
||||||
|
def has_info(d):
|
||||||
try:
|
try:
|
||||||
return os.path.isfile(d + INFO_FILE)
|
return os.path.isfile(d + INFO_FILE)
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return filter(hasInfo, drives)
|
return list(filter(has_info, drives))
|
||||||
|
|
||||||
def boardID(path):
|
|
||||||
|
def board_id(path):
|
||||||
with open(path + INFO_FILE, mode='r') as file:
|
with open(path + INFO_FILE, mode='r') as file:
|
||||||
fileContent = file.read()
|
file_content = file.read()
|
||||||
return re.search("Board-ID: ([^\r\n]*)", fileContent).group(1)
|
return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)
|
||||||
|
|
||||||
def listdrives():
|
|
||||||
for d in getdrives():
|
|
||||||
print d, boardID(d)
|
|
||||||
|
|
||||||
def writeFile(name, buf):
|
def list_drives():
|
||||||
|
for d in get_drives():
|
||||||
|
print(d, board_id(d))
|
||||||
|
|
||||||
|
|
||||||
|
def write_file(name, buf):
|
||||||
with open(name, "wb") as f:
|
with open(name, "wb") as f:
|
||||||
f.write(buf)
|
f.write(buf)
|
||||||
print "Wrote %d bytes to %s." % (len(buf), name)
|
print("Wrote %d bytes to %s." % (len(buf), name))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global appstartaddr, familyid
|
global appstartaddr, familyid
|
||||||
def error(msg):
|
def error(msg):
|
||||||
print msg
|
print(msg)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
|
parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
|
||||||
parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
|
parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
|
||||||
|
|
@ -218,9 +239,13 @@ def main():
|
||||||
help='list connected devices')
|
help='list connected devices')
|
||||||
parser.add_argument('-c' , '--convert', action='store_true',
|
parser.add_argument('-c' , '--convert', action='store_true',
|
||||||
help='do not flash, just convert')
|
help='do not flash, just convert')
|
||||||
|
parser.add_argument('-D' , '--deploy', action='store_true',
|
||||||
|
help='just flash, do not convert')
|
||||||
parser.add_argument('-f' , '--family', dest='family', type=str,
|
parser.add_argument('-f' , '--family', dest='family', type=str,
|
||||||
default="0x0",
|
default="0x0",
|
||||||
help='specify familyID - number or name (default: 0x0)')
|
help='specify familyID - number or name (default: 0x0)')
|
||||||
|
parser.add_argument('-C' , '--carray', action='store_true',
|
||||||
|
help='convert binary file to a C array, not UF2')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
appstartaddr = int(args.base, 0)
|
appstartaddr = int(args.base, 0)
|
||||||
|
|
||||||
|
|
@ -233,38 +258,44 @@ def main():
|
||||||
error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
|
error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
|
||||||
|
|
||||||
if args.list:
|
if args.list:
|
||||||
listdrives()
|
list_drives()
|
||||||
else:
|
else:
|
||||||
if not args.input:
|
if not args.input:
|
||||||
error("Need input file")
|
error("Need input file")
|
||||||
with open(args.input, mode='rb') as file:
|
with open(args.input, mode='rb') as f:
|
||||||
inpbuf = file.read()
|
inpbuf = f.read()
|
||||||
fromUF2 = isUF2(inpbuf)
|
from_uf2 = is_uf2(inpbuf)
|
||||||
ext = "uf2"
|
ext = "uf2"
|
||||||
if fromUF2:
|
if args.deploy:
|
||||||
outbuf = convertFromUF2(inpbuf)
|
outbuf = inpbuf
|
||||||
|
elif from_uf2:
|
||||||
|
outbuf = convert_from_uf2(inpbuf)
|
||||||
ext = "bin"
|
ext = "bin"
|
||||||
elif isHEX(inpbuf):
|
elif is_hex(inpbuf):
|
||||||
outbuf = convertFromHexToUF2(inpbuf)
|
outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
|
||||||
|
elif args.carray:
|
||||||
|
outbuf = convert_to_carray(inpbuf)
|
||||||
|
ext = "h"
|
||||||
else:
|
else:
|
||||||
outbuf = convertToUF2(inpbuf)
|
outbuf = convert_to_uf2(inpbuf)
|
||||||
print "Converting to %s, output size: %d, start address: 0x%x" % (ext, len(outbuf), appstartaddr)
|
print("Converting to %s, output size: %d, start address: 0x%x" %
|
||||||
|
(ext, len(outbuf), appstartaddr))
|
||||||
if args.convert:
|
if args.convert or ext != "uf2":
|
||||||
drives = []
|
drives = []
|
||||||
if args.output == None:
|
if args.output == None:
|
||||||
args.output = "flash." + ext
|
args.output = "flash." + ext
|
||||||
else:
|
else:
|
||||||
drives = getdrives()
|
drives = get_drives()
|
||||||
|
|
||||||
if args.output:
|
if args.output:
|
||||||
writeFile(args.output, outbuf)
|
write_file(args.output, outbuf)
|
||||||
else:
|
else:
|
||||||
if len(drives) == 0:
|
if len(drives) == 0:
|
||||||
error("No drive to deploy.")
|
error("No drive to deploy.")
|
||||||
for d in drives:
|
for d in drives:
|
||||||
print "Flashing %s (%s)" % (d, boardID(d))
|
print("Flashing %s (%s)" % (d, board_id(d)))
|
||||||
writeFile(outbuf, d + "/NEW.UF2")
|
write_file(d + "/NEW.UF2", outbuf)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue