View this page on MeshCore Docs.
MeshTower V2 microSD LoRa OTA
Heltec_tower_v2_sdcard_repeater_lora_ota_no_external_sensors is the one
canonical MeshTower V2 repeater target. Its SD OTA target ID 0A9DBBF0,
hardware ID Heltec_tower_v2, application layout, and artifact names remain
unchanged. The required Full and Reduced sensor choices use that same target.
Existing SD users need no configuration change.
The build aliases Heltec_tower_v2_repeater and
Heltec_tower_v2_repeater_lora_ota_no_external_sensors resolve to this primary
target. An installed internal-only image has a different OTA identity and
bootloader/layout contract: migrate it once with the matching combined OTAFIX
profile and application over local USB/BLE DFU or SWD. An alias does not make
that migration a same-target LoRa update.
Automatic storage selection
The onboard microSD socket remains primary. A mountable card accepts full application images, in-place application deltas, and explicitly selected signed v3 bootloader packages. After verification, the SD-aware OTAFIX bootloader reads the staged file and programs the application or bootloader.
With a missing or unmountable card, the same application can select internal
flash for signed application in-place deltas only. The signer must be trusted
by the node, as on the SD application path. This fallback requires the
published combined MeshTower SD/internal profile in
OTAFIX 2.4.11:
the privileged storage profile must remain exactly 09 (SD|BOOT_UPDATE),
with a separate positive MOTASTOR optional-application storage record of
02 (STAGE_CEILING). A missing record or an internal-only bootloader does
not authorize fallback. Full images, bootloader packages, SD archiving, and
automatic own-image offering remain unavailable on internal storage.
Selection is deferred until OTA use so card probing does not delay USB setup.
The selected backend stays pinned through the transfer, including a paused or
failed transfer. Removing a card produces a storage failure; it does not move
the partial download into internal flash. Use ota cancel before selecting
storage again after changing the card. The fallback uses internal flash only;
it does not reserve or use the 64 KiB APPHYBRID retained-RAM arena.
The pin assignment follows the Heltec MeshTower V2 partial reference circuit:
| Signal | nRF52840 pin | Arduino pin number |
|---|---|---|
| SD CS | P1.00 | 32 |
| SD MOSI | P1.01 | 33 |
| SD SCK | P0.06 | 6 |
| SD MISO | P0.26 | 26 |
The SD socket uses its own SPI peripheral, so card traffic does not change the LoRa radio pinout.
Card requirements
Use a FAT16, FAT32, or exFAT card accepted by the bundled SdFat version. The
normal update path imposes no additional MBR/sector-1 layout requirement;
filesystem layouts SdFat cannot mount are still rejected. MeshCore creates
/meshcore-ota.mota as a contiguous file and passes its exact sector range to
OTAFIX in reset-retained MCU RAM.
MeshCore never reads or writes raw sector 1 and does not infer ownership from blank card sectors. Both application and bootloader OTA require a locally provisioned BLM2-capable bootloader that understands the retained-RAM record. Preview.12 must first be upgraded over USB/BLE DFU or SWD.
Capacity and update types
When SD is selected, the card removes the internal-flash staging limit. The
.mota container may be much larger than the old internal staging gap, and
either a full image or an in-place delta may be downloaded. The installed firmware itself must still fit
the nRF52840 application region below InternalFS (ending at 0xED000); SD
storage does not increase the MCU's executable flash.
For this S140 v6 target, the maximum application image including its EndF
trailer is 0xC7000 bytes (815,104 bytes). To package a full self-update:
motatool build --fw ./Heltec_tower_v2_sdcard-new.hex \
--sign ./trusted-signer.key --out-dir ./motas
motatool verify ./motas/*.mota
A delta uses the exact installed image as its base. The normal 0x98000
workspace remains compatible. If either image is larger than that legacy
limit, build the patch with the SD target's larger workspace:
motatool build \
--base ./Heltec_tower_v2_sdcard-running.hex \
--fw ./Heltec_tower_v2_sdcard-new.hex \
--patch-type in-place \
--inplace-memory 0xC7000 \
--sign ./trusted-signer.key \
--out-dir ./motas
motatool verify ./motas/*.mota
The internal fallback has a smaller flash staging gap and in-place workspace. A delta generated for the larger SD workspace can be rejected even though it updates the same application and target. Keep a usable card inserted for that package, or generate an exact-base delta that fits the fallback's available workspace and staging capacity. The firmware refuses an oversized package; it does not switch backends during its download.
An SD download is resumable because the partial .mota stays on the card.
Once it reaches ready, ota install performs the final verification,
publishes a one-reset authorization record, and reboots. SD application
installation requires a valid signature from a key in the node's allowlist.
Keep the card inserted through the reboot and installation.
The BLM2 retained-auth SD-aware bootloader is mandatory for application OTA.
ota install refuses to reboot if its continuity metadata does not match the
running S140 family/FWID/application layout or if its capability marker does
not advertise SD staging and the selected codec. Earlier internal-only
MeshTower firmware retains its old identity until the deliberate local
migration described above.
Signed bootloader update
Only the canonical SD target with SD selected exposes a usable privileged
LoRa bootloader-update path. Internal application fallback does not stage
bootloader packages. It requires an already installed exact-board ABI-3
OTAFIX bootloader whose one
unambiguous capability marker is exactly 0x09 (SD|BOOT_UPDATE) and whose
codec mask is 0x0005 (FULL|INPLACE). Requiring both application codecs
prevents a bootloader self-update from disabling either normal SD application
path. A stock bootloader still requires USB/BLE DFU or SWD. The sole remote
bootstrap is intentionally absent: preview.12 and any other legacy-v1 image
must be upgraded locally before either SD application or bootloader OTA.
The signed v3 container is exactly 41,330 bytes and carries a 40 KiB candidate
for the installed 239A0071 / TOWER_V2_OTA identity (boot target 1150F50E).
It uses the same contiguous /meshcore-ota.mota file as an application update.
GPREGRET 0x6B plus the distinct SD source marker 0x53 selects the bootloader
path; LoRa transport remains payload type 0x0C.
The application linker still ends at 0xED000, but OTAFIX needs
0xE0000..0xEA000 as temporary scratch while replacing itself. Before a boot
package can be downloaded or approved, MeshCore requires a hash-valid live
EndF proving the complete running image ends by 0xE0000; OTAFIX repeats
that no-overlap check before its first erase. An application extending above
that boundary can still receive normal FULL or delta application updates from
SD, but its bootloader must be updated locally.
MeshCore also checks the live boot settings before touching that scratch page.
Erased settings and a valid-app record with CRC disabled are allowed. When a
nonzero bank CRC is active, its recorded bank_0_size must cover the complete
EndF-inclusive running image and must end by 0xE0000; an undersized or
oversized record refuses the operation and requires local DFU/SWD.
Because the card is removable, approval is bound to the exact bytes that were
authenticated. For both fmt2 application and fmt3 bootloader packages,
MeshCore authenticates one exact signed manifest, requires every streamed
manifest byte to match it, and verifies the leaves, payload, and image. During
that same pass it hashes the entire container with only the mutable four-byte
APRV field normalized to zero. After writing and syncing APRV, MeshCore
publishes a 72-byte MOTASDA2 record at reset-retained RAM address
0x20006008. The record binds package purpose/format, exact LBA range, card
size, container length, and that normalized SHA-256. OTAFIX consumes and clears
the record before reading the card; changing the card or file can only fail.
A power loss erases the authorization and also fails closed.
For fmt3, MeshCore additionally writes and readback-verifies the temporary
64-byte MOTASDBL token at 0xE0000, binding the exact total and signed
manifest image_hash. OTAFIX requires the parsed manifest, streamed payload,
and final scratch image to match it. The token page becomes scratch during a
successful boot update and is not permanently reserved.
Every new candidate also carries a backward-compatible BLM2/SOFT
continuity extension next to its legacy embedded manifest. The complete
76-byte envelope is fixed at final raw-image offset 0x9FB4; a relocated copy
is not a valid candidate. Its embedded
bootloader version must equal the outer package version, match the runtime
SoftDevice family/FWID, application base, and layout ABI, and be strictly newer
than an installed BLM2 version. Preview values use low bytes 1..254 and a
stable release uses 0xFF; zero-preview and all-ones versions are invalid.
Remote bootloader rollback is not supported; use local DFU/SWD when rollback
or migration is intentional.
Bootloader packages are never autofetched or autoinstalled. Select and confirm one exact package manually:
ota ls
ota pull <MID8> flash
# wait for ota status to report the bootloader download ready
ota bootloader
ota bootloader install <MID8> <HASH16>
Copy the MID and first 16 image-hash hex digits from ota bootloader. Ordinary
ota install rejects this package, and the bootloader command rejects an
application package. Keep the SD card inserted through the reboot. A later
ota status value of blup:C8 reports a successful bootloader replacement.
After reboot, ota bootloader displays the installed board identity, CRC and
capabilities. Its mid and hash fields describe a currently staged download,
not the installed bootloader, so staged:none mid=- hash=- is not an install
failure. blrc:00 means no retained apply result is available; it does not by
itself prove either success or failure.
Older MeshCore application firmware can report get bootloader.ver as unknown
for OTAFIX 2.4.6 because this bootloader image omits the legacy UF2 Bootloader
text. The updated getter also reads the CRC-validated BLM2 version. For the
official signed MeshTower V2 SD 2.4.6 .mota, the installed identity is
TOWER_V2_OTA, CRC 5DACDB3D, ABI 3, and capabilities 09. Compare the exact
board/storage package when interpreting these values; the non-SD image differs.
SD card CLI
The SD-backed target provides these CLI commands:
set sdcard format [--force]
set sdcard erase [--force]
get sdcard
get sdcard *
get sdcard format
get sdcard erase
get sdcard free
get sdcard ls
get sdcard ls 2
get sdcard dir 3
format creates a new FAT16, FAT32, or exFAT filesystem according to card
size. erase first uses the card's raw media erase command and then formats it,
so a successful erase finishes with a usable filesystem. Both operations
destroy all data on the card and cancel a staged SD OTA download. If the
internal fallback slot is occupied, card maintenance is refused until an
explicit ota cancel; it does not abandon an internal download to format SD.
The firmware records successful format and erase completion times in RAM.
Repeating the same operation within five minutes is rejected unless --force
is present. Format and erase have independent cooldowns. Because erase also
formats the card, a successful erase updates both timestamps. The timestamps
reset when the device reboots. The get sdcard age queries report how long ago
each operation completed. get sdcard free reports used and free filesystem
space in human-readable binary units.
get sdcard ls and get sdcard dir recursively list files on the card, two
files per page. A bare command shows page 1; append a positive page number to
move through the remaining results. Each row includes the path and a compact
file size. The header reports the selected page, total pages, and total files.
Persistent OTA archive and seeder
With SD selected, automatic OTA archiving is on by default. While the temporary OTA radio is active, the node requests full catalogs from seeders and saves every complete mOTA it discovers, including firmware for other hardware targets and codecs that this node cannot install. Archive downloads use the same per-block Merkle proof checks as an install download, but archived images are never selected for local installation.
Completed containers are stored as /mota/<manifest-id>.mota. An interrupted
download remains /mota/<manifest-id>.part and resumes when that mOTA is seen
again. Completed files survive reboot, are enumerated on the first archive
access or TempRadio window, and are advertised and served directly from SD.
The SD target supports the protocol maximum served set: its own running
firmware plus up to 254 archived mOTAs.
Automatic capture preserves an 8 MiB free-space reserve for manual
/meshcore-ota.mota installation staging. It stops starting new archive files
when the next file would cross that reserve. Archive allocation first tries the
fast contiguous path and then falls back to an ordinary fragmented FAT file;
only the bootloader staging file requires contiguous sectors.
Preload many mOTAs from a computer
You can populate the archive much faster on a computer than over LoRa. Use only
complete, verified .mota containers. Do not copy firmware .bin, .hex,
.zip, or .part files into the archive.
Install the standalone motatool first if
it is not already available:
git clone https://github.com/vk496/motatool.git
cargo install --path ./motatool
The on-card filename is part of the archive index and has a strict format:
/mota/<merkle_root>.mota
<merkle_root> is the eight-hex-digit value printed by motatool inspect. It
is also the mOTA's four-byte manifest ID. The extension must be lowercase, the
file must be directly inside /mota, and descriptive release filenames are
not indexed. For example, if inspection reports:
merkle_root : ABCD1234
copy that container to:
/mota/abcd1234.mota
If two containers have the same Merkle root, they have the same protocol ID
and cannot both be present. Keep only the intended one. The current SD seeder
can index up to 254 archived files. A served file must use a logical block size
of at most 1024 bytes and contain at most 2048 blocks; check block_size and
block_count in motatool inspect when importing unusually large images.
Files outside that serve geometry are not counted or advertised. If a malformed
/mota/<id>.mota conflicts with a newly discovered valid image, the repeater
preserves the malformed file as <id>.bad through <id>.bad9 and downloads a
clean replacement instead of treating path existence as a valid cache hit.
To prepare and load the card:
- Format it as described under Card requirements. The
easiest route is to insert it in the repeater, run
set sdcard format, power the repeater off, and then move the card to the computer. Formatting destroys the existing card contents. - Mount the card on the computer and create a directory named
motaat the filesystem root. Do not use a nested directory such as/firmware/mota. - Run
motatool verify FILE.motafor every source file. Do not copy a file that reportsFAIL. - Run
motatool inspect FILE.mota, read itsmerkle_root, and copy the file to/mota/<lowercase-merkle-root>.motaon the card. - Flush pending writes, safely eject the card, power the repeater off, insert
the card, and boot
Heltec_tower_v2_sdcard_repeater_lora_ota_no_external_sensors.
On Linux or macOS, this Bash example verifies and imports every .mota from
./motas. Replace the example mount path before running it:
card_mount=/media/YOU/MESHCORE
mkdir -p "$card_mount/mota"
shopt -s nullglob
for image in ./motas/*.mota; do
motatool verify "$image" || exit 1
mid=$(motatool inspect "$image" |
awk '$1 == "merkle_root" { print tolower($3) }')
if [[ ! $mid =~ ^[0-9a-f]{8}$ ]]; then
echo "Could not read the Merkle root from: $image" >&2
exit 1
fi
destination="$card_mount/mota/$mid.mota"
if [[ -e $destination ]]; then
cmp -s "$image" "$destination" || {
echo "Different containers have the same ID: $mid" >&2
exit 1
}
else
cp "$image" "$destination"
fi
done
sync
On Windows, create E:\mota, inspect each source with motatool inspect, and
rename it to the reported lowercase root in the same way. Safely eject the
drive after all copies finish.
After boot, scan the archive and confirm the card contents from the repeater console:
get sdcard ls
get sdcard ls 2
ota cache
ota folder
ota cache should report the imported count. ota folder reports the current
served count; once OTA serving starts in TempRadio, that set also includes the
repeater's running firmware. If the files appear in get sdcard ls but not in
the served count, check their exact names, run motatool verify again, and
inspect their block geometry. It is fine to run ota cache off for a curated,
read-mostly archive: that disables capture of new mOTAs but continues serving
every valid file already on the card.
Serve the preloaded archive over TempRadio
LoRa OTA traffic exists only during an active temporary-radio window. The SD repeater, each receiving node, and every intermediate repeater in the path need overlapping windows on the same temporary channel. Use a frequency permitted for the node's configured region. This North American example uses the recommended fast OTA settings and a 120-minute window:
tempradio 909.950,250,5,5,120
Start the farthest receiving node first, then intermediate repeaters, and the
SD source last so their windows overlap for as long as possible. Before or
during the source window, ota cache makes the source scan and attach the SD
archive. Entry into TempRadio automatically triggers an OTA advertisement
burst; ota announce can send another advertisement immediately.
On a receiving OTA-capable node, allow a few seconds for catalog exchange, then run:
ota ls
ota ls 2
ota get <mid8> flash
ota status
Use ota ls 2, ota ls 3, and so on when the source advertises more than the
two rows that fit in one remote CLI reply. Each row includes a stable
eight-hex-digit manifest ID. Use that ID instead of a list number, because
asynchronous catalog refreshes can reorder rows between the list and pull
commands. A receiver retains the complete protocol catalog and verifies every
transferred block. It still applies its normal target, hardware, codec,
signature, and installation checks. The SD
repeater may advertise images for many hardware families; it never installs
those archive files merely because it serves them.
When a temporary window expires or a node reboots, it returns to its saved
radio settings and OTA transfer stops. The archive remains on the card. Start
another set of overlapping tempradio windows to resume an interrupted
download, or use synchronized tempradioat entries for a scheduled window.
Archive capture is lower priority than operator work. An explicit ota pull
to install storage or a host folder immediately takes the single receive slot;
the archive partial is checkpointed and resumes later. Existing cached files
can still answer peer requests while another archive file is downloading.
Use these commands to inspect or control automatic capture:
ota cache
ota cache on
ota cache off
ota config cache on
ota config cache off
The on/off choice is saved on the SD card. Turning capture off stops new
downloads but keeps serving files already cached. Formatting or erasing the
card removes both the archive and its off marker, so a newly formatted card
returns to the default-on setting. ota config sdseed on|off is also accepted
as an alias.