Files
HaloKeymind/ota_nrf52_bootloader_update/index.html
T
2026-09-15 05:34:40 +00:00

3310 lines
60 KiB
HTML

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Documentation for the open source MeshCore firmware">
<link rel="canonical" href="https://mikecarper.github.io/MeshCore/ota_nrf52_bootloader_update/">
<link rel="prev" href="../ota_meshtower_v2_sdcard/">
<link rel="next" href="../ota_nrf52_qspi/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.7">
<title>nRF52 bootloader updates over LoRa - MeshCore Docs</title>
<link rel="stylesheet" href="../assets/stylesheets/main.ec1eaa64.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../_stylesheets/extra.css">
<link rel="stylesheet" href="../_stylesheets/firmware_picker.css">
<link rel="stylesheet" href="../_stylesheets/telemetry_decoder.css">
<link rel="stylesheet" href="../_stylesheets/filter_tool.css">
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#nrf52-bootloader-updates-over-lora" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="MeshCore Docs" class="md-header__button md-logo" aria-label="MeshCore Docs" data-md-component="logo">
<img src="../_assets/meshcore.svg" alt="logo">
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
MeshCore Docs
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
nRF52 bootloader updates over LoRa
</span>
</div>
</div>
</div>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/mikecarper/MeshCore/" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
mikecarper/MeshCore
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="MeshCore Docs" class="md-nav__button md-logo" aria-label="MeshCore Docs" data-md-component="logo">
<img src="../_assets/meshcore.svg" alt="logo">
</a>
MeshCore Docs
</label>
<div class="md-nav__source">
<a href="https://github.com/mikecarper/MeshCore/" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
mikecarper/MeshCore
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Introduction
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../WiFi/" class="md-nav__link">
<span class="md-ellipsis">
WiFi and MQTT by Firmware Type
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../cad_scan_validation/" class="md-nav__link">
<span class="md-ellipsis">
SX1262 channel-scanning hardware results
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../carrier_wave/" class="md-nav__link">
<span class="md-ellipsis">
Carrier-wave RF checks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../carrier_wave_validation/" class="md-nav__link">
<span class="md-ellipsis">
CW and CW2 validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../cli_build_matrix/" class="md-nav__link">
<span class="md-ellipsis">
CLI Availability by Firmware Build
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../cli_command_availability/" class="md-nav__link">
<span class="md-ellipsis">
CLI Command Availability Matrix
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../cli_commands/" class="md-nav__link">
<span class="md-ellipsis">
CLI Commands
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../cli_settings_audit/" class="md-nav__link">
<span class="md-ellipsis">
CLI setting dispatch audit
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../companion_contact_cache/" class="md-nav__link">
<span class="md-ellipsis">
Full Companion contact caches
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../companion_contact_cache_results/" class="md-nav__link">
<span class="md-ellipsis">
Contact-cache and NimBLE RAM qualification
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../companion_offline_queue/" class="md-nav__link">
<span class="md-ellipsis">
Companion Offline Message Queue
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../companion_protocol/" class="md-nav__link">
<span class="md-ellipsis">
Companion Protocol
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../companion_radio_full/" class="md-nav__link">
<span class="md-ellipsis">
Full Companion
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../companion_usb_ascii_validation/" class="md-nav__link">
<span class="md-ellipsis">
USB Companion ASCII default validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../companion_usb_client_validation/" class="md-nav__link">
<span class="md-ellipsis">
USB Companion client compatibility
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../docs/" class="md-nav__link">
<span class="md-ellipsis">
Local Documentation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../double_frequency_v4_validation/" class="md-nav__link">
<span class="md-ellipsis">
Double center-frequency write: V4 transmitter / XIAO receiver
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../esp32_memory_budget/" class="md-nav__link">
<span class="md-ellipsis">
Classic ESP32 image memory budget
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../espnow_bridge_setup/" class="md-nav__link">
<span class="md-ellipsis">
ESP-NOW bridge: simple setup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../faq/" class="md-nav__link">
<span class="md-ellipsis">
Frequently Asked Questions
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../filter_tool/" class="md-nav__link">
<span class="md-ellipsis">
Filter policy playground
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../firmware_memory_budget/" class="md-nav__link">
<span class="md-ellipsis">
Firmware memory checks
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../firmware_picker/" class="md-nav__link">
<span class="md-ellipsis">
Firmware picker
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../flood_filtering/" class="md-nav__link">
<span class="md-ellipsis">
Flood Filtering and Moderation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../four_fixed_tx_dwell_validation/" class="md-nav__link">
<span class="md-ellipsis">
Four fixed transmitters: longer dwell repeats
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../four_fixed_tx_single_pass_validation/" class="md-nav__link">
<span class="md-ellipsis">
Four fixed transmitters / fast single-pass receiver
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../full_companion_features/" class="md-nav__link">
<span class="md-ellipsis">
Full Companion: turn features on and off
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../full_companion_usb_switcher/" class="md-nav__link">
<span class="md-ellipsis">
Companion USB CLI and binary switcher
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../full_usb_logging_validation/" class="md-nav__link">
<span class="md-ellipsis">
Full USB logging repair — 1.17.1.6
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../gps_tracking/" class="md-nav__link">
<span class="md-ellipsis">
GPS Tracking
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../halo_keymind_settings/" class="md-nav__link">
<span class="md-ellipsis">
Halo and Keymind Branch Settings
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../hardware_validation_bluetooth_stealth_2026-09-07/" class="md-nav__link">
<span class="md-ellipsis">
XIAO Bluetooth stealth hardware validation - 2026-09-07
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../hardware_validation_checklist/" class="md-nav__link">
<span class="md-ellipsis">
Hardware validation checklist
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../home_text_spacing/" class="md-nav__link">
<span class="md-ellipsis">
Home-screen text spacing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../host_cli_service/" class="md-nav__link">
<span class="md-ellipsis">
LoRa CLI Host Service
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../kiss_modem_protocol/" class="md-nav__link">
<span class="md-ellipsis">
MeshCore KISS Modem Protocol
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../lora_ota_automation/" class="md-nav__link">
<span class="md-ellipsis">
Scripted LoRa OTA from start to finish
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../mbedtls-tls-footprint/" class="md-nav__link">
<span class="md-ellipsis">
Shrinking the per-connection TLS footprint on non-PSRAM observers
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../mixed_scan_validation/" class="md-nav__link">
<span class="md-ellipsis">
Mixed-bandwidth two-channel reception test
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../mixed_sf_bw_validation/" class="md-nav__link">
<span class="md-ellipsis">
Equal-symbol-time mixed SF/BW, four-channel test
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../nimble_companion_trial/" class="md-nav__link">
<span class="md-ellipsis">
ESP32-S3 NimBLE Full Companion trial
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../nimble_companion_trial_results/" class="md-nav__link">
<span class="md-ellipsis">
ESP32-S3 NimBLE trial results, 2026-09-08
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../nrf52-usb-ready-fix/" class="md-nav__link">
<span class="md-ellipsis">
nRF52 USB READY hang
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../nrf52_companion_storage_recovery/" class="md-nav__link">
<span class="md-ellipsis">
nRF52 Companion automatic ExtraFS recovery
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../nrf52_power_management/" class="md-nav__link">
<span class="md-ellipsis">
nRF52 Power Management
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../number_allocations/" class="md-nav__link">
<span class="md-ellipsis">
Number Allocations
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../ota_easy/" class="md-nav__link">
<span class="md-ellipsis">
Easy firmware updates over LoRa
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../ota_meshtower_v2_sdcard/" class="md-nav__link">
<span class="md-ellipsis">
MeshTower V2 microSD LoRa OTA
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
nRF52 bootloader updates over LoRa
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
nRF52 bootloader updates over LoRa
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#storage-layouts" class="md-nav__link">
<span class="md-ellipsis">
Storage layouts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#internal-flash-target-inventory" class="md-nav__link">
<span class="md-ellipsis">
Internal-flash target inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#embedded-continuity-and-version-policy" class="md-nav__link">
<span class="md-ellipsis">
Embedded continuity and version policy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#explicit-install-workflow" class="md-nav__link">
<span class="md-ellipsis">
Explicit install workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#failure-behavior" class="md-nav__link">
<span class="md-ellipsis">
Failure behavior
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../ota_nrf52_qspi/" class="md-nav__link">
<span class="md-ellipsis">
nRF52 repeater LoRa OTA with external QSPI
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../ota_protocol/" class="md-nav__link">
<span class="md-ellipsis">
MeshCore OTA - .mota container &amp; LoRa protocol
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../ota_user_guide/" class="md-nav__link">
<span class="md-ellipsis">
Updating your node over the air (OTA) - user guide
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../packet_format/" class="md-nav__link">
<span class="md-ellipsis">
Packet Format
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../pair_4p6_300us_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF7/62.5 + SF8/500: 4.6 slow chirps, 0.3 ms reserve
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../pair_preamble32_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF7/62.5 + SF8/500, fast switching and preamble 32
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../payloads/" class="md-nav__link">
<span class="md-ellipsis">
Payload Format
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../pr7_review_validation/" class="md-nav__link">
<span class="md-ellipsis">
PR #7 review and validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../preamble_detection_investigation/" class="md-nav__link">
<span class="md-ellipsis">
Off-channel preamble investigation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../qr_codes/" class="md-nav__link">
<span class="md-ellipsis">
QR Codes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../radio_chirp_math_validation/" class="md-nav__link">
<span class="md-ellipsis">
Shared radio/radio2 chirp timing and preamble warnings
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../radio_dwell_policy_validation/" class="md-nav__link">
<span class="md-ellipsis">
Automatic dwell policy: 4.6 chirps on both profiles
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../radio_profile_switch_validation/" class="md-nav__link">
<span class="md-ellipsis">
SX1262 profile-switch validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../radio_profiles/" class="md-nav__link">
<span class="md-ellipsis">
Two LoRa profiles on one radio
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../radio_profiles_validation/" class="md-nav__link">
<span class="md-ellipsis">
Dual-profile receive validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../radio_receive_calibration/" class="md-nav__link">
<span class="md-ellipsis">
Radio receive calibration and recovery
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../rak3401_mota_chain/" class="md-nav__link">
<span class="md-ellipsis">
RAK3401 1W repeater compact LoRa update chain
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../role_feature_switches/" class="md-nav__link">
<span class="md-ellipsis">
Feature switches by role — 1.17.1.5 USA Cascade
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../s3_memory_soak_validation/" class="md-nav__link">
<span class="md-ellipsis">
ESP32-S3 OTA memory experiment
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../separated_radio_modulation_cache_validation/" class="md-nav__link">
<span class="md-ellipsis">
Separated radios, normal XIAO RX gain, and unchanged-modulation timing
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf10_10_vs_100hz_full_sample_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF10/125 full samples: 10 Hz versus 100 Hz first-pass detour
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf10_10hz_cr_detour_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF10/125: tiny first-pass frequency offset and coding-rate detour
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf10_full_retune_twice_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF10/125: apply the full radio settings twice per hop
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf10_nrf52_double_write_settling_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF10 / 125 kHz, XIAO nRF52 TX, double write, 1 ms delay grid
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf10_rollback_failure_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF10/125 rollback and wider-spacing failure isolation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf125_settling_limits_validation/" class="md-nav__link">
<span class="md-ellipsis">
Descending SF10..SF5 / 125 kHz settling limits
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf5_250_dwell_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF5 / 250 kHz: four-channel dwell sweep
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf6_125_settling_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF6/125 four-channel post-switch settling sweep
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf6_5p1_trace_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF6 / 125 kHz: four channels at 5.1 chirps
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf6_channel_scan_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF6 / 125 kHz channel-count test
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../sf8_5p1_trace_validation/" class="md-nav__link">
<span class="md-ellipsis">
SF8 / 125 kHz: four-channel comparison at 5.1 chirps
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../spiffs_regular_file_reads/" class="md-nav__link">
<span class="md-ellipsis">
SPIFFS regular-file reads and login replay state
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../stats_binary_frames/" class="md-nav__link">
<span class="md-ellipsis">
Stats Binary Frame Structures
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../telemetry_decoder/" class="md-nav__link">
<span class="md-ellipsis">
Telemetry decoder
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../terminal_chat_cli/" class="md-nav__link">
<span class="md-ellipsis">
Terminal Chat CLI
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../usb_serial_backpressure/" class="md-nav__link">
<span class="md-ellipsis">
Native USB backpressure and radio liveness
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../v4_pixel5_font_trial/" class="md-nav__link">
<span class="md-ellipsis">
Small-screen message fonts
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_88" >
<label class="md-nav__link" for="__nav_88" id="__nav_88_label" tabindex="0">
<span class="md-ellipsis">
Releases
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_88_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_88">
<span class="md-nav__icon md-icon"></span>
Releases
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../releases/1.17.1.5-doc-audit/" class="md-nav__link">
<span class="md-ellipsis">
Markdown audit — 1.17.1.5 USA Cascade
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../releases/1.17.1.5/" class="md-nav__link">
<span class="md-ellipsis">
MeshCore 1.17.1.5 Dev - USA Cascade
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../releases/1.17.1.6/" class="md-nav__link">
<span class="md-ellipsis">
MeshCore 1.17.1.6 Dev - USA Cascade
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#storage-layouts" class="md-nav__link">
<span class="md-ellipsis">
Storage layouts
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#internal-flash-target-inventory" class="md-nav__link">
<span class="md-ellipsis">
Internal-flash target inventory
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#embedded-continuity-and-version-policy" class="md-nav__link">
<span class="md-ellipsis">
Embedded continuity and version policy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#explicit-install-workflow" class="md-nav__link">
<span class="md-ellipsis">
Explicit install workflow
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#failure-behavior" class="md-nav__link">
<span class="md-ellipsis">
Failure behavior
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<a href="https://github.com/mikecarper/MeshCore/edit/keymindCascade/docs/ota_nrf52_bootloader_update.md" title="Edit this page" class="md-content__button md-icon" rel="edit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10 20H6V4h7v5h5v3.1l2-2V8l-6-6H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h4zm10.2-7c.1 0 .3.1.4.2l1.3 1.3c.2.2.2.6 0 .8l-1 1-2.1-2.1 1-1c.1-.1.2-.2.4-.2m0 3.9L14.1 23H12v-2.1l6.1-6.1z"/></svg>
</a>
<h1 id="nrf52-bootloader-updates-over-lora">nRF52 bootloader updates over LoRa</h1>
<p>For <strong>1.17.1.5</strong>, use the exact board/storage profile from
<a href="https://github.com/mikecarper/Adafruit_nRF52_Bootloader_OTAFIX/releases/tag/0.11.0-OTAFIX2.4.6">OTAFIX 2.4.6</a>
for nRF52 OTAFIX installations. New internal-flash hybrid receivers require
its 64 KiB retained-RAM handoff; QSPI and microSD targets require their own
matching bootloader profiles. Earlier preview versions mentioned below
describe compatibility/migration history, not the current recommended download.
Full Companion is a MOTA source and normally updates itself over USB.</p>
<p>Selected nRF52840 repeater LoRa-OTA builds can replace their matching OTAFIX
bootloader without replacing the running application. This is a privileged
maintenance path, not a normal firmware update. A stock bootloader must first
be replaced with the exact ABI-3 self-update-capable OTAFIX build over USB/BLE
DFU or SWD. MeshTower SD also requires the BLM2 retained-auth version to be
provisioned locally before either application or bootloader OTA.</p>
<h2 id="storage-layouts">Storage layouts</h2>
<table>
<thead>
<tr>
<th>Layout</th>
<th style="text-align: right;">Application limit</th>
<th style="text-align: right;">Staged boot package</th>
<th style="text-align: right;">Work area</th>
<th>Handoff / capability</th>
</tr>
</thead>
<tbody>
<tr>
<td>XIAO-module raw QSPI</td>
<td style="text-align: right;">below <code>0xE0000</code></td>
<td style="text-align: right;">external QSPI offset 0</td>
<td style="text-align: right;">dedicated internal <code>0xE0000..0xEA000</code> scratch</td>
<td>source <code>0x51</code>, flags <code>0x0E</code></td>
</tr>
<tr>
<td>Qualified internal-flash target</td>
<td style="text-align: right;">normal <code>0xED000</code> flash limit; top 64 KiB SRAM reserved for application-delta staging</td>
<td style="text-align: right;">shared internal slot, exact start <code>0xE2000</code></td>
<td style="text-align: right;">the same eleven-page slot; no second flash reservation</td>
<td>source <code>0xED</code>, flags <code>0x0A</code></td>
</tr>
<tr>
<td>MeshTower V2 microSD</td>
<td style="text-align: right;">normal <code>0xED000</code> limit</td>
<td style="text-align: right;">contiguous <code>/meshcore-ota.mota</code></td>
<td style="text-align: right;">dynamic internal <code>0xE0000..0xEA000</code> scratch; live image must end by <code>0xE0000</code></td>
<td>source <code>0x53</code>, flags <code>0x09</code></td>
</tr>
</tbody>
</table>
<p>The exact SD target is
<code>Heltec_tower_v2_sdcard_repeater_lora_ota_no_external_sensors</code>. Its normal
application FULL and delta updates continue to use the SD file without the
bootloader-update scratch restriction. For a bootloader package only, both
MeshCore and OTAFIX require a hash-valid live <code>EndF</code> proving the complete
running image ends at or below <code>0xE0000</code>. If boot settings carry a nonzero app
bank CRC, the recorded bank size must also cover that full EndF-inclusive
image and stop by <code>0xE0000</code>; erased or explicitly CRC-disabled settings remain
valid. OTAFIX then copies the verified 40
KiB payload from SD into <code>0xE0000..0xEA000</code> and uses the MBR copy operation to
replace <code>0xF4000..0xFE000</code>. The application linker remains at <code>0xED000</code>; a
future application extending above <code>0xE0000</code> can still use application mOTA
but must update its bootloader through local DFU/SWD.</p>
<p>The removable SD authorization is fail-closed for both application and
bootloader packages. MeshCore directly authenticates one exact signed
manifest, requires the streamed copy to remain byte-identical, verifies all
leaves/payload/image bytes, and computes a normalized full-container SHA-256 in
that same pass (<code>APRV</code> bytes are treated as zero). After syncing <code>APRV</code>, it
publishes a 72-byte <code>MOTASDA2</code> record in reset-retained RAM at <code>0x20006008</code>.
That record binds purpose/format, first LBA, sector count, total length, card
sector count, and the normalized digest. OTAFIX copies, validates, and clears
the record before SD access. A card swap/mutation or a power cycle therefore
fails closed; there is no general sector-1 ownership inference.</p>
<p>For fmt3, MeshCore also writes and readback-verifies a 64-byte <code>MOTASDBL</code> token
at <code>0xE0000</code>. That token contains the exact container length and the exact
authenticated signed manifest <code>image_hash</code>. OTAFIX requires the same hash in
the parsed manifest, streamed payload, and final scratch image. The page is
consumed as scratch during success and is not permanently reserved.</p>
<p>The first BLM2-capable MeshTower bootloader cannot be delivered to preview.12
through the retained-RAM protocol that preview.12 predates. MeshCore does not
write a raw-sector compatibility record because a blank sector is not proof of
ownership. Upgrade preview.12 through USB/BLE DFU or SWD. Both fmt2 application
and fmt3 bootloader OTA then require the installed BLM2 metadata to match the
live SoftDevice/application layout; neither MeshCore nor OTAFIX uses sector 1.</p>
<p>The internal path does not change the application's <code>0xED000</code> flash limit or
permanently set aside separate app-OTA, boot-package, and flash-scratch
regions. Qualified applications do use a dedicated linker that reserves the
top 64 KiB of SRAM for hybrid application-delta staging and a retained
authorization record. That SRAM is never part of a bootloader-update package.
The ordinary bottom-aligned internal flash store holds one container prefix at
a time: either an application delta or the complete bootloader package.</p>
<p>The exact bootloader container is 41,330 bytes: 365 bytes of signed mOTA
metadata, a 40 KiB payload, and the five-byte trailer. Below the normal
<code>0xED000</code> store ceiling it bottom-aligns at <code>0xE2000</code>. Admission requires a
hash-valid live <code>EndF</code> proving the current application, including its trailer,
ends at or before <code>0xE2000</code>. OTAFIX then reads each source window before
erasing and compacts the payload forward in place to the page-aligned raw range
<code>0xE2000..0xEC000</code>; it verifies every page and the whole image before asking the
MBR to copy that image over <code>0xF4000..0xFE000</code>.</p>
<p>An ordinary application delta can be smaller or larger than this eleven-page
shape. On the qualified profile, a delta larger than one page uses the minimum
page-aligned flash prefix ending at <code>0xED000</code> and keeps up to 64 KiB of its
logical tail in reset-retained SRAM; its detools workspace must stop at the
actual flash-prefix start. A one-page-or-smaller delta stays wholly in flash.
In either case the reconstructed application must stop below <code>0xED000</code>. The
two package kinds are mutually exclusive because they use the same flash
store, and a bootloader package never uses the volatile SRAM suffix.</p>
<p>For internal-self-update builds, an absent or corrupt live <code>EndF</code> disables
<strong>all</strong> internal staging before the first erase. The older 608 KiB rescue
estimate is unsafe when a normally linked application may extend to
<code>0xED000</code>. Builds without this feature retain the legacy rescue behavior.</p>
<h2 id="internal-flash-target-inventory">Internal-flash target inventory</h2>
<p>The release builder consumes the allowlist in
<code>tools/mota/nrf52_internal_bootloader_targets.txt</code>. Ten names are also literal
PlatformIO environments and are enabled automatically when built directly.
The other eleven are release aliases assembled from a base environment plus
the lean OTA overlay; build them through <code>build.sh</code>, which passes the same
allowlist decision to the common pre-build guard. These lean repeater/bridge
targets have no OTA-owned SD/QSPI store and have an exact curated OTAFIX
manifest identity:</p>
<table>
<thead>
<tr>
<th>Build target(s)</th>
<th>Installed OTAFIX identity</th>
<th style="text-align: right;">Boot target ID</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Heltec_tower_v2_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A0071 / TOWER_V2_OTA</code></td>
<td style="text-align: right;"><code>1150F50E</code></td>
</tr>
<tr>
<td>T096 lean repeater and RS232 bridge</td>
<td><code>239A0071 / T096_DFU</code></td>
<td style="text-align: right;"><code>42354C85</code></td>
</tr>
<tr>
<td><code>Heltec_t1_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A0071 / T1_DFU</code></td>
<td style="text-align: right;"><code>FC556FFC</code></td>
</tr>
<tr>
<td>T114 display and without-display lean repeaters</td>
<td><code>239A0071 / T114_DFU</code></td>
<td style="text-align: right;"><code>0C3F2902</code></td>
</tr>
<tr>
<td><code>Mesh_pocket_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A0071 / MESH_POCKET_OTA</code></td>
<td style="text-align: right;"><code>059277F4</code></td>
</tr>
<tr>
<td><code>KeepteenLT1_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A00B3 / KeepteenLT1_OTA</code></td>
<td style="text-align: right;"><code>DB2E7B51</code></td>
</tr>
<tr>
<td><code>Minewsemi_me25ls01_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A0029 / MX25_DFU</code></td>
<td style="text-align: right;"><code>026AA982</code></td>
</tr>
<tr>
<td><code>ProMicro_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A00B3 / PROM_DFU</code></td>
<td style="text-align: right;"><code>AF79E8CC</code></td>
</tr>
<tr>
<td><code>t1000e_repeater_lora_ota_no_external_sensors</code></td>
<td><code>28860057 / T1KE_DFU</code></td>
<td style="text-align: right;"><code>E6F5F03F</code></td>
</tr>
<tr>
<td><code>ThinkNode_M3_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A00DA / TNM3_DFU</code></td>
<td style="text-align: right;"><code>0CA41DB2</code></td>
</tr>
<tr>
<td><code>RAK_3401_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A0029 / 3401_DFU</code></td>
<td style="text-align: right;"><code>23818A80</code></td>
</tr>
<tr>
<td>RAK4631 lean repeater and both lean RS232 bridges</td>
<td><code>239A0029 / 4631_DFU</code></td>
<td style="text-align: right;"><code>2D0DF000</code></td>
</tr>
<tr>
<td>GAT562 30S/Tracker Pro/EVB Pro lean targets</td>
<td><code>239A0029 / GAT562_DFU</code></td>
<td style="text-align: right;"><code>D50D2D44</code></td>
</tr>
<tr>
<td><code>R1Neo_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A0029 / 4631_DFU</code></td>
<td style="text-align: right;"><code>2D0DF000</code></td>
</tr>
<tr>
<td><code>RAK_WisMesh_Tag_repeater_lora_ota_no_external_sensors</code></td>
<td><code>239A0029 / RTAG_DFU</code></td>
<td style="text-align: right;"><code>C72E9C9C</code></td>
</tr>
</tbody>
</table>
<p>Board IDs are not globally unique. For generic targets, the signed hardware ID
is the exact NUL-padded 32-byte value
<code>NRF_BL_&lt;BOARD_ID&gt;_&lt;DEVICE_NAME&gt;</code>. The wire target is the little-endian first
four SHA-256 bytes of all 32 padded bytes. The installed and candidate embedded
manifest pairs must match exactly. XIAO retains its deployed
<code>XIAO_BL_28860044</code> / <code>XIAO_BL_28860045</code> identity and raw board-ID target.</p>
<p>The Python reference builder and release tooling audit these boot targets for
duplicates and collisions with application target IDs. Generic image parsing
can inspect a future canonical identity, but signing/building a package fails
until that exact identity is in the qualified inventory.</p>
<p>Earlier GAT562 installations may report the RAK-compatible <code>4631_DFU</code>
identity. The installed and candidate identity pair must match exactly, so a
remote bootloader update cannot migrate one of those devices to <code>GAT562_DFU</code>.
Provision the GAT562-specific OTAFIX bootloader once through USB/BLE DFU or SWD;
subsequent signed bootloader mOTA updates use target <code>D50D2D44</code>.</p>
<h2 id="embedded-continuity-and-version-policy">Embedded continuity and version policy</h2>
<p>Every remotely supplied successor retains the legacy 44-byte CRC-valid <code>BLMF</code>
v1 record for identity continuity and diagnostics. Immediately after that
record is a 32-byte <code>BLM2</code>/<code>SOFT</code> extension. The complete 76-byte envelope is
fixed at the final raw-image offset <code>0x9FB4</code>; relocated candidates are refused,
while installed legacy-v1 discovery remains a generic diagnostic scan. The extension carries the actual packed
bootloader version, SoftDevice family and FWID, application base, layout ABI,
and zero compatibility/reserved fields; the legacy whole-image CRC covers the
extension too. Scanners first count CRC-valid 44-byte base manifests, then
interpret continuity only after one base identity remains. A malformed claimed
extension therefore cannot make a duplicate identity disappear, and a sole
half-present extension is rejected rather than treated as legacy.</p>
<p>MeshCore requires the candidate extension, exact equality between its embedded
version and the signed outer mOTA version, and exact agreement with the running
SoftDevice family/FWID/application layout. Qualified internal/QSPI paths may
bootstrap a CRC-valid installed legacy-v1 image once; the SD path never does
and requires local BLM2 provisioning. Once the installed bootloader has BLM2
metadata, the candidate version must be strictly greater. Preview low bytes are <code>1..254</code>, a
stable release uses <code>0xFF</code>, and low-byte zero or all-ones values are rejected.
There is no remote rollback/migration override; intentional rollback uses
USB/BLE DFU or SWD.</p>
<p>The legacy <code>no_external_sensors</code> profiles trim selected optional environmental
and ranging drivers to preserve flash headroom; they do not generally disable
the I2C bus. Reduced RAK3401 and RAK4631 recipes retain INA219, INA226, INA260,
and INA3221 voltage/current monitors. Those are entries in the optional sensor
table, not the complete set of I2C consumers: the SSD1306 OLED, supported
autodiscovered RTCs, and RAK12500 GPS remain separate I2C peripherals where the
exact board recipe enables them. RAK3401 retains its compatible RAK12500 I2C
and RAK12501/L76K UART GPS paths in sensor slot A. The plain RAK4631 repeater
and Serial2 bridge retain GPS. Its RAK12501 path uses Serial1; the explicitly
compiled Serial1 RS232 bridge therefore omits the combined GPS provider,
including RAK12500, even though RAK12500 itself is I2C rather than UART.</p>
<p>The firmware-configured INA3221 and RAK12500 addresses are both <code>0x42</code>. They
cannot coexist on the same bus at those addresses. Leave RAK12500 at <code>0x42</code>,
strap INA3221 A0 to SCL for <code>0x43</code>, and use firmware built with
<code>-DTELEM_INA3221_ADDRESS=0x43</code> when both modules are installed.</p>
<p>Boards with onboard external flash are not silently redirected to internal
staging. Mesh Solar, Nano G2 Ultra, T-Impulse Plus, ThinkNode M8, T-Echo
Lite/Card, MeshTracker X1, and Wio WM1110 have board-specific QSPI hardware and
need a separately matched QSPI path where available. Full Companions, other
SD/QSPI/ExtraFS roles, Ethernet roles, source-only roles, and unqualified
full-sensor roles are excluded from the internal-flash inventory. The exact
MeshTower V2 SD role above is separately qualified for its SD path.</p>
<p>This internal layout is limited to nRF52840 devices with 1 MiB internal flash,
the exact S140 v6/v7 map, and the 40 KiB boot region at
<code>0xF4000..0xFE000</code>. nRF52833 and smaller nRF52 parts cannot provide that map
plus a non-overlapping 41,330-byte live staging slot, so configuration fails
closed instead of selecting smaller or overlapping geometry.</p>
<h2 id="explicit-install-workflow">Explicit install workflow</h2>
<p>The <code>tools/lora_ota/lora_ota.sh</code> and <code>.ps1</code> runners can perform the discovery,
transfer, verification and radio cleanup for a bootloader mOTA with
<code>--no-install</code>. They detect format 3 automatically and check the destination's
bootloader-specific identity/capabilities. They do not install it: the final
MID/hash confirmation below remains an explicit operator action. A mixed
application/bootloader ZIP requires <code>--zip-member</code>.</p>
<p>Check the installed identity and capability marker:</p>
<pre><code class="language-text">ota bootloader
</code></pre>
<p>The response must show a CRC-valid exact identity, ABI 3 or newer, both FULL
and INPLACE application codecs (<code>codecs=0x5</code>), and exact flags <code>0x09</code> for
MeshTower V2 SD, <code>0x0A</code> for internal shared storage, or <code>0x0E</code> for XIAO QSPI. A
bootloader row is visible in <code>ota ls</code>, but it is never autofetched or
autoinstalled. Fetch its exact MID, then explicitly arm it:</p>
<pre><code class="language-text">ota pull &lt;MID8&gt; flash
# wait for ota status to report the download ready
ota bootloader
ota bootloader install &lt;MID8&gt; &lt;HASH16&gt;
</code></pre>
<p>Copy both confirmation values from the second <code>ota bootloader</code> response.
Ordinary <code>ota install</code> rejects a bootloader package; the bootloader command
rejects an application package. The FULL-codec exception exists only for that
manual bootloader MID. Ordinary application FULL remains disabled on an
internal single-slot node, bootloader autofetch remains off, and a partial
bootloader package is not automatically resumed after an application reboot.</p>
<p>Before writing <code>APRV</code>, the application authenticates and authorizes the
package: exact v3 geometry, trusted Ed25519 signer, signed/embedded identity,
one unambiguous capability marker, embedded CRC, sane vectors, complete
Merkle/payload/image hashes, storage-specific safe live placement, and the
typed MID/hash confirmation. The manifest root and target inside the received
manifest must also equal the MID/target that opened the fetch before any store
is allocated. On SD, <code>APRV</code>, the retained geometry/container authorization,
and the internal signed-image-hash token are published in that order before
the reset trigger. OTAFIX consumes that application-written authorization and
then
independently rechecks the safety/integrity subset: strict v3 structure,
canonical identity/capabilities, vectors, full payload SHA, embedded manifest
CRC, the applicable live <code>EndF</code>/bank-settings no-overlap geometry, the SD token
binding where applicable, scratch readback, and final
copy hash. It does not re-run Ed25519, the signer allowlist, Merkle leaves/root,
or the typed operator confirmation. Success is reported as <code>blup:C8</code>.</p>
<h2 id="failure-behavior">Failure behavior</h2>
<p>The feature fails closed when the MCU/map is wrong, required valid <code>EndF</code>
headroom is insufficient, an unsupported external/ExtraFS role owns the target,
the exact installed capability marker is absent or ambiguous, identity cannot be derived
unambiguously, or any package check fails. Before the storage-specific
scratch/copy step, the application and bootloader are unchanged. OTAFIX
consumes the trigger and authorization before its first destructive action, so
an interrupted operation cannot automatically retry a partly consumed
package. On SD, the retained-RAM authorization is zero-consumed before media
access; <code>APRV</code> and the fmt3 flash token may persist but are inert without a new
explicit authenticated re-arm and reset trigger. No SD raw-sector handoff is
created or consumed.
Use USB/BLE DFU or SWD for initial provisioning and local recovery.</p>
<p>For XIAO and ordinary external-QSPI details, see
<a href="../ota_nrf52_qspi/">nRF52 repeater LoRa OTA with external QSPI</a>.</p>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["content.action.edit", "content.code.copy", "search.highlight", "search.suggest"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.d7400e89.min.js"></script>
<script src="../_javascript/firmware_picker.js"></script>
<script src="../_javascript/telemetry_decoder.js"></script>
<script src="../_javascript/filter_tool.js"></script>
</body>
</html>