221 lines
7.2 KiB
C++
221 lines
7.2 KiB
C++
#include <Arduino.h>
|
|
#include "target.h"
|
|
#include "OnboardRadioSleep.h"
|
|
#include <helpers/ArduinoHelpers.h>
|
|
#include <helpers/radiolib/Nrf52BufferedRadioHal.h>
|
|
#include <nrf_gpio.h>
|
|
#ifdef USE_CC310_HW_CRYPTO
|
|
#include <helpers/NRF52Crypto.h>
|
|
#endif
|
|
|
|
#ifndef MC_RAK3401_SPI_HZ
|
|
#define MC_RAK3401_SPI_HZ 8000000
|
|
#endif
|
|
#if MC_RAK3401_SPI_HZ > 8000000
|
|
#error "RAK3401 SPI above 8 MHz needs an unimplemented SoftDevice-compatible SPIM3 mitigation"
|
|
#elif MC_RAK3401_SPI_HZ != 2000000 && MC_RAK3401_SPI_HZ != 4000000 \
|
|
&& MC_RAK3401_SPI_HZ != 8000000
|
|
#error "RAK3401 radio SPI clock must be 2, 4 or 8 MHz"
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
// This is the optional private radio bus, NOT the external RAK13302's SPI1.
|
|
// It is only probed during board startup, before any other bus owner starts.
|
|
class OnboardRadioIO {
|
|
public:
|
|
void begin() {
|
|
digitalWrite(ONBOARD_LORA_NSS, HIGH);
|
|
pinMode(ONBOARD_LORA_NSS, OUTPUT);
|
|
digitalWrite(ONBOARD_LORA_RESET, HIGH);
|
|
pinMode(ONBOARD_LORA_RESET, OUTPUT);
|
|
digitalWrite(ONBOARD_LORA_ANT_SW, LOW);
|
|
pinMode(ONBOARD_LORA_ANT_SW, OUTPUT);
|
|
pinMode(ONBOARD_LORA_BUSY, INPUT_PULLUP);
|
|
SPI.setPins(ONBOARD_LORA_MISO, ONBOARD_LORA_SCLK, ONBOARD_LORA_MOSI);
|
|
SPI.begin();
|
|
// Reject a floating MISO rather than mistaking it for a version string.
|
|
pinMode(ONBOARD_LORA_MISO, INPUT_PULLDOWN);
|
|
}
|
|
|
|
void reset() {
|
|
digitalWrite(ONBOARD_LORA_RESET, LOW);
|
|
delay(1);
|
|
digitalWrite(ONBOARD_LORA_RESET, HIGH);
|
|
delay(5);
|
|
}
|
|
|
|
bool waitBusy(bool high, uint32_t timeout) {
|
|
const uint32_t started = millis();
|
|
while ((digitalRead(ONBOARD_LORA_BUSY) != LOW) != high) {
|
|
if (millis() - started >= timeout) return false;
|
|
delay(1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void readVersion(char* version) {
|
|
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
|
|
digitalWrite(ONBOARD_LORA_NSS, LOW);
|
|
SPI.transfer(RADIOLIB_SX126X_CMD_READ_REGISTER);
|
|
SPI.transfer(RADIOLIB_SX126X_REG_VERSION_STRING >> 8);
|
|
SPI.transfer(RADIOLIB_SX126X_REG_VERSION_STRING & 0xff);
|
|
SPI.transfer(0);
|
|
for (unsigned i = 0; i < 6; ++i) version[i] = SPI.transfer(0);
|
|
digitalWrite(ONBOARD_LORA_NSS, HIGH);
|
|
SPI.endTransaction();
|
|
}
|
|
|
|
void releaseProbePulls() {
|
|
pinMode(ONBOARD_LORA_BUSY, INPUT);
|
|
pinMode(ONBOARD_LORA_MISO, INPUT);
|
|
}
|
|
|
|
void sleep() {
|
|
SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
|
|
digitalWrite(ONBOARD_LORA_NSS, LOW);
|
|
SPI.transfer(RADIOLIB_SX126X_CMD_SET_SLEEP);
|
|
SPI.transfer(RADIOLIB_SX126X_SLEEP_START_COLD | RADIOLIB_SX126X_SLEEP_RTC_OFF);
|
|
digitalWrite(ONBOARD_LORA_NSS, HIGH);
|
|
SPI.endTransaction();
|
|
delay(1);
|
|
}
|
|
|
|
void end() {
|
|
SPI.end();
|
|
nrf_gpio_cfg_default(g_ADigitalPinMap[ONBOARD_LORA_SCLK]);
|
|
nrf_gpio_cfg_default(g_ADigitalPinMap[ONBOARD_LORA_MOSI]);
|
|
nrf_gpio_cfg_default(g_ADigitalPinMap[ONBOARD_LORA_MISO]);
|
|
nrf_gpio_cfg_default(g_ADigitalPinMap[ONBOARD_LORA_BUSY]);
|
|
// Retain NSS/reset HIGH and RF switch LOW. Releasing NSS can wake the
|
|
// sleeping SX1262; only its clock/data/BUSY pads should be disconnected.
|
|
digitalWrite(ONBOARD_LORA_NSS, HIGH);
|
|
}
|
|
};
|
|
|
|
static_assert(ONBOARD_LORA_MISO == PIN_SPI_MISO &&
|
|
ONBOARD_LORA_MOSI == PIN_SPI_MOSI &&
|
|
ONBOARD_LORA_SCLK == PIN_SPI_SCK,
|
|
"The optional radio probe must leave SPI on its default pins");
|
|
|
|
} // namespace
|
|
|
|
rak3401::OnboardRadioState rak3401::sleepOnboardRadio() {
|
|
OnboardRadioIO onboard;
|
|
#if MESH_DEBUG
|
|
const uint32_t started = millis();
|
|
#endif
|
|
// RadioLib identifies the SX1262 by the same six-byte SX1261 prefix.
|
|
const auto state = rak3401::sleepOnboardRadio(onboard, RADIOLIB_SX1262_CHIP_TYPE);
|
|
#if MESH_DEBUG
|
|
const char* outcome = state == OnboardRadioState::Absent ? "absent" :
|
|
state == OnboardRadioState::Asleep ? "asleep" :
|
|
state == OnboardRadioState::BusyTimeout ? "busy-timeout" : "sleep-failed";
|
|
MESH_DEBUG_PRINTLN("onboard SX1262: %s elapsed=%lu ms", outcome,
|
|
static_cast<unsigned long>(millis() - started));
|
|
#endif
|
|
return state;
|
|
}
|
|
|
|
RAK3401Board board;
|
|
|
|
#ifndef PIN_USER_BTN
|
|
#define PIN_USER_BTN (-1)
|
|
#endif
|
|
|
|
#ifdef DISPLAY_CLASS
|
|
DISPLAY_CLASS display;
|
|
MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true);
|
|
|
|
#if defined(PIN_USER_BTN_ANA)
|
|
MomentaryButton analog_btn(PIN_USER_BTN_ANA, 1000, 20);
|
|
#endif
|
|
#endif
|
|
|
|
// The RAK13302 is wired to the second WisBlock SPI controller. Keep the same
|
|
// controller instance for startup and watchdog recovery; resetting the radio
|
|
// must not replace or tear down the SPI object used by its Module.
|
|
// Batch each RadioLib command into one SPIM transfer rather than starting a
|
|
// transfer per byte. Keep 8 MHz by default on SPI1/SPIM2. Higher speeds need
|
|
// a separate SPIM3/SoftDevice mitigation before they can be offered safely.
|
|
// Changing the speed never replaces this bus or its recovery/handoff path.
|
|
static Nrf52BufferedRadioHal radioHal(SPI1, MC_RAK3401_SPI_HZ);
|
|
RADIO_CLASS radio = new Module(&radioHal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
|
|
|
WRAPPER_CLASS radio_driver(radio, board);
|
|
|
|
VolatileRTCClock fallback_clock;
|
|
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
|
|
|
#if ENV_INCLUDE_GPS
|
|
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
|
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
|
|
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
|
#else
|
|
EnvironmentSensorManager sensors;
|
|
#endif
|
|
|
|
static bool target_radio_available = false;
|
|
|
|
bool radio_init() {
|
|
rtc_clock.begin(Wire);
|
|
// A successful physical reset proves that the external SX1262 can release
|
|
// BUSY before RadioLib is allowed to issue SPI commands. This also prevents
|
|
// a missing RAK13302 from turning an initialization retry into a watchdog
|
|
// reset loop.
|
|
if (!board.recoverRadio()) {
|
|
target_radio_available = false;
|
|
mesh::usbLoggingPort().println(
|
|
"ERROR: RAK13302 BUSY stayed high after reset; check module seating and power");
|
|
return false;
|
|
}
|
|
|
|
if (radio.std_init(&SPI1)) {
|
|
target_radio_available = true;
|
|
board.enableRadioFrontend();
|
|
return true;
|
|
}
|
|
|
|
// Retry once after another physical reset. Keep the shared GPS/sensor rail
|
|
// and the SPI controller instance intact throughout recovery.
|
|
if (!board.recoverRadio()) {
|
|
target_radio_available = false;
|
|
return false;
|
|
}
|
|
if (!radio.std_init(&SPI1)) {
|
|
target_radio_available = false;
|
|
return false;
|
|
}
|
|
target_radio_available = true;
|
|
board.enableRadioFrontend();
|
|
return true;
|
|
}
|
|
|
|
uint32_t radio_fallback_rng_seed() {
|
|
uint32_t seed = micros() ^ NRF_FICR->DEVICEID[0] ^ NRF_FICR->DEVICEID[1];
|
|
#ifdef USE_CC310_HW_CRYPTO
|
|
mesh::mixCC310Random(reinterpret_cast<uint8_t*>(&seed), sizeof(seed));
|
|
#endif
|
|
return seed;
|
|
}
|
|
|
|
mesh::LocalIdentity radio_new_identity() {
|
|
if (target_radio_available) {
|
|
RadioNoiseListener rng(radio);
|
|
return mesh::LocalIdentity(&rng);
|
|
}
|
|
|
|
class RAK3401IdentityRNG : public mesh::RNG {
|
|
public:
|
|
void random(uint8_t* dest, size_t sz) override {
|
|
for (size_t i = 0; i < sz; ++i) {
|
|
dest[i] = static_cast<uint8_t>(::random(0, 256));
|
|
}
|
|
#ifdef USE_CC310_HW_CRYPTO
|
|
mesh::mixCC310Random(dest, sz);
|
|
#endif
|
|
}
|
|
} rng;
|
|
return mesh::LocalIdentity(&rng);
|
|
}
|