MicroPython Hex File

When MicroPython is built, the compiler produces an Intel Hex file containing the MicroPython firmware. Additional data can then be added to this file to contain information about the MicroPython version, or the Python code to execute on start-up.

The general memory layout used is:

  • 0x00000000: Start of MicroPython firmware - up to 248 KBs
  • 0x0003e000: Start of appended script (optional) - up to 8 Kbs
  • 0x100010c0: UICR customer[16] register, start of MicroPython information - 28 bytes

Note

If you append any data or modify the Intel Hex file, please ensure the addresses of the data stored progress in incremental order. If there is an address jump backwards DAPLink will fail to flash the file.

UICR format (micro:bit V1)

The User Information Configuration Registers (UICR) is a region of Non-Volatile Memory available to store user-specific settings. The first 128 Bytes are reserved, but we can use the other 128 Bytes to store any arbitrary data.

MicroPython stores the following information, in little endian, starting from the UICR customer[16] register:

  • 0x100010c0: 4-byte integer with magic value 0x17eeb07c
  • 0x100010c4: 4-byte integer with value 0xffffffff
  • 0x100010c8: 4-byte integer with value 0x0000000a (log base 2 of the flash page size, being 1024 bytes)
  • 0x100010ca: 2-byte integer with value 0x0000 (start page of the firmware)
  • 0x100010cc: 2-byte integer storing number of pages used by the firmware
  • 0x100010d0: 4-byte integer with value 0xffffffff
  • 0x100010d4: 4-byte integer with the address in the firmware of the version string
  • 0x100010d8: 4-byte integer with value 0x00000000

Layout table (micro:bit V2)

A flash layout table is appended to the the hex file when building MicroPython for a micro:bit V2.

The layout table is a sequence of 16-byte entries. The last entry contains the header (including magic numbers) and is aligned to the end of a page such that the final byte of the layout table is the final byte of the page it resides in. This is so it can be quickly and easily searched for.

The layout table has the following format. All integer values are unsigned and store little endian.

0x00  0x01  0x02  0x03  0x04  0x05  0x06  0x07  0x08  0x09  0x0a  0x0b  0x0c  0x0d  0x0e

ID    HT    REG_PAGE    REG_LEN                 HASH_DATA
(additional regions)
...
MAGIC1                  VERSION     TABLE_LEN   NUM_REG     PSIZE_LOG2  MAGIC2

The values are:

ID         - 1 byte  - region id for this entry, defined by the region
HT         - 1 byte  - hash type of the region hash data
REG_PAGE   - 2 bytes - starting page number of the region
REG_LEN    - 4 bytes - length in bytes of the region
HASH_DATA  - 8 bytes - data for the hash of this region
                       HT=0: hash data is empty
                       HT=1: hash data contains 8 bytes of verbatim data
                       HT=2: hash data contains a 4-byte pointer to a string

MAGIC1     - 4 bytes - 0x597F30FE
VERSION    - 2 bytes - table version (currently 1)
TABLE_LEN  - 2 bytes - length in bytes of the table excluding this header row
NUM_REG    - 2 bytes - number of regions
PSIZE_LOG2 - 2 bytes - native page size of the flash, log-2
MAGIC2     - 4 bytes - 0xC1B1D79D

This layout table is used to communicate the version of MicroPython and the current memory layout to a Bluetooth client and enable partial flashing (only updating the Python script, and keeping the existing version of MicroPython in flash).

Steps to create the firmware.hex file

micro:bit V1

This applies to MicroPython for the micro:bit V1, the source of which can be found here: bbcmicrobit/micropython.

The yotta tool is used to build MicroPython, but before that takes place additional files have to be generated by the Makefile in preparation for the build, and additional data is added to the hex file after.

Running the make all command executes the following steps:

  • The tools/makeversionhdr.py script creates the microbitversion.h file with macros containing build information
  • Yotta builds the source and creates a bare hex file with just the firmware
  • The tools/adduicr.py script adds the UICR to the bare hex
  • The final hex file is placed in build/firmware.hex
  • The user can optionally append a script using tools/makecombinedhex.py (or other tools)

micro:bit V2

This applies to MicroPython for the micro:bit V2, the source of which can be found here: microbit-foundation/micropython-microbit-v2.

This is a port of MicroPython to the micro:bit which uses CODAL as the underlying target platform.

Running the make command executes the following steps:

  • Create build output directory, run cmake, and make sure codal libraries exist (via cmake).
  • Build both libmicropython.a (from source in src/codal_port/) and the CODAL app (from source in src/codal_app/).
  • Run addlayouttable.py to add the layout table to the .hex file
  • Create the microbit-micropython firmware as MICROBIT.hex in the src/ directory, which can be copied to the micro:bit.

Including a user script

This section applies to both micro:bit V1 and V2.

User scripts are stored in the MicroPython filesystem and if a main.py script exists it is run when MicroPython starts. Additional Python scripts can also be included and executed from the main.py file, or the REPL.

The Python Editor uses microbit-fs to create the filesystem and include it in the HEX file. The Python Editor must add the filesystem to HEX files for MicroPython V1 & V2, and then combine both into a Universal HEX file to ensure compatibility with both hardware variants.

Appended script format (Deprecated)

This method of appending the script to the end of MicroPython was originally used for micro:bit V1, but is no longer used. Python files are now stored in the filesystem and main.py is the program entry point.

MicroPython checks the first 2 bytes at address 0x0003e000 for a magic string to indicate if there is an appended script. If the magic string is found, it will automatically execute the Python code stored there, unless there is a main.py file stored in the MicroPython filesystem.

  • 0x0003e000: 2 bytes “MP”
  • 0x0003e002: 2 bytes, little endian integer for the length (in bytes) of the appended script (not counting this 4 byte header)
  • 0x0003e004: Script stored as bytes, for MicroPython to decode using utf-8.