feat(boards): RAK4631 WisBlock bring-up + build-system cleanup

RAK4631 board:
- SX126X_POWER_EN on P1.05 driven HIGH at init (radio is power-gated;
  without this the chip has no power and SPI hangs)
- HFXO + LFXO external crystals in embassy_nrf::Config (USB CDC-ACM
  needs +/- 500 ppm which the internal RC oscillator can't provide)
- SoftwareVbusDetect(true, true) so USB enumerates without needing a
  CLOCK_POWER interrupt handler
- Green LED on P1.03 via SimpleLed (RAK19007 Base LED1, active HIGH)
- driver/mod.rs: open simple_led feature gate for rak_wisblock_4631
- Spim::new pin-order comment on SPI setup (arg order is sck/miso/mosi,
  not sck/mosi/miso — easy to get wrong)

debug-checkpoint instrumentation (feature-gated, zero cost in release):
- radio.rs: debug_blink::set(N) at each TX-phase boundary, filling in
  the long-aspirational blink-map doc
- debug_blink.rs: checkpoint 19 between prepare_for_cad and cad(), so
  future bring-ups can distinguish modem-setup hang from DIO1-wait hang

justfile + mise.toml:
- set shell := ["mise", "exec", "--", "sh", "-c"] so recipes
  always resolve tools through mise, not whatever happens to be on PATH
- replace per-board := vars + long if/elif _info chain with a single
  case statement — one place per board, two lists (names + metadata)
- new flash-dfu recipe using adafruit-nrfutil via uvx, with VID:PID
  (239a:0029) port autodetect for the Adafruit nRF52 bootloader
- mise.toml: rust pins now carry ARM targets + clippy, so 'mise install'
  is sufficient bootstrapping (no extra rustup target add steps)

Verified TX on real hardware (RAK19007 Base + RAK4631 Core + RAK1921
OLED, US915 band): ADVERT round-trip with an external receiver.
This commit is contained in:
swaits committed 2026-04-24 00:15:29 -06:00
1 parent 0fbd8412c2
commit 647929ff8d
8 files changed
+116 -71

No files matched your search

+25 -1
View File
@@ -369,6 +369,8 @@ async fn next_packet(
}
async fn perform_tx(lora: &mut LoRaDriver, pending: &mut Option<PendingTx>) -> RadioInput {
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(6);
let Some(tx) = pending.take() else {
return RadioInput::TransmitFailed;
};
@@ -378,7 +380,11 @@ async fn perform_tx(lora: &mut LoRaDriver, pending: &mut Option<PendingTx>) -> R
airtime_us: Instant::now().duration_since(start).as_micros() as u32,
},
Ok(TxOutcome::ChannelBusy) => RadioInput::ChannelBusy,
Err(_) => RadioInput::TransmitFailed,
Err(_) => {
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(15);
RadioInput::TransmitFailed
}
}
}
@@ -705,7 +711,13 @@ async fn do_tx(lora: &mut LoRaDriver, tx: &PendingTx) -> Result<TxOutcome, Radio
let mdltn = to_lora_mod_params(lora, &tx.config)?;
if !tx.skip_cad {
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(7);
lora.prepare_for_cad(&mdltn).await?;
// Reaches here → prepare_for_cad (SPI setup) completed.
// Hang past here means lora.cad() is waiting on DIO1.
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(19);
let mut saw_activity = false;
for _ in 0..CAD_MAX_RETRIES {
if !lora.cad(&mdltn).await? {
@@ -728,9 +740,15 @@ async fn do_tx(lora: &mut LoRaDriver, tx: &PendingTx) -> Result<TxOutcome, Radio
tx.config.iq_invert,
&mdltn,
)?;
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(8);
lora.prepare_for_tx(&mdltn, &mut tx_pkt, tx.config.tx_power_dbm as i32, &tx.data)
.await?;
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(9);
lora.tx().await?;
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(10);
Ok(TxOutcome::Transmitted)
}
@@ -781,13 +799,19 @@ pub async fn radio_task(
events: &'static RadioEventChannel,
info: &'static Info,
) {
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(1);
let lora = match LoRa::new(parts.driver, false, parts.delay).await {
Ok(l) => {
info!("radio initialized");
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(2);
l
}
Err(e) => {
error!("radio init failed: {}", e);
#[cfg(feature = "debug-checkpoint")]
crate::debug_blink::set(16);
return;
}
};