Detect header W25Q16 on unified RAK firmware

This commit is contained in:
mikecarper committed 2026-09-25 14:20:29 -07:00
1 parent cecddf4bae
commit 9127f8fe25
11 files changed
+111 -8

No files matched your search

+8
View File
@@ -334,6 +334,14 @@ occupied. See the
[nRF52 QSPI guide](ota_nrf52_qspi.md#one-w25q16-wiring-for-rak4631-or-rak3401--rak13302)
before installing either image.
For a build that uses only the 2.54 mm headers, use the unified
`RAK_4631_repeater_unified_lora_ota` or
`RAK_3401_repeater_unified_lora_ota` image with its matching adaptive
OTAFIX 2.4.8 bootloader. Wire `CLK` to J10 `TX1`, `DO` to J10 `RX1`, `DI` to
J11 `IO1`, and `CS` to J11 `AIN1`; keep the `CS` pull-up and J12 power wiring.
This gives up UART1 GPS/PPS but leaves I2C sensors and the RAK3401 radio
available. See the [header-only wiring table](ota_nrf52_qspi.md#header-only-w25q16-wiring-for-the-unified-rak-images).
### 4. Build and check the in-place delta
```bash
+24
View File
@@ -143,6 +143,30 @@ core:
The physical flash wiring is identical, but the two application and
bootloader pairs are not interchangeable.
### Header-only W25Q16 wiring for the unified RAK images
The unified RAK repeater images and matching OTAFIX 2.4.8 adaptive bootloaders
also support a W25Q16 through the RAK19007 2.54 mm headers. This arrangement
does not need an IO-connector interposer or underside SPI solder points:
| W25Q16 breakout | RAK19007 header | nRF52840 / Arduino pin |
| --- | --- | --- |
| `CLK` | J10 `TX1` | P0.16 / `16` |
| `DO` / MISO | J10 `RX1` | P0.15 / `15` |
| `DI` / MOSI | J11 `IO1` | P0.17 / `17` |
| `CS` | J11 `AIN1` | P0.31 / `31` |
| `VCC` | J12 `VDD` | regulated 3.3 V |
| `GND` | J12 `GND` | ground |
Keep the approximately 10 kOhm `CS`-to-`VDD` pull-up. This mapping gives up
the UART1 GPS and PPS pins while the flash is fitted. The unified firmware
detects the exact `EF4015` device before GPS discovery and leaves I2C sensor
pins `SDA` and `SCL` available. RAK4631 can still use its UART2 bridge; its
UART1 bridge is unavailable with header flash. RAK3401 retains its 1 W radio
and shared sensor/radio power rail. Do not substitute `SCL` for flash clock or
use `SWCLK`, which is the debug clock. The older dedicated W25Q16 images above
expect the WisBlock SPI connector wiring and do not use this header mapping.
## One-time prerequisite
Install a QSPI-capable OTAFIX 2.4.1 preview.9 or newer bootloader for the exact
+5
View File
@@ -104,6 +104,11 @@ power the flash from VBAT. The three SPI signals require underside pads or an
IO-connector interposer; the easy J10/J11/J12 headers do not expose them. The
flash consumes no WisBlock slot, so a supported GPS can remain in slot A.
The unified RAK repeater images additionally support a header-only W25Q16
layout, using J10 `TX1`/`RX1` and J11 `IO1`/`AIN1`. This layout needs the
matching adaptive OTAFIX 2.4.8 bootloader and gives up UART1 GPS/PPS; I2C
sensors remain available. See the [pin table](ota_nrf52_qspi.md#header-only-w25q16-wiring-for-the-unified-rak-images).
The ordinary full-sensor `RAK_4631_repeater` image remains too large for the
safe internal in-place update limit. Without external flash, use
`RAK_4631_repeater_lora_ota_no_external_sensors`; it trims selected optional
+5
View File
@@ -1197,6 +1197,11 @@ bool ota_apply_mota_nrf52(OtaStoreQspiNrf52& store, const SignerAllowlist& allow
if (!ota_apply_mota_nrf52_external(store, allow, OTA_BL_STORAGE_QSPI, "QSPI", false, st, msg)) {
return false;
}
#if defined(OTA_RAK_AUTO_STORE)
if (OtaStoreQspiNrf52::headerW25Detected()) {
g_nrf52_qspi_handoff = GPREGRET2_OTA_STAGE_HEADER_W25;
}
#endif
#if defined(OTA_RAK_AUTO_STORE) && !defined(RAK_3401)
if (store.jedec_id() == 0xC84015UL) {
OtaBootloaderIdentity identity;
+2 -1
View File
@@ -84,7 +84,8 @@ static const uint8_t OTA_BL_STORAGE_SD = 0x01;
static const uint8_t OTA_BL_STORAGE_STAGE_CEILING = 0x02;
static const uint8_t OTA_BL_STORAGE_QSPI = 0x04;
static const uint8_t OTA_BL_STORAGE_BOOT_UPDATE = 0x08;
static const uint8_t OTA_BL_STORAGE_KNOWN = 0x0F;
static const uint8_t OTA_BL_STORAGE_HEADER_W25 = 0x10;
static const uint8_t OTA_BL_STORAGE_KNOWN = 0x1F;
static const uint8_t OTA_BL_PROFILE_SD_BOOT_UPDATE =
OTA_BL_STORAGE_SD | OTA_BL_STORAGE_BOOT_UPDATE;
static const uint8_t OTA_BL_PROFILE_INTERNAL_BOOT_UPDATE =
+1
View File
@@ -56,6 +56,7 @@ static const uint8_t GPREGRET2_OTA_STAGE_LEGACY = 0xD4u;
static const uint8_t GPREGRET2_OTA_STAGE_EXPANDED = 0xEDu;
static const uint8_t GPREGRET2_OTA_STAGE_QSPI = 0x51u;
static const uint8_t GPREGRET2_OTA_STAGE_RAK15001 = 0x52u;
static const uint8_t GPREGRET2_OTA_STAGE_HEADER_W25 = 0x54u;
static const uint8_t GPREGRET2_OTA_STAGE_SD = 0x53u;
static const uint8_t GPREGRET2_OTA_STAGE_HYBRID = 0xA6u;
+8 -1
View File
@@ -50,6 +50,12 @@ class OtaStoreAdaptiveNrf52 : public OtaStore {
}
const bool qspi_bootloader = caps.present &&
(caps.storage_flags & OTA_BL_STORAGE_QSPI) != 0;
if (detected == 2u && OtaStoreQspiNrf52::headerW25Detected() &&
(caps.storage_flags & OTA_BL_STORAGE_HEADER_W25) == 0) {
activateInternal();
_reason = "header W25 needs newer OTAFIX bootloader";
return;
}
OtaBootloaderIdentity identity;
const bool identity_valid = qspi_bootloader &&
ota_installed_bootloader_identity(identity) && identity.crc_ok;
@@ -69,7 +75,8 @@ class OtaStoreAdaptiveNrf52 : public OtaStore {
}
if (choice == RakStorageChoice::Qspi) {
activateExternal();
_reason = detected == 1u ? "RAK15001 C" : "W25Q16";
_reason = detected == 1u ? "RAK15001 C" :
OtaStoreQspiNrf52::headerW25Detected() ? "W25Q16 header" : "W25Q16 SPI";
return;
}
_mode = UNSAFE;
+40 -4
View File
@@ -25,6 +25,12 @@ static uint32_t auto_jedec_id = 0;
static uint8_t auto_detection = 0xFF;
static uint32_t auto_rak_probe_id = 0;
static uint32_t auto_w25_probe_id = 0;
// The header-connected W25Q16 uses J10 TX1/RX1 and J11 IO1/AIN1. It is
// mutually exclusive with GPS on this board but leaves both I2C pins free.
static bool auto_header_w25 = false;
static const uint8_t HEADER_SCK_PIN = 16; // J10 TX1, P0.16
static const uint8_t HEADER_IO0_PIN = 17; // J11 IO1, P0.17, W25 DI
static const uint8_t HEADER_IO1_PIN = 15; // J10 RX1, P0.15, W25 DO
#endif
#if defined(OTA_QSPI_SCK_PHYSICAL_PIN) && defined(OTA_QSPI_SCK_ARDUINO_PIN)
@@ -100,6 +106,9 @@ static uint8_t arduino_to_physical(uint32_t arduino_pin) {
}
static uint8_t qspi_sck_pin() {
#if defined(OTA_RAK_AUTO_STORE)
if (auto_header_w25) return arduino_to_physical(HEADER_SCK_PIN);
#endif
#ifdef OTA_QSPI_SCK_PHYSICAL_PIN
return OTA_QSPI_SCK_PHYSICAL_PIN;
#else
@@ -120,6 +129,9 @@ static uint8_t qspi_cs_pin() {
}
static uint8_t qspi_io0_pin() {
#if defined(OTA_RAK_AUTO_STORE)
if (auto_header_w25) return arduino_to_physical(HEADER_IO0_PIN);
#endif
#ifdef OTA_QSPI_IO0_PHYSICAL_PIN
return OTA_QSPI_IO0_PHYSICAL_PIN;
#else
@@ -128,6 +140,9 @@ static uint8_t qspi_io0_pin() {
}
static uint8_t qspi_io1_pin() {
#if defined(OTA_RAK_AUTO_STORE)
if (auto_header_w25) return arduino_to_physical(HEADER_IO1_PIN);
#endif
#ifdef OTA_QSPI_IO1_PHYSICAL_PIN
return OTA_QSPI_IO1_PHYSICAL_PIN;
#else
@@ -320,18 +335,34 @@ uint8_t OtaStoreQspiNrf52::autoDetect() {
pinMode(P_LORA_NSS, OUTPUT);
digitalWrite(P_LORA_NSS, HIGH);
SPI1.end();
const uint32_t w25_id = probe_nor(w25_cs);
auto_w25_probe_id = w25_id;
uint32_t w25_id = probe_nor(w25_cs);
SPI1.begin();
auto_detection = w25_id == 0xEF4015UL ? 2u : 0u;
#else
const uint8_t rak_cs = arduino_to_physical(26);
nrf_gpio_pin_set(rak_cs);
nrf_gpio_cfg_output(rak_cs);
const uint32_t rak_id = probe_nor(rak_cs);
const uint32_t w25_id = probe_nor(w25_cs);
uint32_t w25_id = probe_nor(w25_cs);
auto_rak_probe_id = rak_id;
#endif
if (w25_id != 0xEF4015UL) {
// GPS discovery calls this one-time probe before it starts UART1. Ending
// an unopened Adafruit UART can block indefinitely, so leave it alone.
auto_header_w25 = true;
const uint32_t header_id = probe_nor(w25_cs);
if (header_id == 0xEF4015UL) {
w25_id = header_id;
} else {
auto_header_w25 = false;
nrf_gpio_cfg_default(arduino_to_physical(HEADER_SCK_PIN));
nrf_gpio_cfg_default(arduino_to_physical(HEADER_IO0_PIN));
nrf_gpio_cfg_default(arduino_to_physical(HEADER_IO1_PIN));
}
}
auto_w25_probe_id = w25_id;
#if defined(RAK_3401)
auto_detection = w25_id == 0xEF4015UL ? 2u : 0u;
#else
const bool rak = rak_id == 0xC84015UL;
const bool w25 = w25_id == 0xEF4015UL;
auto_detection = rak && w25 ? 3u : rak ? 1u : w25 ? 2u : 0u;
@@ -346,6 +377,11 @@ uint8_t OtaStoreQspiNrf52::autoDetect() {
return auto_detection;
}
bool OtaStoreQspiNrf52::headerW25Detected() {
autoDetect();
return auto_header_w25;
}
void OtaStoreQspiNrf52::autoProbeIds(uint32_t& rak15001, uint32_t& w25q16) {
autoDetect();
rak15001 = auto_rak_probe_id;
+1
View File
@@ -170,6 +170,7 @@ public:
// Probe exact supported NOR parts before activating the nRF QSPI block.
// 0 = absent/unsupported, 1 = RAK15001 C, 2 = W25Q16, 3 = ambiguous.
static uint8_t autoDetect();
static bool headerW25Detected();
// Raw read-only JEDEC results from the cached startup probe. Useful when a
// connected breakout is not one of the exact supported parts.
static void autoProbeIds(uint32_t& rak15001, uint32_t& w25q16);
@@ -2,6 +2,9 @@
#include "I2CAddressClaimPolicy.h"
#include "EnvironmentI2CConfig.h"
#include "NmeaSentenceProbe.h"
#if defined(OTA_RAK_AUTO_STORE) && defined(RAK_BOARD)
#include "../ota/OtaStoreQspiNrf52.h"
#endif
#include <Wire.h>
@@ -1226,6 +1229,18 @@ void EnvironmentSensorManager::initBasicGPS() {
// or make a new location provider ...
#ifdef RAK_WISBLOCK_GPS
void EnvironmentSensorManager::rakGPSInit() {
#if defined(OTA_RAK_AUTO_STORE) && defined(RAK_BOARD)
// The 2.54 mm header W25Q16 uses the UART1 and PPS pads. Probe before
// starting GPS discovery so UART1 never drives a NOR data/clock wire.
if (mesh::ota::OtaStoreQspiNrf52::headerW25Detected()) {
gps_active = false;
gps_detected = false;
gps_serial_transport = false;
i2cGPSFlag = false;
serialGPSFlag = false;
return;
}
#endif
// Preserve the established owner across repeated begin() calls. Clearing
// this flag and starting Serial1 here would silently take UART1 back from an
// active bridge.
+2 -2
View File
@@ -47,8 +47,8 @@ void initVariant()
ledOff(PIN_LED2);;
#if defined(OTA_RAK_AUTO_STORE)
// Both candidates share SCK/MOSI/MISO. Hold both chip selects high until
// the exact JEDEC probe chooses one; W25Q16 also needs its reset pull-up.
// Keep both chip selects high through startup. RAK15001 and native W25Q16
// use the WisBlock SPI bus; a header W25Q16 uses TX1/RX1/IO1 instead.
pinMode(26, OUTPUT);
digitalWrite(26, HIGH);
pinMode(31, OUTPUT);