midbus (modbus) bootloader
Go to file
alexis 26f22fc141 Fix boot on ch32v1 2023-05-07 12:47:54 -06:00
ch32v Fix boot on ch32v1 2023-05-07 12:47:54 -06:00
inc Fix a few ch32v1 bugs 2023-04-23 19:06:01 -06:00
mbbl Return device info more usefully as a dict 2023-04-22 13:18:31 -06:00
src Adapt to midbus changes 2023-04-26 16:03:57 -06:00
.gitignore Make mbbl python module installable 2023-01-15 18:08:50 -07:00
README.md Add target information and mbbl_can_boot() 2023-04-17 20:16:39 -06:00
mbbl_build.h.in Silence some warnings 2023-01-16 23:32:29 -07:00
meson.build Support CH32V1 2023-04-16 14:38:59 -06:00
meson_options.txt Support CH32V1 2023-04-16 14:38:59 -06:00
requirements.txt Make mbbl python module installable 2023-01-15 18:08:50 -07:00
setup.cfg Make mbbl python module installable 2023-01-15 18:08:50 -07:00
setup.py Make mbbl python module installable 2023-01-15 18:08:50 -07:00



MBBL (ModBus BootLoader) is a bootloader for Modbus, using midbus.

Features include:

  • Read, erase, and write by page
  • Fuse read and write
  • CRC validation separate from modbus, allowing broadcast of pages to multiple devices followed by poll for CRC

MBBL client

A simple Python-based client for MBBL is provided.

usage: mbbl [options] device command ...

device is a serial port specification. It can be a device path under `/dev`,
or a comma-separated list of criteria. To list devices present and what
criteria can be used to match them, try `mbbl list`. An example:

              vid: 0x16c0
              pid: 0x05e1
              man: alexisvl.rocks
              prd: PDP-1
              ser: 266230508EED383BE339E339

            mbbl man=alexisvl.rocks,prd=PDP-1

Supported commands:

mbbl read [--start START] [--end END | --length LENGTH] [--file FILE]
     Read from memory, from START throughg END or LENGTH. By default, reads
     the entire suggested page range from the bootloader. If --file is given,
     the contents are written out as Intel Hex, otherwise they are displayed
     on the console.

mbbl write [--boot] [--(no-)verify] [--(no-)erase] FILE
     Write to flash. FILE is Intel Hex and will be written to the regions
     specified in the hex file. Both verify and erase options default to true.
     If --boot is given, the file will be booted after a successful verify.

mbbl erase [--start START] [--end END | --length LENGTH]
     Erase flash, from START through END or LENGTH. By default, erases the
     entire suggested page range from the bootloader.

mbbl read_fuses [--file FILE]
     Read the fuse section, writing it to a hex file or the console.

mbbl write_fuses file | hex:...
     Write the fuse section. Fuses can be sourced from a hex file, or from
     hexadecimal directly on the command line, prefixed with `hex:`, e.g.

     mbbl write_fuses hex:a500ff00ff00ff00ff00ff00ff00ff00

mbbl boot
mbbl reboot
     Boot the application, or reboot the target.

mbbl info
     Print descriptors received from the bootloader.

Register definitions

Protocol version: 0x0102

Note that I am using hexadecimal register numbers here, with each register address block starting at 0000 and running to FFFF. This breaks with the modbus spec, but matches the conventions of midbus.

Protocol history

  • 0x0102: 64-character BUILD field was split into 32-character BUILD and 32-character TARGET. Backwards-compatible, will just drop TARGET due to NUL termination (no build IDs of exactly 32 characters ever existed).

PAGE_BUFFER: Holding 0000-7FFF: page buffer, up to 64KB

This range is the page buffer, through which all read and write operations work. Only as much space is defined as is needed for a given application. For byte-oriented platforms (most, of course), data is stored little-endian unless CAPABILITIES indicates otherwise.

PAGE_ADDR: Holding A000-A001: page address

This range holds the starting address to which the next command applies. The address is written in little endian. A single write-multiple may be used to fill PAGE_ADDR, PAGE_CRC, and COMMAND, triggering an operation.

PAGE_CRC: Holding A002: page checksum

For write operations, the expected checksum of the contents of PAGE_BUFFER should be written here, using the MODBUS RTU checksum algorithm. Page reads will generate a CRC, which will be accessible here.

COMMAND: Holding A003: command register to trigger operations

This is a bitfield register holding operation commands, as follows;

15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
|  |  |  |  |^^^^  |^^^^^^^^^^^^^^^^^^^^^^^^^^^
|  |  |  |  |      |
|  |  |  |  |      |_______________ COMMAND_KEY
|  |  |  |  |
|  |  |  |  |______________________ MULTI_PAGE
|  |  |  |
|  |  |  |_________________________ ERASE_FIRST
|  |  |
|  |  |____________________________ VERIFY
|  |
|  |_______________________________ TOGGLE
|__________________________________ reserved, set to 0
  • TOGGLE: Must alternate between command invocations, to ensure a command is never falsely retriggered. On the first command, you must read this register to get the initial state so it can be toggled.
  • VERIFY: After writing a page, verify it
  • ERASE_FIRST: On writes, erase the page first
  • MULTI_PAGE: If set to zero, a single page is written or read. If set to 1, 2, or 3, then either 2, 4, or 8 pages will be written/read. The number of pages that can be written in one operation varies by implementation and can be queried from the information descriptor registers defined below. Writing more data at once can reduce overhead and, if using compression, improve compression ratio. Does not affect operations other than PAGE_WRITE and PAGE_READ.
  • COMMAND_KEY: One of the keys to trigger a command:
    • 0x000: NOP: Does nothing, successfully.
    • 0x011: PAGE_ERASE: Erase the page specified by PAGE_ADDR.
    • 0x012: PAGE_WRITE: Write the contents of PAGE_BUFFER to PAGE_ADDR. If ERASE\_FIRST is not set, the results are implementation (hardware) defined.
    • 0x013: PAGE_READ: Dump the contents of PAGE_ADDR into PAGE_BUFFER.
    • 0x014: CRC: Compute a checksum of the memory from PAGE_ADDR to the last byte stored little-endian in PAGE_BUFFER. The CRC is returned in the PAGE_CRC register.
    • 0x021: PAGE_ERASE_MULTIPLE: Erase the number of pages specified by PAGE_BUFFER[0] plus one (up to 65536).
    • 0x032: FUSE_WRITE: Write fuses from PAGE_BUFFER into PAGE_ADDR. The specific behavior and definition of a "fuse", the number of locations written, and whether PAGE_ADDR is used, are hardware specific.
    • 0x033: FUSE_READ: Read fuses at PAGE_ADDR into PAGE_BUFFER. The specific behavior and definition of a "fuse", the number of locations written, and whether PAGE_ADDR is used, are hardware specific.
    • 0x1AA: BOOT: Boot into the application.
    • 0x155: REBOOT: Reboot the target.

USER: Holding F000-FFFF: application-defined

These registers may be used by a specific application for any purposes. ALL user registers should live here, do not define coils, discrete inputs, or input registers — only this range is guaranteed not to be used in future versions.

STATUS: Input 0000: bootloader status

This is a bitfield register holding status information, as follows:

15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
|  |                           |  |  |  |  |  |_____ BAD_COMMAND
|  |                           |  |  |  |  |
|  |                           |  |  |  |  |________ BAD_CHECKSUM
|  |                           |  |  |  |
|  |                           |  |  |  |___________ DRIVER_ERROR
|  |                           |  |  |
|  |                           |  |  |______________ HARDWARE_ERROR
|  |                           |  |
|  |                           |  |_________________ ADDRESS_ERROR
|  |                           |
|  |                           |____________________ VERIFY_ERROR
|  |
|  |________________________________________________ OK
|___________________________________________________ BUSY
  • BAD_COMMAND: The last command given was invalid, either due to an unrecognized command key, invalid option bits, command not listed in CAPABILITIES, or incorrect TOGGLE.
  • DRIVER_ERROR: The hardware driver reported a software error, likely due to a detail explained in "Hardware-specific definitions", below.
  • HARDWARE_ERROR: The flash hardware flagged an error.
  • ADDRESS_ERROR: An operation was attempted to an invalid address.
  • VERIFY_ERROR: The VERIFY bit was set on a PAGE_WRITE command and it failed to verify.
  • OK: Operation completed successfully.
  • BUSY: An asynchronous operation is in progress.

When a command is received, one of two operations occurs immediately, before any further modbus operations are executed:

  • If the command is invalid, the STATUS register is set to just BAD_COMMAND with all other bits clear.
  • If the command is valid, the STATUS register is set to BUSY with all other bits clear.

OUT_SIZE: Input 0001: output size

Contains the length of data present in PAGE_BUFFER after a read. Particularly useful for fuse reads (which do not always return full pages) and for compression.

MAGIC: Input 0010-0013: bootloader magic number

Contains a 64-bit magic number that identifies the bootloader. The magic number for midbus-bootloader is {0x3732, 0xFF2C, 0xFB8A, 0xC576}.

PROTOCOL: Input 0014: bootloader protocol version

Contains the version number of the bootloader protocol. New features that do not break old ones, for instance by adding new command keys, repurposing reserved bits, or adding new registers, will increment the low 8 bits. Breaking changes will increment the upper 8 bits. Always validate this register against your feature set!

CAPABILITIES: Input 0015: bootloader capabilities and properties

15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
^^^^^^^^^^^^^^^^^^^^  |     |  |  |  |  |  |  |
|                     |  r  |  |  |  |  |  |  |____ READ
|                     |  e  |  |  |  |  |  |
|                     |  s  |  |  |  |  |  |_______ WRITE
|                     |  e  |  |  |  |  |
|                     |  r  |  |  |  |  |__________ ERASE
|                     |  v  |  |  |  |
|                     |  e  |  |  |  |_____________ FUSE_READ
|                     |  d  |  |  |
|                     |     |  |  |________________ FUSE_WRITE
|                     |     |  |
|                     |     |  |___________________ BOOT
|                     |     |
|                     |     |______________________ REBOOT
|                     |
|                     |____________________________ BIG_ENDIAN
|__________________________________________________ reserved

Enumerates the capabilities and properties of this bootloader.

  • READ: can read pages
  • WRITE: can write pages
  • ERASE: can erase pages
  • FUSE_READ: can read fuses
  • FUSE_WRITE: can write fuses
  • BOOT: can boot the application on command
  • REBOOT: can reboot the target
  • BIG_ENDIAN: page buffer is big endian

BUILD: Input 0016-0025: build identifier string

Contains an ASCII string, up to 32 characters, identifying the build, hardware platform, and version. Generally starts with mbbl-.

TARGET: Input 0026-0045: target identifier string

Contains an ASCII string, up to 64 characters, identifying the specific target. Determined entirely by the implementer, and may be empty. Strongly recommended to begin with an originator ID (possibly a domain you own?) followed by a forward slash (/).

This field was added in protocol revision 0102, but these registers also exist and are guaranteed empty in protocol 0101 builds.

PAGE_SIZE: Input 0060: memory page size

Contains the size of a page in bytes.

MULTI_PAGE: Input 0061: number of pages supported in one operation

Contains the number of pages that can be written in a MULTI_PAGE operation, either 1, 2, 4, or 8.

PAGE_RANGE_START: Input 0062-0063: first valid page address

Contains the first valid page number, in little endian.

PAGE_RANGE_END: Input 0064-0065: last valid page address

Contains the last valid page number, in little endian. Note that there is no guarantee that every page from PAGE_RANGE_START through PAGE_RANGE_END will be accessible; a device with memory holes will trigger STATUS.ADDRESS_ERROR when they are accessed.

FUSE_RANGE_START: Input 0066-0067: first valid fuse address

Contains the first valid fuse address, in little endian. Note that the fuse commands do not accept addresses; they work on the entire block. This address is a reference for the bootloader client, so it can detect when a block of content is fuses rather than flash.

FUSE_RANGE_END: Input 0068-0069: last valid fuse address

Contains the last valid fuse address, in little endian.

OPER_TIMEOUT: Input 006A: flash operation timeout

For targets not supporting asynchronous operation of modbus and flash, the maximum time it may take a device to respond to a modbus message that was received correctly. Note that the only operation that should be performed while a command executes is a read of the STATUS register. If the device is unresponsive for longer than OPER_TIMEOUT, it can be assumed to be busy, and the read can be retried.

Hardware-specific definitions


Supports CH32V3, CH32V1

The "fuse" range contains the user option bytes. MBBL will compute the inverted check bytes for you, so only the noninverted bytes are relevant in your fuse setting.

Pages written to the flash mirror region at 0x0 are automatically translated up to 0x08000000.