t1000 fixes

This commit is contained in:
liquidraver
2026-02-26 14:34:18 +01:00
parent 501d4ce459
commit ced260c8c3
17 changed files with 9211 additions and 49 deletions
+20 -12
View File
@@ -550,22 +550,30 @@ void ZephyrDataStore::saveContacts(DataStoreHost *host)
LOG_ERR("saveContacts: fs_open(%s) failed: %d", tmp_path, rc);
return;
}
/* Batch each contact into a single fs_write() to minimise LittleFS
* overhead. 12 individual writes per contact × hundreds of contacts
* was 2000+ fs_write() calls — each with CRC/metadata overhead.
* One write per contact cuts the time by ~60-70%. */
static constexpr size_t REC_SZ = 152; /* 32+32+1+1+1+4+1+4+64+4+4+4 */
uint8_t rec[REC_SZ];
uint32_t idx = 0;
ContactInfo c;
uint8_t unused = 0;
while (host->getContactForSave(idx, c)) {
if (fs_write(&file, c.id.pub_key, 32) != 32) break;
if (fs_write(&file, (uint8_t *)&c.name, 32) != 32) break;
if (fs_write(&file, &c.type, 1) != 1) break;
if (fs_write(&file, &c.flags, 1) != 1) break;
if (fs_write(&file, &unused, 1) != 1) break;
if (fs_write(&file, (uint8_t *)&c.sync_since, 4) != 4) break;
if (fs_write(&file, (uint8_t *)&c.out_path_len, 1) != 1) break;
if (fs_write(&file, (uint8_t *)&c.last_advert_timestamp, 4) != 4) break;
if (fs_write(&file, c.out_path, 64) != 64) break;
if (fs_write(&file, (uint8_t *)&c.lastmod, 4) != 4) break;
if (fs_write(&file, (uint8_t *)&c.gps_lat, 4) != 4) break;
if (fs_write(&file, (uint8_t *)&c.gps_lon, 4) != 4) break;
uint8_t *p = rec;
memcpy(p, c.id.pub_key, 32); p += 32;
memcpy(p, c.name, 32); p += 32;
*p++ = c.type;
*p++ = c.flags;
*p++ = unused;
memcpy(p, &c.sync_since, 4); p += 4;
*p++ = c.out_path_len;
memcpy(p, &c.last_advert_timestamp, 4); p += 4;
memcpy(p, c.out_path, 64); p += 64;
memcpy(p, &c.lastmod, 4); p += 4;
memcpy(p, &c.gps_lat, 4); p += 4;
memcpy(p, &c.gps_lon, 4); p += 4;
if (fs_write(&file, rec, REC_SZ) != (ssize_t)REC_SZ) break;
idx++;
}
int sync_rc = fs_sync(&file);
+1 -1
View File
@@ -544,7 +544,7 @@ void LoRaRadioBase::triggerNoiseFloorCalibrate(int threshold)
if (_noise_floor < -120) _noise_floor = -120;
if (_noise_floor > -50) _noise_floor = -50;
}
LOG_INF("noise_floor_cal: %d samples/%d total, floor=%d, took %lld ms",
LOG_DBG("noise_floor_cal: %d samples/%d total, floor=%d, took %lld ms",
count, NUM_NOISE_FLOOR_SAMPLES, _noise_floor, elapsed);
}
@@ -112,6 +112,11 @@ enum lr11xx_system_irq_e
LR11XX_SYSTEM_IRQ_CAD_DETECTED = ( 1 << 9 ),
LR11XX_SYSTEM_IRQ_TIMEOUT = ( 1 << 10 ),
LR11XX_SYSTEM_IRQ_LR_FHSS_INTRA_PKT_HOP = ( 1 << 11 ),
LR11XX_SYSTEM_IRQ_RTTOF_REQ_VALID = ( 1 << 14 ),
LR11XX_SYSTEM_IRQ_RTTOF_REQ_DISCARDED = ( 1 << 15 ),
LR11XX_SYSTEM_IRQ_RTTOF_RESP_DONE = ( 1 << 16 ),
LR11XX_SYSTEM_IRQ_RTTOF_EXCH_VALID = ( 1 << 17 ),
LR11XX_SYSTEM_IRQ_RTTOF_TIMEOUT = ( 1 << 18 ),
LR11XX_SYSTEM_IRQ_GNSS_SCAN_DONE = ( 1 << 19 ),
LR11XX_SYSTEM_IRQ_WIFI_SCAN_DONE = ( 1 << 20 ),
LR11XX_SYSTEM_IRQ_EOL = ( 1 << 21 ),
@@ -119,13 +124,18 @@ enum lr11xx_system_irq_e
LR11XX_SYSTEM_IRQ_ERROR = ( 1 << 23 ),
LR11XX_SYSTEM_IRQ_FSK_LEN_ERROR = ( 1 << 24 ),
LR11XX_SYSTEM_IRQ_FSK_ADDR_ERROR = ( 1 << 25 ),
LR11XX_SYSTEM_IRQ_LORA_RX_TIMESTAMP = ( 1 << 27 ), /* FW >= 0x0308 */
LR11XX_SYSTEM_IRQ_ALL_MASK =
LR11XX_SYSTEM_IRQ_TX_DONE | LR11XX_SYSTEM_IRQ_RX_DONE | LR11XX_SYSTEM_IRQ_PREAMBLE_DETECTED |
LR11XX_SYSTEM_IRQ_SYNC_WORD_HEADER_VALID | LR11XX_SYSTEM_IRQ_HEADER_ERROR | LR11XX_SYSTEM_IRQ_CRC_ERROR |
LR11XX_SYSTEM_IRQ_CAD_DONE | LR11XX_SYSTEM_IRQ_CAD_DETECTED | LR11XX_SYSTEM_IRQ_TIMEOUT |
LR11XX_SYSTEM_IRQ_LR_FHSS_INTRA_PKT_HOP | LR11XX_SYSTEM_IRQ_GNSS_SCAN_DONE | LR11XX_SYSTEM_IRQ_WIFI_SCAN_DONE |
LR11XX_SYSTEM_IRQ_LR_FHSS_INTRA_PKT_HOP | LR11XX_SYSTEM_IRQ_RTTOF_REQ_VALID |
LR11XX_SYSTEM_IRQ_RTTOF_REQ_DISCARDED | LR11XX_SYSTEM_IRQ_RTTOF_RESP_DONE |
LR11XX_SYSTEM_IRQ_RTTOF_EXCH_VALID | LR11XX_SYSTEM_IRQ_RTTOF_TIMEOUT |
LR11XX_SYSTEM_IRQ_GNSS_SCAN_DONE | LR11XX_SYSTEM_IRQ_WIFI_SCAN_DONE |
LR11XX_SYSTEM_IRQ_EOL | LR11XX_SYSTEM_IRQ_CMD_ERROR | LR11XX_SYSTEM_IRQ_ERROR |
LR11XX_SYSTEM_IRQ_FSK_LEN_ERROR | LR11XX_SYSTEM_IRQ_FSK_ADDR_ERROR,
LR11XX_SYSTEM_IRQ_FSK_LEN_ERROR | LR11XX_SYSTEM_IRQ_FSK_ADDR_ERROR |
LR11XX_SYSTEM_IRQ_LORA_RX_TIMESTAMP,
};
/**
+28 -4
View File
@@ -149,6 +149,7 @@ CompanionMesh::CompanionMesh(mesh::Radio &radio, mesh::MillisecondClock &ms, mes
_batt_cb = nullptr;
_radio_reconfig_cb = nullptr;
_pin_change_cb = nullptr;
_save_schedule_cb = nullptr;
_contact_iter_active = false;
_contact_iter_idx = 0;
_contact_iter_lastmod = 0;
@@ -275,10 +276,34 @@ void CompanionMesh::markChannelsDirty()
void CompanionMesh::flushDirtyContacts()
{
if (_dirty_contacts_expiry) {
LOG_INF("flushDirtyContacts: saving contacts (lazy write)");
_dirty_contacts_expiry = 0;
if (_save_schedule_cb) {
/* Offload flash I/O to system workqueue — main thread
* stays free to drain LoRa ring buffer / process BLE. */
LOG_INF("flushDirtyContacts: scheduling background save");
_save_schedule_cb();
} else {
/* No callback set — save synchronously (fallback) */
LOG_INF("flushDirtyContacts: saving contacts (sync)");
_store->saveContacts(this);
}
}
}
void CompanionMesh::flushAllSync()
{
/* Synchronous flush for reboot path — MUST complete before sys_reboot.
* Clears dirty flags so any pending background work is a no-op. */
if (_dirty_contacts_expiry) {
LOG_INF("flushAllSync: saving contacts");
_store->saveContacts(this);
_dirty_contacts_expiry = 0;
}
if (_dirty_channels_expiry) {
LOG_INF("flushAllSync: saving channels");
_store->saveChannels(this);
_dirty_channels_expiry = 0;
}
}
void CompanionMesh::flushDirtyChannels()
@@ -1891,9 +1916,8 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
case CMD_REBOOT:
if (len >= 7 && memcmp(&data[1], "reboot", 6) == 0) {
LOG_INF("Reboot requested");
/* Flush any pending lazy writes before reboot */
flushDirtyContacts();
flushDirtyChannels();
/* Synchronous flush — must complete before reboot */
flushAllSync();
sendPacketOk();
sys_reboot(SYS_REBOOT_COLD);
} else {
+17
View File
@@ -89,6 +89,9 @@ typedef void (*RadioReconfigureCallback)(void);
/* Callback for BLE PIN change */
typedef void (*PinChangeCallback)(uint32_t new_pin);
/* Callback for scheduling background save (called instead of blocking) */
typedef void (*SaveScheduleCallback)(void);
/**
* CompanionMesh: Application layer for ZephCore Companion device
*
@@ -138,6 +141,19 @@ public:
*/
void setPinChangeCallback(PinChangeCallback cb) { _pin_change_cb = cb; }
/**
* Set callback for scheduling background contact saves.
* When set, flushDirtyContacts() submits a work item instead of blocking.
*/
void setSaveScheduleCallback(SaveScheduleCallback cb) { _save_schedule_cb = cb; }
/**
* Synchronous flush saves contacts + channels to flash on the calling
* thread. Use ONLY before reboot / factory reset where we MUST block
* until the write completes.
*/
void flushAllSync();
/**
* Continue contact iteration (call each main loop iteration).
* Returns true if contacts are still being sent.
@@ -237,6 +253,7 @@ private:
GetBatteryCallback _batt_cb;
RadioReconfigureCallback _radio_reconfig_cb;
PinChangeCallback _pin_change_cb;
SaveScheduleCallback _save_schedule_cb;
/* Contact iteration state */
bool _contact_iter_active;
@@ -81,6 +81,7 @@ struct lr11xx_data {
/* Extension features (duty cycle, boost) */
bool rx_duty_cycle_enabled;
bool rx_boost_enabled;
bool rx_boost_applied; /* RX boost register written to hardware */
/* Deferred hardware init — heavy SPI/radio work runs on first config() */
bool hw_initialized;
@@ -185,8 +186,17 @@ static void lr11xx_hardware_reset(struct lr11xx_data *data,
uint16_t freq_mhz = data->modem_cfg.frequency / 1000000;
lr11xx_system_calibrate_image_in_mhz(ctx, freq_mhz - 2, freq_mhz + 2);
lr11xx_radio_set_rx_tx_fallback_mode(ctx, LR11XX_RADIO_FALLBACK_STDBY_RC);
lr11xx_radio_set_pkt_type(ctx, LR11XX_RADIO_PKT_TYPE_LORA);
lr11xx_system_clear_errors(ctx);
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
/* RX boost lost on hardware reset — flag it for re-apply in
* start_rx after modem config (where radio is fully configured). */
data->rx_boost_applied = false;
lr11xx_hal_enable_dio1_irq(&data->hal_ctx);
LOG_WRN("LR1110 recovered from hardware reset");
@@ -363,18 +373,31 @@ static void lr11xx_start_rx(struct lr11xx_data *data,
/* Apply modem config for RX */
lr11xx_apply_modem_config(data, cfg, false);
/* Start RX — continuous or duty cycle */
/* Apply RX boost — persistent register, only written once.
* Deferred from hw_init to here so radio is fully configured. */
if (data->rx_boost_enabled && !data->rx_boost_applied) {
lr11xx_radio_cfg_rx_boosted(ctx, true);
data->rx_boost_applied = true;
}
/* Start RX — continuous or duty cycle.
* 0xFFFFFF is the magic RTC-step value for continuous RX.
* Must use the raw RTC-step API set_rx() converts from ms,
* which overflows uint32_t and gives a ~131 s timeout instead. */
if (data->rx_duty_cycle_enabled) {
lr11xx_apply_rx_duty_cycle(data);
} else {
lr11xx_radio_set_rx(ctx, 0xFFFFFF);
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
}
/* SetRxBoosted (0x0227) is a persistent config — apply once on
* full start, survives subsequent SetRx calls. */
if (data->rx_boost_enabled) {
lr11xx_radio_cfg_rx_boosted(ctx, true);
}
/* LR1110 firmware sets CMD_ERROR IRQ flag on several write commands
* (SetModParams, SetSyncWord, SetRxBoosted, SetRx) across all
* tested FW versions (0x0307, 0x0401). The commands succeed
* status byte returns OK, BUSY deasserts normally, radio operates
* correctly. RadioLib has the same behavior but never notices
* because it doesn't read the IRQ register after write commands.
* Clear here so CMD_ERROR doesn't leak into the DIO1 handler. */
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
data->in_rx_mode = true;
data->tx_active = false;
@@ -386,34 +409,19 @@ static void lr11xx_start_rx(struct lr11xx_data *data,
* skip most of lr11xx_apply_modem_config.
* Full lr11xx_start_rx() kept for initial start and TXRX.
*
* Packet params (preamble, pld_len=255) MUST be re-set on every restart:
* the LR1110 can silently corrupt these registers after CRC/header errors
* and through CADRX transitions, causing missed packets especially when
* small and large packets are interleaved on the mesh. Arduino MeshCore
* applies the same workaround (CustomLR1110Wrapper::onSendFinished). */
* Packet params are NOT re-applied here they persist through SetRx.
* RadioLib (Arduino) also skips re-applying packet params on RX restart.
* Only TX changes pld_len, and TXRX goes through full start_rx(). */
static void lr11xx_restart_rx(struct lr11xx_data *data)
{
void *ctx = &data->hal_ctx;
struct lora_modem_config *mc = &data->modem_cfg;
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
/* Re-apply packet params — preamble + pld_len=255 for RX */
lr11xx_radio_pkt_params_lora_t pkt = {
.preamble_len_in_symb = mc->preamble_len,
.header_type = LR11XX_RADIO_LORA_PKT_EXPLICIT,
.pld_len_in_bytes = 255,
.crc = mc->packet_crc_disable ? LR11XX_RADIO_LORA_CRC_OFF
: LR11XX_RADIO_LORA_CRC_ON,
.iq = mc->iq_inverted ? LR11XX_RADIO_LORA_IQ_INVERTED
: LR11XX_RADIO_LORA_IQ_STANDARD,
};
lr11xx_radio_set_lora_pkt_params(ctx, &pkt);
if (data->rx_duty_cycle_enabled) {
lr11xx_apply_rx_duty_cycle(data);
} else {
lr11xx_radio_set_rx(ctx, 0xFFFFFF);
lr11xx_radio_set_rx_with_timeout_in_rtc_step(ctx, 0xFFFFFF);
}
/* RX boost persists through SetRx — no re-apply needed. */
@@ -433,12 +441,28 @@ static void lr11xx_dio1_work_handler(struct k_work *work)
k_mutex_lock(&data->spi_mutex, K_FOREVER);
/* Read IRQ status then clear ALL bits. Must use ALL_MASK because
* LR1110 triggers CMD_ERROR when ClearIrq is called with a mask
* that does NOT include the CMD_ERROR bit (all FW versions).
* By always clearing ALL, CMD_ERROR gets cleared as part of the
* operation. */
lr11xx_system_irq_mask_t irq;
lr11xx_system_get_and_clear_irq_status(ctx, &irq);
lr11xx_system_get_irq_status(ctx, &irq);
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
LOG_INF("DIO1 IRQ: 0x%08x tx=%d t=%lld", irq, data->tx_active,
k_uptime_get());
/* CMD_ERROR (bit 22) is expected — LR1110 firmware sets it on
* several write commands (SetModParams, SetSyncWord, SetRxBoosted,
* SetRx) as a benign side effect on all FW versions (0x0307, 0x0401).
* Commands succeed, radio operates correctly. RadioLib has the same
* behavior but never notices because it doesn't read IRQ after writes.
* ERROR (bit 23) indicates an actual hardware fault. */
if (irq & LR11XX_SYSTEM_IRQ_ERROR) {
LOG_WRN("IRQ hardware ERROR: 0x%08x", irq);
}
/* ── RX done ── */
if (irq & LR11XX_SYSTEM_IRQ_RX_DONE) {
lr11xx_radio_rx_buffer_status_t rx_stat;
@@ -800,13 +824,27 @@ void lr11xx_set_rx_boost(const struct device *dev, bool enable)
{
struct lr11xx_data *data = dev->data;
/* Skip if already in the desired state — SetRxBoosted is a
* persistent register, redundant calls are wasteful. */
if (data->rx_boost_enabled == enable) {
return;
}
data->rx_boost_enabled = enable;
LOG_INF("RX boost %s", enable ? "enabled" : "disabled");
if (data->in_rx_mode) {
if (data->in_rx_mode && data->configured) {
/* Radio is fully configured — safe to apply immediately */
k_mutex_lock(&data->spi_mutex, K_FOREVER);
lr11xx_radio_cfg_rx_boosted(&data->hal_ctx, enable);
/* Clear spurious CMD_ERROR from SetRxBoosted (FW artifact) */
lr11xx_system_clear_irq_status(&data->hal_ctx,
LR11XX_SYSTEM_IRQ_ALL_MASK);
data->rx_boost_applied = enable;
k_mutex_unlock(&data->spi_mutex);
} else {
/* Defer to next start_rx where radio will be configured */
data->rx_boost_applied = false;
}
}
@@ -875,18 +913,39 @@ static int lr11xx_hw_init(struct lr11xx_data *data,
LOG_INF("RF switch: en=0x%02x rx=0x%02x tx=0x%02x txhp=0x%02x",
rfsw.enable, rfsw.rx, rfsw.tx, rfsw.tx_hp);
/* Calibrate */
/* Calibrate all 6 blocks (LF RC, HF RC, PLL, ADC, IMG, PLL TX).
* LR11xx has 6 cal blocks (0x3F), not 7 like SX126x. */
lr11xx_system_calibrate(ctx, 0x3F);
LOG_INF("Calibration OK");
/* After RX/TX, fall back to STBY_RC (not FS). Without this the
* chip may linger in FS mode, affecting RX chain re-init. */
lr11xx_radio_set_rx_tx_fallback_mode(ctx, LR11XX_RADIO_FALLBACK_STDBY_RC);
/* LoRa mode */
lr11xx_radio_set_pkt_type(ctx, LR11XX_RADIO_PKT_TYPE_LORA);
/* Clear any errors accumulated during init (calibration, TCXO, etc.)
* and any pending IRQ bits otherwise CMD_ERROR (bit 22) will fire
* DIO1 immediately after we enable it. */
uint16_t sys_errors = 0;
lr11xx_system_get_errors(ctx, &sys_errors);
if (sys_errors) {
LOG_WRN("System errors at init: 0x%04x — clearing", sys_errors);
}
lr11xx_system_clear_errors(ctx);
lr11xx_system_clear_irq_status(ctx, LR11XX_SYSTEM_IRQ_ALL_MASK);
/* Enable DIO1 */
lr11xx_hal_enable_dio1_irq(&data->hal_ctx);
/* Set default boost from DTS */
/* Set default boost from DTS — actual hardware register write
* is deferred to start_rx() where the radio is fully configured
* (frequency, modulation, pkt params). RadioLib/Arduino calls
* SetRxBoosted after full configuration. Calling it here (before
* frequency is set) triggers CMD_ERROR on all LR1110 FW versions. */
data->rx_boost_enabled = cfg->rx_boosted;
data->rx_boost_applied = false;
data->hw_initialized = true;
LOG_INF("LR11xx driver ready");
+19
View File
@@ -83,10 +83,12 @@ static struct k_event mesh_events;
/* Work items for event-driven processing */
static void rx_process_work_fn(struct k_work *work);
static void contact_iter_work_fn(struct k_work *work);
static void contact_save_work_fn(struct k_work *work);
static void housekeeping_timer_fn(struct k_timer *timer);
K_WORK_DEFINE(rx_process_work, rx_process_work_fn);
K_WORK_DEFINE(contact_iter_work, contact_iter_work_fn);
K_WORK_DEFINE(contact_save_work, contact_save_work_fn);
/* Housekeeping timer for periodic tasks (noise floor calibration, etc.)
* Fires every 5 seconds to wake event loop for maintenance without
@@ -365,6 +367,22 @@ static mesh::SimpleMeshTables mesh_tables;
static mesh::StaticPoolPacketManager packet_mgr;
static CompanionMesh companion_mesh(lora_radio, ms_clock, zephyr_rng, rtc_clock,
packet_mgr, mesh_tables, data_store);
/* Contact save work — runs on system workqueue so the main mesh thread
* stays free to drain LoRa ring buffer and process BLE frames.
* Submitted from CompanionMesh::flushDirtyContacts() via callback. */
static void contact_save_work_fn(struct k_work *work)
{
ARG_UNUSED(work);
if (companion_mesh_ptr) {
data_store.saveContacts(companion_mesh_ptr);
}
}
static void schedule_contact_save(void)
{
k_work_submit(&contact_save_work);
}
#endif
/* GPS enable callback - logs state changes
@@ -554,6 +572,7 @@ int main(void)
companion_mesh.setPinChangeCallback([](uint32_t new_pin) {
zephcore_ble_set_passkey(new_pin);
});
companion_mesh.setSaveScheduleCallback(schedule_contact_save);
companion_mesh_ptr = &companion_mesh;
/* Set LoRa callbacks for event-driven packet processing */
@@ -0,0 +1,73 @@
# SPDX-License-Identifier: Apache-2.0
# ZephCore LR1110 Firmware Updater updates LR1110 radio firmware to 0x0401.
#
# Board-specific: only builds for boards with an LR1110 radio (e.g., t1000_e).
#
# Build: west build -b t1000_e/nrf52840 zephcore/tools/lr1110_updater --pristine
# Flash: drag-drop build/zephyr/zephyr.uf2 onto UF2 drive
# (or: nrfjprog --program build/zephyr/zephyr.hex --sectorerase -r)
#
# After the updater finishes, flash ZephCore main firmware.
cmake_minimum_required(VERSION 3.20.0)
# Reuse the main project's custom board definitions
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..)
# Extract base board name (strip qualifier like /nrf52840)
string(REPLACE "/" ";" BOARD_PARTS ${BOARD})
list(GET BOARD_PARTS 0 BOARD_BASE)
# Find board overlay prefer updater-specific overlay, fall back to main board
file(GLOB_RECURSE UPD_OVERLAY_CANDIDATES
"${CMAKE_CURRENT_SOURCE_DIR}/boards/*/${BOARD_BASE}/board.overlay"
"${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.overlay")
if(UPD_OVERLAY_CANDIDATES)
list(GET UPD_OVERLAY_CANDIDATES 0 UPDATER_BOARD_OVERLAY)
else()
# Fall back to main board overlay
file(GLOB_RECURSE BOARD_OVERLAY_CANDIDATES
"${CMAKE_CURRENT_SOURCE_DIR}/../../boards/*/${BOARD_BASE}/board.overlay")
if(BOARD_OVERLAY_CANDIDATES)
list(GET BOARD_OVERLAY_CANDIDATES 0 UPDATER_BOARD_OVERLAY)
endif()
endif()
if(UPDATER_BOARD_OVERLAY)
if(EXTRA_DTC_OVERLAY_FILE)
set(EXTRA_DTC_OVERLAY_FILE "${EXTRA_DTC_OVERLAY_FILE};${UPDATER_BOARD_OVERLAY}" CACHE STRING "" FORCE)
else()
set(EXTRA_DTC_OVERLAY_FILE "${UPDATER_BOARD_OVERLAY}" CACHE STRING "" FORCE)
endif()
endif()
# Find updater-specific board.conf
file(GLOB_RECURSE UPD_BOARD_CONF_CANDIDATES
"${CMAKE_CURRENT_SOURCE_DIR}/boards/*/${BOARD_BASE}/board.conf"
"${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_BASE}/board.conf")
if(UPD_BOARD_CONF_CANDIDATES)
list(GET UPD_BOARD_CONF_CANDIDATES 0 UPD_BOARD_CONF)
if(EXTRA_CONF_FILE)
set(EXTRA_CONF_FILE "${EXTRA_CONF_FILE};${UPD_BOARD_CONF}" CACHE STRING "" FORCE)
else()
set(EXTRA_CONF_FILE "${UPD_BOARD_CONF}" CACHE STRING "" FORCE)
endif()
endif()
message(STATUS "LR1110 updater board overlay: ${UPDATER_BOARD_OVERLAY}")
message(STATUS "LR1110 updater board conf: ${UPD_BOARD_CONF}")
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(zephcore_lr1110_updater)
target_sources(app PRIVATE
src/main.c
src/lr11xx_hal_updater.c
src/lr1110_bootloader.c
)
# Include paths for Semtech driver headers and firmware image
target_include_directories(app PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/lib
${CMAKE_CURRENT_SOURCE_DIR}/../../adapters/radio/lr11xx
)
@@ -0,0 +1,31 @@
/*
* T1000-E LR1110 Updater overlay
* SPDX-License-Identifier: Apache-2.0
*
* Minimal overlay — only SPI1 (LR1110 radio) and USB console.
* Disables GPS UART, I2C sensors, and other peripherals not needed for update.
*/
/* Disable UART0 (GPS) — not needed, prevents driver init hang */
&uart0 {
status = "disabled";
};
/* Disable I2C0 (sensors) — not needed */
&i2c0 {
status = "disabled";
};
/* Route console to USB CDC ACM */
/ {
chosen {
zephyr,console = &cdc_acm_uart;
zephyr,shell-uart = &cdc_acm_uart;
};
};
&zephyr_udc0 {
cdc_acm_uart: cdc_acm_uart {
compatible = "zephyr,cdc-acm-uart";
};
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,75 @@
Semtech's software made available with this lr1110_transceiver_0401_license.txt
file includes or is provided with certain third-party components that are
subject to separate terms and conditions specified by applicable third-party
licenses (“Third-Party Components”). These Third-Party Components and applicable
licenses are set forth in this lr1110_transceiver_0401_license.txt file.
Your access and use of all Third-Party Components are at all times governed by
the applicable third-party licenses.
_______________________________________________________________________________
LR1110 transceiver firmware 0x0401
_______________________________________________________________________________
Semtech Corporation
-------------------
The Clear BSD License
Copyright Semtech Corporation 2021. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted (subject to the limitations in the disclaimer
below) provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Semtech corporation nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEMTECH CORPORATION BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Micro-ecc v1.0
--------------
https://github.com/kmackay/micro-ecc
Copyright (c) 2014, Kenneth MacKay
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+38
View File
@@ -0,0 +1,38 @@
# ZephCore LR1110 Firmware Updater — minimal config
# Only needs SPI + GPIO to talk to the LR1110 radio.
# SPI for radio communication
CONFIG_SPI=y
# GPIO for reset, busy, NSS, LED
CONFIG_GPIO=y
# Use DTS code_partition for linker origin (required for bootloader boards)
CONFIG_USE_DT_CODE_PARTITION=y
# UF2 output
CONFIG_BUILD_OUTPUT_UF2=y
# USB CDC console
CONFIG_USB_DEVICE_STACK_NEXT=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_UART_LINE_CTRL=y
CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=y
CONFIG_CDC_ACM_SERIAL_ENABLE_AT_BOOT=y
# Minimal logging — console printk only
CONFIG_LOG=n
CONFIG_ASSERT=n
# Reboot API (reboot to UF2 DFU after update)
CONFIG_REBOOT=y
# Don't need any of these
CONFIG_GNSS=n
CONFIG_I2C=n
CONFIG_SENSOR=n
CONFIG_BT=n
CONFIG_FILE_SYSTEM=n
CONFIG_FLASH=n
@@ -0,0 +1,247 @@
/*!
* @file lr1110_bootloader.c
*
* @brief Bootloader driver implementation for LR1110
*
* The Clear BSD License
* Copyright Semtech Corporation 2021. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the disclaimer
* below) provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Semtech corporation nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
* THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEMTECH CORPORATION BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Source: https://github.com/Lora-net/lr1110_driver
* Adapted for ZephCore updater tool only include path changes.
*/
#include "lr1110_bootloader.h"
#include "lr11xx_hal.h"
/* ── Constants ─────────────────────────────────────────────── */
#define LR1110_FLASH_DATA_MAX_LENGTH_UINT32 (64)
#define LR1110_FLASH_DATA_MAX_LENGTH_UINT8 (LR1110_FLASH_DATA_MAX_LENGTH_UINT32 * 4)
#define LR1110_BL_CMD_NO_PARAM_LENGTH (2)
#define LR1110_BL_GET_STATUS_CMD_LENGTH (2 + 4)
#define LR1110_BL_VERSION_CMD_LENGTH LR1110_BL_CMD_NO_PARAM_LENGTH
#define LR1110_BL_ERASE_FLASH_CMD_LENGTH LR1110_BL_CMD_NO_PARAM_LENGTH
#define LR1110_BL_WRITE_FLASH_ENCRYPTED_CMD_LENGTH (LR1110_BL_CMD_NO_PARAM_LENGTH + 4)
#define LR1110_BL_REBOOT_CMD_LENGTH (LR1110_BL_CMD_NO_PARAM_LENGTH + 1)
#define LR1110_BL_GET_PIN_CMD_LENGTH LR1110_BL_CMD_NO_PARAM_LENGTH
#define LR1110_BL_READ_CHIP_EUI_CMD_LENGTH LR1110_BL_CMD_NO_PARAM_LENGTH
#define LR1110_BL_READ_JOIN_EUI_CMD_LENGTH LR1110_BL_CMD_NO_PARAM_LENGTH
/* ── Opcodes ───────────────────────────────────────────────── */
enum {
LR1110_BL_GET_STATUS_OC = 0x0100,
LR1110_BL_GET_VERSION_OC = 0x0101,
LR1110_BL_ERASE_FLASH_OC = 0x8000,
LR1110_BL_WRITE_FLASH_ENCRYPTED_OC = 0x8003,
LR1110_BL_REBOOT_OC = 0x8005,
LR1110_BL_GET_PIN_OC = 0x800B,
LR1110_BL_READ_CHIP_EUI_OC = 0x800C,
LR1110_BL_READ_JOIN_EUI_OC = 0x800D,
};
/* ── Helper ────────────────────────────────────────────────── */
static uint8_t get_min_block_size(uint32_t operand)
{
return (operand > LR1110_FLASH_DATA_MAX_LENGTH_UINT32)
? LR1110_FLASH_DATA_MAX_LENGTH_UINT32
: (uint8_t)operand;
}
/* ── Implementation ────────────────────────────────────────── */
lr1110_status_t lr1110_bootloader_get_status(const void *context,
lr1110_bootloader_stat1_t *stat1,
lr1110_bootloader_stat2_t *stat2,
lr1110_bootloader_irq_mask_t *irq_status)
{
uint8_t data[LR1110_BL_GET_STATUS_CMD_LENGTH];
const lr1110_status_t status = (lr1110_status_t)
lr11xx_hal_direct_read(context, data, LR1110_BL_GET_STATUS_CMD_LENGTH);
if (status == LR1110_STATUS_OK) {
stat1->is_interrupt_active = ((data[0] & 0x01) != 0);
stat1->command_status = (lr1110_bootloader_command_status_t)(data[0] >> 1);
stat2->is_running_from_flash = ((data[1] & 0x01) != 0);
stat2->chip_mode = (lr1110_bootloader_chip_modes_t)((data[1] & 0x0F) >> 1);
stat2->reset_status = (lr1110_bootloader_reset_status_t)((data[1] & 0xF0) >> 4);
*irq_status = ((lr1110_bootloader_irq_mask_t)data[2] << 24) +
((lr1110_bootloader_irq_mask_t)data[3] << 16) +
((lr1110_bootloader_irq_mask_t)data[4] << 8) +
((lr1110_bootloader_irq_mask_t)data[5] << 0);
}
return status;
}
lr1110_status_t lr1110_bootloader_get_version(const void *context,
lr1110_bootloader_version_t *version)
{
const uint8_t cbuffer[LR1110_BL_VERSION_CMD_LENGTH] = {
(uint8_t)(LR1110_BL_GET_VERSION_OC >> 8),
(uint8_t)(LR1110_BL_GET_VERSION_OC >> 0),
};
uint8_t rbuffer[LR1110_BL_VERSION_LENGTH] = { 0 };
const lr1110_status_t status = (lr1110_status_t)
lr11xx_hal_read(context, cbuffer, LR1110_BL_VERSION_CMD_LENGTH,
rbuffer, LR1110_BL_VERSION_LENGTH);
if (status == LR1110_STATUS_OK) {
version->hw = rbuffer[0];
version->type = rbuffer[1];
version->fw = ((uint16_t)rbuffer[2] << 8) + (uint16_t)rbuffer[3];
}
return status;
}
lr1110_status_t lr1110_bootloader_erase_flash(const void *context)
{
const uint8_t cbuffer[LR1110_BL_ERASE_FLASH_CMD_LENGTH] = {
(uint8_t)(LR1110_BL_ERASE_FLASH_OC >> 8),
(uint8_t)(LR1110_BL_ERASE_FLASH_OC >> 0),
};
return (lr1110_status_t)
lr11xx_hal_write(context, cbuffer, LR1110_BL_ERASE_FLASH_CMD_LENGTH, 0, 0);
}
lr1110_status_t lr1110_bootloader_write_flash_encrypted(const void *context,
uint32_t offset, const uint32_t *data, uint8_t length)
{
const uint8_t cbuffer[LR1110_BL_WRITE_FLASH_ENCRYPTED_CMD_LENGTH] = {
(uint8_t)(LR1110_BL_WRITE_FLASH_ENCRYPTED_OC >> 8),
(uint8_t)(LR1110_BL_WRITE_FLASH_ENCRYPTED_OC >> 0),
(uint8_t)(offset >> 24),
(uint8_t)(offset >> 16),
(uint8_t)(offset >> 8),
(uint8_t)(offset >> 0),
};
/* Convert uint32_t words to big-endian byte array for SPI */
uint8_t cdata[256] = { 0 };
for (uint8_t i = 0; i < length; i++) {
uint8_t *p = &cdata[i * sizeof(uint32_t)];
p[0] = (uint8_t)(data[i] >> 24);
p[1] = (uint8_t)(data[i] >> 16);
p[2] = (uint8_t)(data[i] >> 8);
p[3] = (uint8_t)(data[i] >> 0);
}
return (lr1110_status_t)
lr11xx_hal_write(context, cbuffer, LR1110_BL_WRITE_FLASH_ENCRYPTED_CMD_LENGTH,
cdata, length * sizeof(uint32_t));
}
lr1110_status_t lr1110_bootloader_write_flash_encrypted_full(const void *context,
uint32_t offset, const uint32_t *buffer, uint32_t length)
{
uint32_t remaining = length;
uint32_t local_offset = offset;
uint32_t loop = 0;
while (remaining != 0) {
const lr1110_status_t status = lr1110_bootloader_write_flash_encrypted(
context, local_offset,
buffer + loop * LR1110_FLASH_DATA_MAX_LENGTH_UINT32,
get_min_block_size(remaining));
if (status != LR1110_STATUS_OK) {
return status;
}
local_offset += LR1110_FLASH_DATA_MAX_LENGTH_UINT8;
remaining = (remaining < LR1110_FLASH_DATA_MAX_LENGTH_UINT32)
? 0
: (remaining - LR1110_FLASH_DATA_MAX_LENGTH_UINT32);
loop++;
}
return LR1110_STATUS_OK;
}
lr1110_status_t lr1110_bootloader_reboot(const void *context,
bool stay_in_bootloader)
{
const uint8_t cbuffer[LR1110_BL_REBOOT_CMD_LENGTH] = {
(uint8_t)(LR1110_BL_REBOOT_OC >> 8),
(uint8_t)(LR1110_BL_REBOOT_OC >> 0),
stay_in_bootloader ? 0x03 : 0x00,
};
return (lr1110_status_t)
lr11xx_hal_write(context, cbuffer, LR1110_BL_REBOOT_CMD_LENGTH, 0, 0);
}
lr1110_status_t lr1110_bootloader_read_pin(const void *context,
lr1110_bootloader_pin_t pin)
{
const uint8_t cbuffer[LR1110_BL_GET_PIN_CMD_LENGTH] = {
(uint8_t)(LR1110_BL_GET_PIN_OC >> 8),
(uint8_t)(LR1110_BL_GET_PIN_OC >> 0),
};
return (lr1110_status_t)
lr11xx_hal_read(context, cbuffer, LR1110_BL_GET_PIN_CMD_LENGTH,
pin, LR1110_BL_PIN_LENGTH);
}
lr1110_status_t lr1110_bootloader_read_chip_eui(const void *context,
lr1110_bootloader_chip_eui_t chip_eui)
{
const uint8_t cbuffer[LR1110_BL_READ_CHIP_EUI_CMD_LENGTH] = {
(uint8_t)(LR1110_BL_READ_CHIP_EUI_OC >> 8),
(uint8_t)(LR1110_BL_READ_CHIP_EUI_OC >> 0),
};
return (lr1110_status_t)
lr11xx_hal_read(context, cbuffer, LR1110_BL_READ_CHIP_EUI_CMD_LENGTH,
chip_eui, LR1110_BL_CHIP_EUI_LENGTH);
}
lr1110_status_t lr1110_bootloader_read_join_eui(const void *context,
lr1110_bootloader_join_eui_t join_eui)
{
const uint8_t cbuffer[LR1110_BL_READ_JOIN_EUI_CMD_LENGTH] = {
(uint8_t)(LR1110_BL_READ_JOIN_EUI_OC >> 8),
(uint8_t)(LR1110_BL_READ_JOIN_EUI_OC >> 0),
};
return (lr1110_status_t)
lr11xx_hal_read(context, cbuffer, LR1110_BL_READ_JOIN_EUI_CMD_LENGTH,
join_eui, LR1110_BL_JOIN_EUI_LENGTH);
}
@@ -0,0 +1,144 @@
/*!
* @file lr1110_bootloader.h
*
* @brief Bootloader driver definition for LR1110
*
* The Clear BSD License
* Copyright Semtech Corporation 2021. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted (subject to the limitations in the disclaimer
* below) provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Semtech corporation nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
* THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEMTECH CORPORATION BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef LR1110_BOOTLOADER_H
#define LR1110_BOOTLOADER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include "lr11xx_types.h"
/* Compatibility aliases — the older lr1110_driver used lr1110_status_t,
* the newer lr11xx_driver (SWDR001) uses lr11xx_status_t.
* Both are the same enum: OK=0, ERROR=3. */
typedef lr11xx_status_t lr1110_status_t;
#define LR1110_STATUS_OK LR11XX_STATUS_OK
#define LR1110_STATUS_ERROR LR11XX_STATUS_ERROR
/* ── Types ─────────────────────────────────────────────────── */
#define LR1110_BL_VERSION_LENGTH 4
#define LR1110_BL_PIN_LENGTH 4
#define LR1110_BL_CHIP_EUI_LENGTH 8
#define LR1110_BL_JOIN_EUI_LENGTH 8
typedef uint8_t lr1110_bootloader_pin_t[LR1110_BL_PIN_LENGTH];
typedef uint8_t lr1110_bootloader_chip_eui_t[LR1110_BL_CHIP_EUI_LENGTH];
typedef uint8_t lr1110_bootloader_join_eui_t[LR1110_BL_JOIN_EUI_LENGTH];
typedef uint32_t lr1110_bootloader_irq_mask_t;
typedef enum {
LR1110_BOOTLOADER_CMD_STATUS_FAIL = 0x00,
LR1110_BOOTLOADER_CMD_STATUS_PERR = 0x01,
LR1110_BOOTLOADER_CMD_STATUS_OK = 0x02,
LR1110_BOOTLOADER_CMD_STATUS_DATA = 0x03,
} lr1110_bootloader_command_status_t;
typedef enum {
LR1110_BOOTLOADER_CHIP_MODE_SLEEP = 0x00,
LR1110_BOOTLOADER_CHIP_MODE_STBY_RC = 0x01,
LR1110_BOOTLOADER_CHIP_MODE_STBY_XOSC = 0x02,
LR1110_BOOTLOADER_CHIP_MODE_FS = 0x03,
LR1110_BOOTLOADER_CHIP_MODE_RX = 0x04,
LR1110_BOOTLOADER_CHIP_MODE_TX = 0x05,
LR1110_BOOTLOADER_CHIP_MODE_LOC = 0x06,
} lr1110_bootloader_chip_modes_t;
typedef enum {
LR1110_BOOTLOADER_RESET_STATUS_CLEARED = 0x00,
LR1110_BOOTLOADER_RESET_STATUS_ANALOG = 0x01,
LR1110_BOOTLOADER_RESET_STATUS_EXTERNAL = 0x02,
LR1110_BOOTLOADER_RESET_STATUS_SYSTEM = 0x03,
LR1110_BOOTLOADER_RESET_STATUS_WATCHDOG = 0x04,
LR1110_BOOTLOADER_RESET_STATUS_IOCD_RESTART = 0x05,
LR1110_BOOTLOADER_RESET_STATUS_RTC_RESTART = 0x06,
} lr1110_bootloader_reset_status_t;
typedef struct {
lr1110_bootloader_command_status_t command_status;
bool is_interrupt_active;
} lr1110_bootloader_stat1_t;
typedef struct {
lr1110_bootloader_reset_status_t reset_status;
lr1110_bootloader_chip_modes_t chip_mode;
bool is_running_from_flash;
} lr1110_bootloader_stat2_t;
typedef struct {
uint8_t hw;
uint8_t type;
uint16_t fw;
} lr1110_bootloader_version_t;
/* ── Functions ─────────────────────────────────────────────── */
lr1110_status_t lr1110_bootloader_get_status(const void *context,
lr1110_bootloader_stat1_t *stat1,
lr1110_bootloader_stat2_t *stat2,
lr1110_bootloader_irq_mask_t *irq_status);
lr1110_status_t lr1110_bootloader_get_version(const void *context,
lr1110_bootloader_version_t *version);
lr1110_status_t lr1110_bootloader_erase_flash(const void *context);
lr1110_status_t lr1110_bootloader_write_flash_encrypted(const void *context,
uint32_t offset, const uint32_t *data, uint8_t length);
lr1110_status_t lr1110_bootloader_write_flash_encrypted_full(const void *context,
uint32_t offset, const uint32_t *buffer, uint32_t length);
lr1110_status_t lr1110_bootloader_reboot(const void *context,
bool stay_in_bootloader);
lr1110_status_t lr1110_bootloader_read_pin(const void *context,
lr1110_bootloader_pin_t pin);
lr1110_status_t lr1110_bootloader_read_chip_eui(const void *context,
lr1110_bootloader_chip_eui_t chip_eui);
lr1110_status_t lr1110_bootloader_read_join_eui(const void *context,
lr1110_bootloader_join_eui_t join_eui);
#ifdef __cplusplus
}
#endif
#endif /* LR1110_BOOTLOADER_H */
@@ -0,0 +1,310 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Minimal LR11xx HAL for firmware updater.
*
* Implements the Semtech lr11xx_hal_* interface used by lr1110_bootloader.c.
* Stripped down: no DIO1 interrupt, no work queue, no sleep tracking.
* Just SPI + GPIO for bootloader commands.
*/
#include "lr11xx_hal_updater.h"
#include "lr11xx_hal.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(lr1110_hal, LOG_LEVEL_INF);
/* ── Hardware from devicetree ──────────────────────────────── */
#define LR1110_NODE DT_NODELABEL(lora)
#if !DT_NODE_EXISTS(LR1110_NODE)
#error "No 'lora' node found in devicetree — is this an LR1110 board?"
#endif
/* SPI bus device */
static const struct device *spi_dev = DEVICE_DT_GET(DT_BUS(LR1110_NODE));
/* SPI config — manual CS (we toggle NSS via GPIO) */
static struct spi_config spi_cfg = {
.frequency = DT_PROP(LR1110_NODE, spi_max_frequency),
.operation = SPI_WORD_SET(8) | SPI_TRANSFER_MSB,
};
/* GPIO pins */
static const struct gpio_dt_spec pin_nss = GPIO_DT_SPEC_GET(DT_BUS(LR1110_NODE), cs_gpios);
static const struct gpio_dt_spec pin_reset = GPIO_DT_SPEC_GET(LR1110_NODE, reset_gpios);
static const struct gpio_dt_spec pin_busy = GPIO_DT_SPEC_GET(LR1110_NODE, busy_gpios);
/* BUSY timeout — 3 seconds (flash erase can take ~2.5s) */
#define BUSY_TIMEOUT_MS 3000
/* Extended BUSY timeout for flash erase */
#define ERASE_BUSY_TIMEOUT_MS 5000
/* ── Context (opaque pointer for Semtech driver) ──────────── */
/* The Semtech driver passes 'context' to every HAL function.
* We use a dummy static all state is in file-scope globals. */
static int dummy_context;
void *lr1110_updater_get_context(void)
{
return &dummy_context;
}
/* ── BUSY wait ────────────────────────────────────────────── */
static int wait_on_busy(uint32_t timeout_ms)
{
int64_t start = k_uptime_get();
while (gpio_pin_get_dt(&pin_busy)) {
if ((k_uptime_get() - start) > timeout_ms) {
printk("ERROR: BUSY timeout after %u ms\n", timeout_ms);
return -ETIMEDOUT;
}
k_busy_wait(100); /* 100us */
}
return 0;
}
/* ── Public init/reset ────────────────────────────────────── */
int lr1110_updater_hal_init(void)
{
int ret;
if (!device_is_ready(spi_dev)) {
printk("ERROR: SPI device not ready\n");
return -ENODEV;
}
/* NSS — output, inactive (HIGH = deselected) */
ret = gpio_pin_configure_dt(&pin_nss, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
printk("ERROR: NSS config failed: %d\n", ret);
return ret;
}
/* RESET — output, inactive (HIGH = not in reset) */
ret = gpio_pin_configure_dt(&pin_reset, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
printk("ERROR: RESET config failed: %d\n", ret);
return ret;
}
/* BUSY — input */
ret = gpio_pin_configure_dt(&pin_busy, GPIO_INPUT);
if (ret < 0) {
printk("ERROR: BUSY config failed: %d\n", ret);
return ret;
}
printk("LR1110 HAL initialized (SPI @ %u Hz)\n", spi_cfg.frequency);
return 0;
}
int lr1110_updater_hw_reset(void)
{
printk("Resetting LR1110...\n");
/* Assert reset (active-low: logical 1 = physical LOW = reset active) */
gpio_pin_set_dt(&pin_reset, 1);
k_msleep(10);
/* Release reset */
gpio_pin_set_dt(&pin_reset, 0);
/* After reset, the LR1110 boots into bootloader if flash is empty,
* or into firmware if flash has valid content.
* Firmware boot takes up to 273ms (datasheet). */
k_msleep(300);
int ret = wait_on_busy(BUSY_TIMEOUT_MS);
if (ret) {
printk("ERROR: BUSY stuck after reset\n");
} else {
printk("LR1110 reset complete, BUSY=low\n");
}
return ret;
}
int lr1110_updater_reset_to_bootloader(void)
{
printk("Resetting LR1110 into bootloader (BUSY held LOW)...\n");
/* Semtech lr1110_updater_tool pattern:
* 1. Drive BUSY LOW as output during reset
* 2. Pulse RESET
* 3. Wait 500ms
* 4. Release BUSY back to input
* 5. Wait 100ms + BUSY low
*
* When BUSY is held LOW by the host during reset, the LR1110
* enters bootloader mode instead of executing flash firmware. */
/* Drive BUSY to physical LOW (pin_busy has GPIO_ACTIVE_HIGH,
* so we use raw GPIO to be explicit about physical level) */
gpio_pin_configure(pin_busy.port, pin_busy.pin,
GPIO_OUTPUT_LOW);
/* Assert reset */
gpio_pin_set_dt(&pin_reset, 1);
k_msleep(10);
/* Release reset — chip starts booting, sees BUSY held LOW → bootloader */
gpio_pin_set_dt(&pin_reset, 0);
k_msleep(500);
/* Release BUSY back to input */
gpio_pin_configure_dt(&pin_busy, GPIO_INPUT);
k_msleep(100);
int ret = wait_on_busy(BUSY_TIMEOUT_MS);
if (ret) {
printk("ERROR: BUSY stuck after bootloader reset\n");
} else {
printk("LR1110 in bootloader mode, BUSY=low\n");
}
return ret;
}
/* ── Semtech HAL interface ────────────────────────────────── */
lr11xx_hal_status_t lr11xx_hal_write(const void *context, const uint8_t *command,
const uint16_t command_length,
const uint8_t *data, const uint16_t data_length)
{
(void)context;
int ret;
/* Wait for device ready */
if (wait_on_busy(BUSY_TIMEOUT_MS)) {
return LR11XX_HAL_STATUS_ERROR;
}
const struct spi_buf tx_bufs[] = {
{ .buf = (uint8_t *)command, .len = command_length },
{ .buf = (uint8_t *)data, .len = data_length },
};
const struct spi_buf_set tx = {
.buffers = tx_bufs,
.count = (data_length > 0) ? 2 : 1,
};
gpio_pin_set_dt(&pin_nss, 1); /* Assert NSS (LOW) */
ret = spi_write(spi_dev, &spi_cfg, &tx);
gpio_pin_set_dt(&pin_nss, 0); /* Deassert NSS (HIGH) */
if (ret < 0) {
printk("ERROR: SPI write failed: %d\n", ret);
return LR11XX_HAL_STATUS_ERROR;
}
/* For flash erase command (0x8000), BUSY can stay high for ~2.5 seconds */
uint16_t opcode = 0;
if (command_length >= 2) {
opcode = ((uint16_t)command[0] << 8) | command[1];
}
uint32_t timeout = (opcode == 0x8000) ? ERASE_BUSY_TIMEOUT_MS : BUSY_TIMEOUT_MS;
if (wait_on_busy(timeout)) {
return LR11XX_HAL_STATUS_ERROR;
}
return LR11XX_HAL_STATUS_OK;
}
lr11xx_hal_status_t lr11xx_hal_read(const void *context, const uint8_t *command,
const uint16_t command_length,
uint8_t *data, const uint16_t data_length)
{
(void)context;
int ret;
/* Wait for device ready */
if (wait_on_busy(BUSY_TIMEOUT_MS)) {
return LR11XX_HAL_STATUS_ERROR;
}
/* Step 1: Write command */
const struct spi_buf tx_buf = { .buf = (uint8_t *)command, .len = command_length };
const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1 };
gpio_pin_set_dt(&pin_nss, 1);
ret = spi_write(spi_dev, &spi_cfg, &tx);
gpio_pin_set_dt(&pin_nss, 0);
if (ret < 0) {
printk("ERROR: SPI write (cmd) failed: %d\n", ret);
return LR11XX_HAL_STATUS_ERROR;
}
if (data_length == 0) {
return (wait_on_busy(BUSY_TIMEOUT_MS) == 0)
? LR11XX_HAL_STATUS_OK : LR11XX_HAL_STATUS_ERROR;
}
/* Step 2: Wait for device ready, then read response */
if (wait_on_busy(BUSY_TIMEOUT_MS)) {
return LR11XX_HAL_STATUS_ERROR;
}
/* LR11xx returns 1 dummy byte + data */
uint8_t dummy;
const struct spi_buf rx_bufs[] = {
{ .buf = &dummy, .len = 1 },
{ .buf = data, .len = data_length },
};
const struct spi_buf_set rx = { .buffers = rx_bufs, .count = 2 };
gpio_pin_set_dt(&pin_nss, 1);
ret = spi_read(spi_dev, &spi_cfg, &rx);
gpio_pin_set_dt(&pin_nss, 0);
if (ret < 0) {
printk("ERROR: SPI read failed: %d\n", ret);
return LR11XX_HAL_STATUS_ERROR;
}
return LR11XX_HAL_STATUS_OK;
}
lr11xx_hal_status_t lr11xx_hal_direct_read(const void *context, uint8_t *data,
const uint16_t data_length)
{
(void)context;
int ret;
if (wait_on_busy(BUSY_TIMEOUT_MS)) {
return LR11XX_HAL_STATUS_ERROR;
}
const struct spi_buf rx_buf = { .buf = data, .len = data_length };
const struct spi_buf_set rx = { .buffers = &rx_buf, .count = 1 };
gpio_pin_set_dt(&pin_nss, 1);
ret = spi_read(spi_dev, &spi_cfg, &rx);
gpio_pin_set_dt(&pin_nss, 0);
if (ret < 0) {
printk("ERROR: SPI direct read failed: %d\n", ret);
return LR11XX_HAL_STATUS_ERROR;
}
return LR11XX_HAL_STATUS_OK;
}
lr11xx_hal_status_t lr11xx_hal_reset(const void *context)
{
(void)context;
return (lr1110_updater_hw_reset() == 0)
? LR11XX_HAL_STATUS_OK : LR11XX_HAL_STATUS_ERROR;
}
lr11xx_hal_status_t lr11xx_hal_wakeup(const void *context)
{
(void)context;
return (wait_on_busy(BUSY_TIMEOUT_MS) == 0)
? LR11XX_HAL_STATUS_OK : LR11XX_HAL_STATUS_ERROR;
}
@@ -0,0 +1,52 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Minimal LR11xx HAL for firmware updater no IRQ, no DIO1, just SPI+GPIO.
*
* Provides the lr11xx_hal_write/read/direct_read/reset interface that the
* Semtech bootloader driver (lr1110_bootloader.c) needs.
*/
#ifndef LR11XX_HAL_UPDATER_H
#define LR11XX_HAL_UPDATER_H
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
/**
* @brief Initialize SPI and GPIOs for LR1110 communication.
*
* Reads pin configuration from the devicetree "semtech,lr1110" node on SPI1.
*
* @return 0 on success, negative errno on failure
*/
int lr1110_updater_hal_init(void);
/**
* @brief Hardware reset the LR1110 (pulse RESET, wait for BUSY low).
*
* After reset the LR1110 boots into firmware (if flash valid) or bootloader.
*
* @return 0 on success, negative errno on failure
*/
int lr1110_updater_hw_reset(void);
/**
* @brief Force LR1110 into bootloader mode via hardware reset.
*
* Holds BUSY LOW as output during RESET pulse this forces the LR1110
* into bootloader mode regardless of flash content. This is the official
* Semtech approach (lr1110_updater_tool).
*
* @return 0 on success, negative errno on failure
*/
int lr1110_updater_reset_to_bootloader(void);
/**
* @brief Get the opaque HAL context pointer for Semtech driver calls.
*
* This pointer is passed as 'context' to lr1110_bootloader_*() functions.
*/
void *lr1110_updater_get_context(void);
#endif /* LR11XX_HAL_UPDATER_H */
+314
View File
@@ -0,0 +1,314 @@
/*
* ZephCore LR1110 Firmware Updater
* SPDX-License-Identifier: Apache-2.0
*
* Standalone tool that updates the LR1110 radio firmware to v0x0401.
* Flash this UF2 first, let it update the radio, then flash ZephCore main firmware.
*
* Firmware image source: https://github.com/Lora-net/radio_firmware_images
* Bootloader protocol source: https://github.com/Lora-net/lr1110_driver
* License: Clear BSD (Semtech Corporation 2021-2023)
*
* Update sequence (matches Semtech's official lr1110_updater_tool):
* 1. Hardware reset LR1110
* 2. Read version (GetVersion 0x0101) type tells us firmware vs bootloader
* 3. If already running 0x0401 skip, reboot to UF2 DFU
* 4. Reboot into bootloader mode (Reboot 0x8005 with stay=true)
* 5. Verify we're in bootloader (type == 0xDF)
* 6. Erase flash (EraseFlash 0x8000) ~2.5 seconds
* 7. Write firmware (WriteFlashEncrypted 0x8003) 61320 words in 64-word chunks
* 8. Reboot into firmware (Reboot 0x8005 with stay=false)
* 9. Verify new firmware version == 0x0401
* 10. Reboot MCU into UF2 DFU mode for main firmware flash
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/reboot.h>
#include "lr11xx_hal_updater.h"
#include "lr1110_bootloader.h"
#include "lr1110_transceiver_0401.h"
#if defined(CONFIG_SOC_SERIES_NRF52X) || defined(CONFIG_SOC_SERIES_NRF52)
#include <hal/nrf_power.h>
#endif
/* Adafruit UF2 bootloader magic — enter mass storage DFU mode */
#define BOOTLOADER_DFU_UF2_MAGIC 0x57
/* Target firmware version */
#define TARGET_FW_VERSION LR11XX_FIRMWARE_VERSION /* 0x0401 */
/* LR1110 type field values */
#define LR1110_TYPE_TRANSCEIVER 0x01
#define LR1110_TYPE_BOOTLOADER 0xDF
/* ── LED feedback (optional) ─────────────────────────────────── */
#if DT_NODE_EXISTS(DT_ALIAS(led0))
#define HAS_LED 1
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
static void led_init(void) { gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); }
static void led_on(void) { gpio_pin_set_dt(&led, 1); }
static void led_off(void) { gpio_pin_set_dt(&led, 0); }
static void led_toggle(void) { gpio_pin_toggle_dt(&led); }
#else
#define HAS_LED 0
static void led_init(void) {}
static void led_on(void) {}
static void led_off(void) {}
static void led_toggle(void) {}
#endif
/* ── Helpers ──────────────────────────────────────────────────── */
static void reboot_to_uf2(void)
{
printk("\nRebooting into UF2 DFU mode...\n");
printk("You can now drag-drop ZephCore firmware UF2.\n");
k_msleep(500);
#if defined(CONFIG_SOC_SERIES_NRF52X) || defined(CONFIG_SOC_SERIES_NRF52)
nrf_power_gpregret_set(NRF_POWER, 0, BOOTLOADER_DFU_UF2_MAGIC);
#endif
sys_reboot(SYS_REBOOT_COLD);
}
static void fatal_error(const char *msg)
{
printk("\n!!! FATAL: %s\n", msg);
printk("Please power cycle the device and try again.\n");
led_off();
/* Blink LED rapidly to indicate error */
while (1) {
led_toggle();
k_msleep(200);
}
}
/* ── Main ─────────────────────────────────────────────────────── */
int main(void)
{
lr1110_bootloader_version_t version = { 0 };
void *ctx;
lr1110_status_t rc;
/* Brief delay for USB CDC to enumerate */
k_msleep(2000);
printk("\n");
printk("============================================\n");
printk(" ZephCore LR1110 Firmware Updater\n");
printk(" Target: transceiver FW 0x%04X\n", TARGET_FW_VERSION);
printk(" Image: %u words (%u KB)\n",
LR11XX_FIRMWARE_IMAGE_SIZE,
(LR11XX_FIRMWARE_IMAGE_SIZE * 4) / 1024);
printk("============================================\n");
printk("\n");
led_init();
led_on();
/* ── Step 1: Initialize HAL ── */
printk("[1/10] Initializing SPI and GPIOs...\n");
if (lr1110_updater_hal_init() != 0) {
fatal_error("HAL init failed");
}
ctx = lr1110_updater_get_context();
/* ── Step 2: Hardware reset ── */
printk("[2/10] Hardware reset LR1110...\n");
if (lr1110_updater_hw_reset() != 0) {
fatal_error("Hardware reset failed (BUSY stuck)");
}
/* ── Step 3: Read current version ── */
printk("[3/10] Reading current firmware version...\n");
rc = lr1110_bootloader_get_version(ctx, &version);
if (rc != LR1110_STATUS_OK) {
fatal_error("GetVersion failed");
}
printk(" HW = 0x%02X\n", version.hw);
printk(" TYPE = 0x%02X", version.type);
if (version.type == LR1110_TYPE_TRANSCEIVER) {
printk(" (transceiver firmware)\n");
} else if (version.type == LR1110_TYPE_BOOTLOADER) {
printk(" (bootloader — no firmware loaded)\n");
} else {
printk(" (unknown)\n");
}
printk(" FW = 0x%04X\n", version.fw);
/* ── Step 4: Check if update needed ── */
if (version.type == LR1110_TYPE_TRANSCEIVER && version.fw == TARGET_FW_VERSION) {
printk("\nAlready running target firmware 0x%04X — no update needed!\n",
TARGET_FW_VERSION);
led_off();
reboot_to_uf2();
return 0;
}
if (version.type == LR1110_TYPE_TRANSCEIVER) {
printk("\n[4/10] Current FW 0x%04X → updating to 0x%04X\n",
version.fw, TARGET_FW_VERSION);
/* Force into bootloader mode via hardware reset with BUSY held LOW.
* This is the official Semtech approach (lr1110_updater_tool).
* Cannot use the software reboot command (0x8005) because that's
* a bootloader-mode opcode we're in firmware mode (0x0118). */
printk("[5/10] Forcing LR1110 into bootloader mode...\n");
if (lr1110_updater_reset_to_bootloader() != 0) {
fatal_error("Failed to enter bootloader mode");
}
} else if (version.type == LR1110_TYPE_BOOTLOADER) {
printk("\n[4/10] Already in bootloader mode — proceeding with flash\n");
printk("[5/10] (skipped — already in bootloader)\n");
} else {
fatal_error("Unknown chip type — cannot proceed");
}
/* ── Step 5: Verify bootloader mode ── */
rc = lr1110_bootloader_get_version(ctx, &version);
if (rc != LR1110_STATUS_OK) {
fatal_error("GetVersion in bootloader failed");
}
printk(" Bootloader: HW=0x%02X TYPE=0x%02X FW=0x%04X\n",
version.hw, version.type, version.fw);
if (version.type != LR1110_TYPE_BOOTLOADER) {
fatal_error("Not in bootloader mode after reboot");
}
/* ── Step 6: Read chip identity ── */
{
lr1110_bootloader_pin_t pin = { 0 };
lr1110_bootloader_chip_eui_t chip_eui = { 0 };
lr1110_bootloader_join_eui_t join_eui = { 0 };
lr1110_bootloader_read_pin(ctx, pin);
lr1110_bootloader_read_chip_eui(ctx, chip_eui);
lr1110_bootloader_read_join_eui(ctx, join_eui);
printk(" PIN = 0x%02X%02X%02X%02X\n",
pin[0], pin[1], pin[2], pin[3]);
printk(" ChipEUI = 0x%02X%02X%02X%02X%02X%02X%02X%02X\n",
chip_eui[0], chip_eui[1], chip_eui[2], chip_eui[3],
chip_eui[4], chip_eui[5], chip_eui[6], chip_eui[7]);
printk(" JoinEUI = 0x%02X%02X%02X%02X%02X%02X%02X%02X\n",
join_eui[0], join_eui[1], join_eui[2], join_eui[3],
join_eui[4], join_eui[5], join_eui[6], join_eui[7]);
}
/* ── Step 7: Erase flash ── */
printk("\n[6/10] Erasing LR1110 flash (~2.5 seconds)...\n");
led_toggle();
rc = lr1110_bootloader_erase_flash(ctx);
if (rc != LR1110_STATUS_OK) {
fatal_error("Flash erase failed");
}
printk(" Flash erase complete!\n");
/* ── Step 8: Write firmware image ── */
printk("[7/10] Writing firmware (%u words = %u KB)...\n",
LR11XX_FIRMWARE_IMAGE_SIZE,
(LR11XX_FIRMWARE_IMAGE_SIZE * 4) / 1024);
/* Progress tracking */
uint32_t total = LR11XX_FIRMWARE_IMAGE_SIZE;
uint32_t chunk_size = 64; /* 64 uint32_t words per write */
uint32_t num_chunks = (total + chunk_size - 1) / chunk_size;
uint32_t progress_step = num_chunks / 10; /* Print every 10% */
if (progress_step == 0) progress_step = 1;
uint32_t remaining = total;
uint32_t offset = 0;
uint32_t chunk_idx = 0;
while (remaining > 0) {
uint8_t this_chunk = (remaining > chunk_size)
? (uint8_t)chunk_size : (uint8_t)remaining;
rc = lr1110_bootloader_write_flash_encrypted(
ctx, offset,
&lr11xx_firmware_image[chunk_idx * chunk_size],
this_chunk);
if (rc != LR1110_STATUS_OK) {
printk("\n");
printk("ERROR: Write failed at offset 0x%08X (chunk %u/%u)\n",
offset, chunk_idx + 1, num_chunks);
fatal_error("Firmware write failed");
}
/* Progress feedback */
if ((chunk_idx % progress_step) == 0) {
uint32_t pct = (chunk_idx * 100) / num_chunks;
printk(" %3u%% (%u / %u words)\n",
pct, total - remaining + this_chunk, total);
led_toggle();
}
offset += this_chunk * sizeof(uint32_t);
remaining -= this_chunk;
chunk_idx++;
}
printk(" 100%% (%u / %u words)\n", total, total);
printk(" Firmware write complete!\n");
/* ── Step 9: Reboot into firmware ── */
printk("\n[8/10] Rebooting LR1110 into new firmware...\n");
lr1110_bootloader_reboot(ctx, false); /* stay_in_bootloader = false */
/* Wait for firmware boot (273ms typical) */
k_msleep(500);
/* Re-reset to ensure clean state */
if (lr1110_updater_hw_reset() != 0) {
fatal_error("Reset after firmware flash failed");
}
/* ── Step 10: Verify new firmware ── */
printk("[9/10] Verifying new firmware version...\n");
rc = lr1110_bootloader_get_version(ctx, &version);
if (rc != LR1110_STATUS_OK) {
fatal_error("GetVersion after flash failed");
}
printk(" HW = 0x%02X\n", version.hw);
printk(" TYPE = 0x%02X", version.type);
if (version.type == LR1110_TYPE_TRANSCEIVER) {
printk(" (transceiver firmware)\n");
} else if (version.type == LR1110_TYPE_BOOTLOADER) {
printk(" (bootloader — firmware not running!)\n");
} else {
printk(" (unknown)\n");
}
printk(" FW = 0x%04X\n", version.fw);
if (version.type == LR1110_TYPE_TRANSCEIVER && version.fw == TARGET_FW_VERSION) {
printk("\n============================================\n");
printk(" UPDATE SUCCESSFUL!\n");
printk(" LR1110 firmware: 0x%04X\n", version.fw);
printk("============================================\n");
led_on();
} else {
printk("\nWARNING: Expected FW 0x%04X but got TYPE=0x%02X FW=0x%04X\n",
TARGET_FW_VERSION, version.type, version.fw);
printk("The update may have failed. Try again.\n");
led_off();
}
/* ── Reboot to UF2 DFU ── */
printk("\n[10/10] Done! Rebooting to UF2 DFU mode...\n");
reboot_to_uf2();
return 0; /* never reached */
}