| name | mint |
| description | Guide for working with mint, an embedded development tool that assembles flash memory hex files from TOML layout files and data sources (Excel/JSON). Use this skill whenever a project uses or mentions mint / mint-cli, when you encounter .toml layout files that define memory blocks for firmware or flash, when you need to create or modify flash block definitions, set up mint in a build system or CI pipeline, or work with Excel/JSON data sources for embedded device configuration. Also trigger when you see references to building Intel HEX or Motorola S-Record files from structured layout definitions, or when a user mentions replacing a custom hex-generation script with a declarative tool. |
mint
mint builds binary flash images (Intel HEX or Motorola S-Record) from a declarative TOML layout file and an optional data source (Excel workbook or JSON). Each layout describes one or more memory blocks — contiguous regions that map to C structs stored at known flash addresses. mint resolves data values, enforces types, computes CRCs, pads to size, and emits the output file. It can also generate matching C headers and ABI fingerprints without a data source.
Install: cargo install mint-cli or via nix flake.
Layout file anatomy
A layout file has three levels: global config, per-block headers, and per-block data fields.
[mint]
endianness = "little"
[mint.checksum.crc32]
polynomial = 0x04C11DB7
start = 0xFFFFFFFF
xor_out = 0xFFFFFFFF
ref_in = true
ref_out = true
[mint.const]
default_voltage = 3.3
fw_name = "BootloaderV2"
[myblock.header]
start_address = 0x8000
length = 0x1000
padding = 0xFF
[myblock.data]
schema = { fingerprint = true, type = "u64" }
device.id = { value = 0x1234, type = "u32" }
device.name = { name = "DeviceName", type = "u8", size = 16 }
voltage = { const = "default_voltage", type = "f32" }
checksum = { checksum = "crc32", type = "u32" }
Block names and every [myblock.data] path segment must be valid, non-keyword C identifiers matching [_a-zA-Z][_a-zA-Z0-9]*. Use unquoted dotted keys or nested tables for nested structs; quoted dotted keys are rejected as flat fields.
Multiple blocks can live in one file. Build specific blocks with layout.toml#blockname.
Generate C typedefs from the same selectors with mint header layout.toml -o layout.h or mint header layout.toml#blockname -o block.h. The layout remains the source of truth for nested structs, storage types, arrays, and bitmap macros.
Dotted paths mirror C struct nesting
The key device.info.version.major maps to block.device.info.version.major in the output — the same hierarchy as nested C structs. This is how mint knows field ordering and grouping.
Information to gather before writing a layout
When setting up mint for a project, these parameters need to be established. If replacing an existing system, many can be inferred from the codebase (struct definitions, linker scripts, existing hex generators). Always confirm with the user before proceeding.
From the hardware/firmware side:
- Endianness — little or big. Check target MCU architecture or existing byte-swap calls.
- Block addresses and sizes — from linker script, memory map, or flash layout documentation. Each block needs a
start_address and length.
- Padding byte — usually
0xFF (erased flash state) but confirm. Some platforms use 0x00.
- CRC algorithm — if blocks need integrity checks, you need the polynomial, initial value, XOR-out, and reflection settings. Check existing CRC routines or documentation.
- Struct layout — C header files defining the structs that live at each flash address. These become the
[block.data] fields.
From the data/build side:
- Which values are constants vs. configurable — one-off constants go as
value = ...; reusable constants go in [mint.const] and fields use const = "..."; configurable values use name = "..." to pull from a data source.
- Data source format — Excel workbook (typical for manufacturing/calibration workflows) or JSON (typical for CI pipelines that fetch or generate data).
- Variant names — the columns/keys that represent build variants (e.g., Default, Debug, Production). The
--variants flag controls fallback priority.
Scalar types
| Type | Width | Notes |
|---|
u8, u16, u32, u64 | 1–8 bytes | Unsigned integers |
i8, i16, i32, i64 | 1–8 bytes | Signed integers (two's complement) |
f32, f64 | 4, 8 bytes | IEEE 754 floats |
qI.F, uqI.F | 1–8 bytes | Binary fixed-point, width must be 8/16/32/64 bits |
Booleans use integer types: { value = true, type = "u8" } stores 1.
Fixed-point examples: uq8.8 (unsigned 16-bit), uq0.16 (unsigned 16-bit pure fraction), q7.8 (signed 16-bit), q15.16 (signed 32-bit). mint encodes them as round_ties_even(input * 2^F).
Field sources
Every field in [block.data] has a type and exactly one source. Sources are mutually exclusive.
Literal values (value)
device.id = { value = 0x1234, type = "u32" }
message = { value = "Hello", type = "u8", size = 16 }
ip_addr = { value = [192, 168, 1, 1], type = "u8", size = 4 }
Strings and arrays require size. Strings are UTF-8 encoded into the byte array.
Reusable constants (const)
[mint.const]
default_voltage = 3.3
fw_name = "BootloaderV2"
ip_addr = [192, 168, 1, 1]
[block.data]
voltage = { const = "default_voltage", type = "f32" }
message = { const = "fw_name", type = "u8", size = 16 }
ip_addr = { const = "ip_addr", type = "u8", size = 4 }
base = { const = "block.start_address", type = "u32" }
Const values use the same literal shapes and conversion rules as value. Each block automatically exposes <block>.start_address and <block>.length using block header values.
Data source lookup (name)
device.name = { name = "DeviceName", type = "u8", size = 16 }
version = { name = "Version", type = "u16" }
gain = { name = "VoltageGain", type = "uq8.8" }
coefficients = { name = "Coefficients", type = "f32", size = 4 }
matrix = { name = "Matrix", type = "i16", size = [2, 2] }
The name string must match a key in the data source. For arrays, size specifies dimensions — use size = N for 1D, size = [rows, cols] for 2D.
size vs SIZE: Lowercase size pads undersized data with the block's padding byte. Uppercase SIZE errors if the data source provides fewer elements than declared. Use SIZE when short data would indicate a real problem.
Bitmaps (bitmap)
Pack multiple named or literal values into a single integer field.
config.flags = { type = "u16", bitmap = [
{ bits = 1, name = "EnableDebug" },
{ bits = 3, value = 0 },
{ bits = 4, name = "RegionCode" },
{ bits = 8, value = 0 },
] }
Fields pack LSB-first. The total bits must equal the type's bit width (e.g., 16 for u16). Each bitmap sub-field can use name (data source) or value (literal). Signed types use two's complement for negative values.
Fixed-point types are not valid with bitmap.
Refs / pointers (ref)
Store the absolute address of another field within the same block.
table.entries = { name = "TableEntries", type = "u16", size = 32 }
table.count = { name = "TableCount", type = "u16" }
table_ptr = { ref = "table", type = "u32" }
count_ptr = { ref = "table.count", type = "u32" }
The ref target is a dotted path rooted at the block's data section and is validated before field values are emitted. Refs resolve to start_address + field_offset, which must fit the selected storage type. The type must be an unsigned integer (u16, u32, u64). Fixed-point types are not valid with ref. Forward and backward refs both work. Cross-block refs are not supported.
ABI fingerprints (fingerprint)
Store the containing block's ABI fingerprint or another block's fingerprint from the same layout:
[config.data]
schema = { fingerprint = true, type = "u64" }
[manifest.data]
config_schema = { fingerprint = "config", type = "u64" }
Fingerprint fields require u64 and cannot use size/SIZE. Fingerprints cover the nameless resolved ABI: endianness, types, dimensions, offsets, alignment, bitmap widths and ref topology. Names, values, producer choices (name, value or const), block addresses, allocated lengths and padding values do not contribute. A selected block is fully validated, while its fingerprint targets have only their ABI shapes resolved; unrelated siblings are not resolved. mint fingerprint layout.toml#config prints one bare 16-character lowercase value; mint fingerprint layout.toml fully validates the whole file and prints block fingerprint lines. Generated headers expose fingerprint fields as <BLOCK>_<FIELD>_FINGERPRINT macros.
Checksums (checksum)
Compute a CRC over all preceding bytes in the block and place the result inline.
[mint.checksum.crc32]
polynomial = 0x04C11DB7
start = 0xFFFFFFFF
xor_out = 0xFFFFFFFF
ref_in = true
ref_out = true
[block.data]
checksum = { checksum = "crc32", type = "u32" }
The checksum covers everything from the start of the block's data up to (but not including) the checksum field itself, including any alignment padding between fields. Type must be u32. Fixed-point types are not valid with checksum. The referenced name must match a [mint.checksum.<name>] config. Multiple checksum fields are resolved in order, so later checksums include earlier ones.
For cross-block CRC or non-CRC algorithms, use a separate hex post-processing tool.
Alignment
mint applies natural C aggregate alignment. Each integer or fixed-point leaf aligns to its storage width, f32 aligns to 4 bytes, and f64 aligns to 8 bytes. Each dotted-path branch aligns to the maximum alignment of its children, preserves parsed child order, and receives tail padding before the next sibling. The root data struct also receives tail padding, so its reserved size matches sizeof under this ABI. All gaps use the block's padding byte.
This means mint does not support packed structs. If the target C code uses __attribute__((packed)), #pragma pack(1), or similar, the TOML layout will produce different offsets than the firmware expects. There is no way to disable alignment in mint. If the firmware uses packed structs, this is a fundamental incompatibility — raise it with the user immediately.
Similarly, mint writes fields in declaration order and cannot reorder them. If the compiler performs struct field reordering (some do for optimization), the layout must match the compiler's actual output, not the source declaration order. When in doubt, check the compiled output or a map file.
Data sources
A data source is optional — layouts with only value fields build without one. You cannot combine multiple data sources in a single build.
Excel (--xlsx)
The workbook has a Main sheet (or specify --main-sheet) with this structure:
| Name | Default | Debug | Production |
|---|
| DeviceName | MyDevice | DebugDev | |
| Version | 1 | 2 | 1 |
| Counter | 1000 | 0 | 50000 |
| Coefficients | #DefaultCoefficients | #DebugCoefficients | |
| Matrix | #CalibrationMatrix | #CalibrationMatrix | |
- Name column: lookup keys matching layout
name fields
- Variant columns: one per build variant. First non-empty value in the
--variants priority chain wins.
- Array sheet refs: A cell value like
#DefaultCoefficients points to a separate sheet containing array data. First row is headers (ignored), values read row-by-row until an empty cell.
JSON (--json)
{
"Default": {
"DeviceName": "MyDevice",
"Version": 1,
"Counter": 1000,
"Coefficients": [1.0, 2.0, 3.0, 4.0],
"Matrix": [
[10, 20],
[30, 40]
]
},
"Debug": {
"DeviceName": "DebugDev",
"Version": 2
}
}
Top-level keys are variant names. Each contains an object of name:value pairs. Arrays are native JSON arrays. 2D arrays are arrays of arrays. Accepts a file path or inline JSON string.
Variant priority (--variants)
--variants Debug/Default means: look in Debug first, fall back to Default if the key is missing or null. The first non-empty value wins.
Name matching: The name field in the layout must exactly match a key in the data source. These are case-sensitive. When setting up a new data source, collect all name = "..." values from the layout and ensure each one exists in the source.
CLI quick reference
mint build layout.toml --xlsx data.xlsx --variants Default -o firmware.hex
mint build layout.toml#config layout.toml#data --xlsx data.xlsx --variants Default -o out.hex
mint header layout.toml -o layout.h
mint fingerprint layout.toml#config
mint fingerprint layout.toml
mint build layout.toml --json data.json --variants Debug/Default -o out.hex
mint build layout.toml --json '{"Default":{"DeviceName":"MyDevice","Version":1}}' --variants Default -o out.hex
--format hex
--format mot
--record-width 16
--strict
--stats
--quiet
--export-json report.json
Run mint --help for the full argument list.
Common patterns
Multiple blocks, one file: Define several [blockname.header] / [blockname.data] sections. Build all with mint build layout.toml or select with layout.toml#blockname.
Generated C header: Run mint header layout.toml -o layout.h. Dotted paths become nested structs, arrays use generated extent macros, named bitmap regions receive shift and mask macros, and fingerprint fields receive expected-value macros. Layout parsing guarantees valid block and field names; header generation rejects statically invalid selected layouts and generated-name collisions.
Multiple CRC configs: Define [mint.checksum.crc32] and [mint.checksum.crc32c] (or any names). Reference by name in checksum fields.
Constants + data source in one block: Mix value and name fields freely. Fields with value don't need a data source.
CI integration: mint build reads files and writes a hex file. Wire it into any build system as a custom command that depends on the layout and data files and produces the hex output.
Gotchas
- Bitmap bit sum: The total bits in a bitmap must exactly equal the type width. A
u16 bitmap needs exactly 16 bits across all sub-fields.
- 2D arrays must come from data source: You cannot inline a 2D array literal in TOML. Use a
name reference instead.
- Checksum type: Must be
u32. No other widths are supported.
- Ref type: Must be unsigned (
u16, u32, u64).
- Fingerprint type: Must be
u64; targets are true or another block in the same layout.
size/SIZE cannot combine with ref, checksum, fingerprint, or bitmap.
- Strict mode: Without
--strict, out-of-range integer values saturate and float-to-int casts truncate (e.g., 300 into u8 becomes 255, 1.5 into u8 becomes 1). Fixed-point values scale by 2^F, round ties-to-even, then clamp. With --strict, mint errors instead.
Further reference
For full annotated examples (layout + C struct + data source), read references/examples.md if this skill is installed as a directory; if you only have mint skill output, fetch it from github.com/tomrford/mint at crates/mint-cli/skill/mint/references/examples.md.
Online documentation: the mint repository's doc/ directory contains layout.md, sources.md, and cli.md with exhaustive detail on every option (github.com/tomrford/mint).