Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 208 additions & 9 deletions variants/rak4631/RAK4631Board.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

#include "RAK4631Board.h"

#if __has_include(<bluefruit.h>)
#include <bluefruit.h>
#endif

#ifdef ETHERNET_ENABLED
// Drive WB_IO2 HIGH as early as possible using direct register access.
// WB_IO2 (P1.02, Arduino pin 34) controls the WisBlock slot power switch.
Expand All @@ -25,31 +29,210 @@ const PowerMgtConfig power_config = {
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
};

#if defined(PIN_USER_BTN_ANA) && (PIN_USER_BTN_ANA >= 0)
#define BOARD_BUTTON_PIN PIN_USER_BTN_ANA
#elif defined(WB_A1)
#define BOARD_BUTTON_PIN WB_A1
#elif defined(PIN_USER_BTN) && (PIN_USER_BTN >= 0)
#define BOARD_BUTTON_PIN PIN_USER_BTN
#endif

static bool isButtonPressed() {
#if defined(PIN_USER_BTN_ANA) && (PIN_USER_BTN_ANA >= 0)
return analogRead(PIN_USER_BTN_ANA) < 20;
#elif defined(BOARD_BUTTON_PIN)
return digitalRead(BOARD_BUTTON_PIN) == LOW;
#else
return false;
#endif
}

void RAK4631Board::powerOff() {
#if defined(BOARD_BUTTON_PIN)
// Turn on LED to indicate long press was recognized and signal to release
#if defined(PIN_LED1)
digitalWrite(PIN_LED1, HIGH);
#elif defined(PIN_LED2)
digitalWrite(PIN_LED2, HIGH);
#endif

// Wait until the user physically releases the button and it remains stably released
uint32_t stable_start = millis();
while (millis() - stable_start < 100) {
if (isButtonPressed()) {
stable_start = millis();
}
delay(5);
}

// Turn off LEDs before shutdown
#if defined(PIN_LED1)
digitalWrite(PIN_LED1, LOW);
#endif
#if defined(PIN_LED2)
digitalWrite(PIN_LED2, LOW);
#endif
#endif

#ifdef NRF52_POWER_MANAGEMENT
initiateShutdown(SHUTDOWN_REASON_USER);
#else
NRF52BoardDCDC::powerOff();
#endif
}

void RAK4631Board::initiateShutdown(uint8_t reason) {
#if __has_include(<bluefruit.h>)
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
Bluefruit.autoConnLed(false);
uint16_t conn_hdl = Bluefruit.connHandle();
if (conn_hdl != BLE_CONN_HANDLE_INVALID) {
Bluefruit.disconnect(conn_hdl);
delay(50);
}
Bluefruit.Advertising.stop();
}
#endif

#if defined(DISPLAY_CLASS)
// The OLED stays powered from 3V3 in SYSTEMOFF and keeps showing its last frame,
// which makes a successful power-off look like a freeze. Paths such as the
// long-press and the CLI "poweroff" command bypass UITask::shutdown(), so blank
// the panel here: display off (0xAE) and charge pump off (0x8D, 0x10).
#ifndef DISPLAY_ADDRESS
#define DISPLAY_ADDRESS 0x3C
#endif
Wire.beginTransmission(DISPLAY_ADDRESS);
Wire.write(0x00); // control byte: command stream
Wire.write(0xAE);
Wire.write(0x8D);
Wire.write(0x10);
Wire.endTransmission();
#endif

// Disconnect LoRa pins and detach RadioLib interrupts before powering down
// Otherwise, the collapsing power rail on the SX1262 triggers a false DETECT wakeup
detachInterrupt(digitalPinToInterrupt(P_LORA_DIO_1));
#ifdef P_LORA_BUSY
detachInterrupt(digitalPinToInterrupt(P_LORA_BUSY));
nrf_gpio_cfg_default(g_ADigitalPinMap[P_LORA_BUSY]);
#endif
#ifdef P_LORA_RESET
nrf_gpio_cfg_default(g_ADigitalPinMap[P_LORA_RESET]);
#endif
#ifdef P_LORA_NSS
nrf_gpio_cfg_default(g_ADigitalPinMap[P_LORA_NSS]);
#endif
nrf_gpio_cfg_default(g_ADigitalPinMap[P_LORA_DIO_1]);

// Disable LoRa module power before shutdown
digitalWrite(SX126X_POWER_EN, LOW);

#if defined(BOARD_BUTTON_PIN)
#if defined(PIN_USER_BTN_ANA) && (PIN_USER_BTN_ANA >= 0)
// analogRead() disables the SAADC afterwards but leaves its channel inputs
// selecting the button's AIN pin. While selected, the pin's digital input reads 0,
// so the settle loop below never exits and SENSE could never see a press.
// Release every channel so the button pin works as a digital input again.
for (int i = 0; i < 8; i++) {
NRF_SAADC->CH[i].PSELP = SAADC_CH_PSELP_PSELP_NC;
NRF_SAADC->CH[i].PSELN = SAADC_CH_PSELN_PSELN_NC;
}
#endif
// Ensure pullup is engaged and line is stably HIGH before arming SENSE (with 3s safety timeout)
pinMode(BOARD_BUTTON_PIN, INPUT_PULLUP);
uint32_t settle_start = millis();
uint32_t settle_timeout = millis();
while (millis() - settle_start < 50 && millis() - settle_timeout < 3000) {
if (digitalRead(BOARD_BUTTON_PIN) == LOW) {
settle_start = millis();
}
delay(5);
}

// Ensure no other pin has SENSE enabled
for (uint32_t p = 0; p < 32; p++) {
if (p != g_ADigitalPinMap[BOARD_BUTTON_PIN]) {
nrf_gpio_cfg_sense_set(p, NRF_GPIO_PIN_NOSENSE);
}
}
#if defined(NRF_P1)
for (uint32_t p = 32; p < 48; p++) {
if (p != g_ADigitalPinMap[BOARD_BUTTON_PIN]) {
nrf_gpio_cfg_sense_set(p, NRF_GPIO_PIN_NOSENSE);
}
}
#endif

nrf_gpio_cfg_sense_input(g_ADigitalPinMap[BOARD_BUTTON_PIN], NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
#endif

if (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
reason == SHUTDOWN_REASON_BOOT_PROTECT) {
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
}

enterSystemOff(reason);
// Record shutdown reason in GPREGRET2
uint8_t sd_on = 0;
sd_softdevice_is_enabled(&sd_on);
if (sd_on) {
sd_power_gpregret_clr(1, 0xFF);
sd_power_gpregret_set(1, reason);
} else {
NRF_POWER->GPREGRET2 = reason;
}

Serial.flush();
delay(50);

// Ensure all LEDs are explicitly powered off and disconnected right before entering sleep
#if defined(PIN_LED1)
digitalWrite(PIN_LED1, LOW);
pinMode(PIN_LED1, INPUT);
nrf_gpio_pin_clear(g_ADigitalPinMap[PIN_LED1]);
nrf_gpio_cfg_default(g_ADigitalPinMap[PIN_LED1]);
#endif
#if defined(PIN_LED2)
digitalWrite(PIN_LED2, LOW);
pinMode(PIN_LED2, INPUT);
nrf_gpio_pin_clear(g_ADigitalPinMap[PIN_LED2]);
nrf_gpio_cfg_default(g_ADigitalPinMap[PIN_LED2]);
#endif

#if defined(BOARD_BUTTON_PIN)
// Ensure the button is unpressed before clearing latch (with 3s safety timeout)
uint32_t release_timeout = millis();
while (digitalRead(BOARD_BUTTON_PIN) == LOW && millis() - release_timeout < 3000) {
delay(10);
}
#endif

// Clear any latched GPIO DETECT events right before sleep
NRF_P0->LATCH = 0xFFFFFFFF;
#if defined(NRF_P1)
NRF_P1->LATCH = 0xFFFFFFFF;
#endif

if (sd_on) {
sd_power_system_off();
}
// Force hardware system off directly
NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter;
while (1) {
NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter;
}
}
#endif // NRF52_POWER_MANAGEMENT

void RAK4631Board::begin() {
NRF52BoardDCDC::begin();
pinMode(PIN_VBAT_READ, INPUT);
#ifdef PIN_USER_BTN
pinMode(PIN_USER_BTN, INPUT_PULLUP);
#endif

#ifdef PIN_USER_BTN_ANA
pinMode(PIN_USER_BTN_ANA, INPUT_PULLUP);
#if defined(BOARD_BUTTON_PIN)
pinMode(BOARD_BUTTON_PIN, INPUT_PULLUP);
#endif


#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif
Expand All @@ -64,4 +247,20 @@ void RAK4631Board::begin() {
#endif
digitalWrite(SX126X_POWER_EN, HIGH);
delay(10); // give sx1262 some time to power up
}
}

void RAK4631Board::loop() {
NRF52BoardDCDC::loop();

#if defined(BOARD_BUTTON_PIN)
if (isButtonPressed()) {
if (btn_press_start == 0) {
btn_press_start = millis();
} else if (millis() - btn_press_start >= 3000) {
powerOff();
}
} else {
btn_press_start = 0;
}
#endif
}
4 changes: 4 additions & 0 deletions variants/rak4631/RAK4631Board.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@

class RAK4631Board : public NRF52BoardDCDC {
protected:
uint32_t btn_press_start = 0;
#ifdef NRF52_POWER_MANAGEMENT
void initiateShutdown(uint8_t reason) override;
#endif

public:
RAK4631Board() : NRF52Board("RAK4631_OTA") {}
void begin();
void loop() override;

#define BATTERY_SAMPLES 8

Expand All @@ -35,4 +37,6 @@ class RAK4631Board : public NRF52BoardDCDC {
const char* getManufacturerName() const override {
return "RAK 4631";
}

void powerOff() override;
};
1 change: 1 addition & 0 deletions variants/rak4631/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ extern "C"
static const uint8_t A7 = PIN_A7;
#define ADC_RESOLUTION 14

#define NRF52_POWER_MANAGEMENT
// Power management boot protection threshold (millivolts)
// Set to 0 to disable boot protection
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV)
Expand Down