mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 21:08:19 +00:00
t1000 fixes
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 TX→RX.
|
||||
*
|
||||
* 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 CAD→RX 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 TX→RX 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");
|
||||
|
||||
@@ -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.
|
||||
@@ -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 */
|
||||
@@ -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 */
|
||||
}
|
||||
Reference in New Issue
Block a user