Provisioning serial numbers on the line
The prototype works. Then the first small batch comes off the line and every board is identical: same firmware, no serial number, no keys, the debug port wide open. The mobile app cannot tell two units apart, the cloud side has nothing to authenticate, and the question that was easy to defer is now blocking shipment: what gets written into each board, in what order, and how do we prove it happened?
This is the provisioning step, and it belongs in the design, not in a hurried script the week before assembly. The three microcontroller families we see most often, STM32, Nordic nRF52 and Espressif ESP32, each give you a chip identifier, a place for per-unit data, and a way to close the debug port. They differ in what is reversible. Get that wrong and the cost is a tray of parts you cannot recover.
What “unique” actually means on each chip
Every family ships a factory identifier, and the first mistake is assuming it is the same kind of thing on each.
STM32 exposes a 96-bit unique device ID in read-only system memory. ST states it is unique for every STM32 ever made, and its fields are wafer coordinates, wafer number and lot number rather than a random value (ST: how to obtain and use the STM32 96-bit UID). That has two consequences. It is a fine serial number, and ST says so. It is a poor secret, because units from the same reel have IDs that are close together, and the value is readable by anything that can execute code on the part. The same article notes the guarantee applies only to families where ST tests the UID in production, so check the reference manual for the exact part before building a serial scheme on it.
nRF52 has a 64-bit DEVICEID and a 48-bit DEVICEADDR in the factory information registers, both written by Nordic and not erasable. The address is what the radio uses as its default Bluetooth device address. Nordic’s support engineers have told developers on DevZone that a value guaranteed unique across your product needs your own numbering in the production flow, since the factory ID is generated rather than sequenced. Treat that as forum guidance, and treat DEVICEID as an identifier you can read, not a serial you can print on the label.
ESP32 stores a factory MAC address in eFuse block 0 and reserves part of block 3 for a custom MAC or user data (ESP-IDF eFuse manager). The factory MAC is unique enough for a Wi-Fi network but, again, is not a secret.
The pattern is the same across all three: the chip ID identifies the die. Your product serial identifies the unit, and those are different things. A board reworked with a new MCU keeps its serial and gets a new chip ID. A label printer needs a number a human can read and a support desk can look up. So the serial is something you assign and write, and the chip ID is something you record against it in the build log.
Where per-unit data lives
Once you accept that you are writing per-unit data, the question is where.
On STM32 the practical choices are a reserved flash page that the application never erases, or the option bytes’ user data fields on families that have them. A dedicated page at the end of flash is the simplest to reason about: the linker excludes it, the bootloader never touches it, and the programmer writes it after the image.
On nRF52 the UICR has CUSTOMER registers meant for exactly this, and a bootloader such as MCUboot or the Nordic DFU bootloader will not erase them during an update. The catch that trips people on DevZone is ordering: a full-chip erase clears UICR too, so the serial must be written after the erase and after any hex file that also contains UICR content, or the second write silently wipes the first. Write the serial last, then read it back.
On ESP32 the intended mechanism is a factory NVS partition, and Espressif ships a mass manufacturing utility that takes a configuration CSV plus one row per device and emits one partition image per unit, optionally encrypted with per-device keys (ESP-IDF mass manufacturing utility). The utility only builds the images. Flashing them is a separate step with esptool or your own fixture software, which means your line needs a way to pick the right image for the board on the fixture. A barcode on the panel or a serial read back from the chip both work. A pile of binaries named by row number does not.
Calibration data, if the product has any, goes in the same place as the serial and by the same rule: written by the fixture, never by the application, and read back before the board leaves.
Keys and the debug port: know what you cannot undo
This is where the families diverge sharply, and where a wrong step is permanent.
| Family | Mechanism | Reversible? |
|---|---|---|
| STM32 | Readout protection level 1 | Yes, regressing to level 0 mass-erases flash |
| STM32 | Readout protection level 2 | No, and the system bootloader is disabled |
| nRF52 | Access port protection | Yes, via ERASEALL, which wipes flash and UICR |
| ESP32 | eFuse bits (flash encryption, secure boot) | No, a burned bit never clears |
On STM32, level 1 blocks flash readout over the debug port and can be undone, at the price of a mass erase; ST’s own procedure notes the erase can take a while and must not be interrupted by a reset. Level 2 locks the option bytes permanently and disables the built-in system bootloader, so ST advises applying it only at final production, and only if the product has its own in-application update path — the kind of path our note on designing firmware updates with recovery and rollback plans for (ST: changing readout protection on STM32F4). The same article warns that an attached ST-LINK, which connects at power-up, can interfere with entering and leaving protection. On a fixture, that means sequencing the debug probe deliberately, not leaving it attached and hoping.
On nRF52, newer silicon revisions changed the rules. Older parts left the debug port open unless firmware or the programmer wrote the protection register. Nordic’s revised parts default to protected after an erase, and the firmware itself has to opt in to leaving debug open (Nordic blog on the improved APPROTECT). The practical trap is a build compiled against an older SDK that never writes the register: the board comes off the fixture locked, and unlocking it is a full erase. Confirm the SDK and MDK versions the build uses against the silicon revision on the reel, and put a “debug port state” read into the fixture’s pass criteria.
On ESP32, protection is eFuses, and every burn is one-way. Flash encryption has a development mode that leaves the encryption counter unprotected so you can turn it back off, and a release mode that write-protects it. Espressif’s documentation states development-mode encryption can be disabled only a limited number of times per chip, and that power must not be interrupted during the first encryption boot or the flash contents are corrupted (ESP-IDF flash encryption). Secure boot v2 burns a digest of your public key into eFuse, after which every future image must be signed with the matching key, and Espressif recommends generating production keys with a standard tool rather than the convenience script. Lose that key and the product can never be updated again. The fixture should enable release mode as its last act, after every read-back has passed, and the fixture’s power should be the last thing anyone touches during that boot.
On key material itself, the rule that survives all three families is: no shared secret across units. Espressif says flash encryption is only as strong as the key and warns explicitly against reusing a pre-generated key on several devices. Per-unit keys are more work for the line, which is why they belong in the fixture design and the CSV pipeline rather than in someone’s shell history.
Planning the programming step
A provisioning step that works on the bench and fails on the line usually fails in sequencing or in evidence — the same fixture that presses pogo pins onto the test points has to own this sequence. The checks a careful engineer builds in:
- Erase, program, provision, protect, in that order. Anything written before an erase is gone. Anything protected before a read-back cannot be inspected.
- Read back everything. Serial, calibration, key digest, protection state. Log it against the chip ID and the panel position. This log is what you will search when a unit comes back.
- Test the recovery path on a scrap board first. Regress an STM32 from level 1. ERASEALL an nRF52 and re-provision it. Confirm an ESP32 in development mode can still be reflashed. Do this before the first real batch, so the first time you need the path is not on a customer’s unit.
- Keep debug open in development builds and closed in release builds by configuration, not by hand. A build flag that is visible in the version string is easier to audit than a checklist.
- Decide what a field-returned board needs to reveal. If support needs to read a log over the debug port, level 2 or release-mode eFuses have already made that impossible. Choose the protection level for the whole product life, not just for shipment day.
The time cost is a day or two of fixture software and a written sequence. The risk of skipping it is a batch that is either unprotected or unrecoverable, and you find out which after the reflow oven.
We design the provisioning sequence alongside the test fixture and the firmware, so the serial, keys and debug lock are written and verified in one pass on the same fixture that checks the board. Send us the target MCU, your update strategy and what support needs to read back later, and we will propose the sequence and its recovery path.
Want the serial, keys and debug lock written and verified on the same fixture that tests the board? We design that step with the fixture.
Tools for this topic
- ESP32 browser flasherFlash a .bin over USB from the browser. It reads the chip and the file first and refuses firmware built for a different chip, revision or flash size.
- STM32 browser flasherFlash a .dfu, .hex or .bin over USB DFU from the browser. It reads the chip’s flash map and identity first and refuses firmware whose bytes or vector table do not belong on it. Never touches option bytes.
- Browser serial terminalConnect a USB board and read or send serial text and hex from the browser. Baud rate and format, DTR/RTS reset profiles for ESP32 and Arduino, timestamps and a saved log. Nothing leaves your computer.
Related notes
- When a chip revision changes but the part number doesn'tHow to read a die-revision PCN, find the silicon revision in firmware and on the package, and decide what to requalify before new parts ship.
- Bare-metal or RTOS on a small MCUHow to decide when a superloop is enough, what an RTOS actually costs in RAM and debugging effort, and the checks to run before you commit to one.
- Firmware updates with recovery and rollbackPlan firmware updates around recovery: image validation, rollback, application confirmation, compatible data, and failure tests on shipped hardware.