Skip to content
Closed
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
194 changes: 106 additions & 88 deletions src/Power.cpp

Large diffs are not rendered by default.

52 changes: 51 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "ReliableRouter.h"
#include "airtime.h"
#include "buzz.h"
#include "power/PowerHAL.h"

#include "FSCommon.h"
#include "Led.h"
Expand Down Expand Up @@ -288,6 +289,46 @@ __attribute__((weak, noinline)) bool loopCanSleep()
void lateInitVariant() __attribute__((weak));
void lateInitVariant() {}

// NRF52 (and probably other platforms) can report when system is in power failure mode
// (eg. too low battery voltage) and operating it is unsafe (data corruption, bootloops, etc).
// For example NRF52 will prevent any flash writes in that case automatically
// (but it causes issues we need to handle).
// This detection is independent from whatever ADC or dividers used in Meshtastic
// boards and is internal to chip.

// we use powerHAL layer to get this info and delay booting until power level is safe

// wait until power level is safe to continue booting (to avoid bootloops)
// blink user led in 3 flashes sequence to indicate what is happening
void waitUntilPowerLevelSafe()
{

// TODO: do not use delay but RTC/IRQ whatever so we don't burn
// energy which is already scarce

#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
#endif

while (powerHAL_isPowerLevelSafe() == false) {

#ifdef LED_PIN

// 3x: blink for 500 ms, pause for 500 ms

for (int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, LED_STATE_ON);
delay(300);
digitalWrite(LED_PIN, LED_STATE_OFF);
delay(300);
}
#endif

// sleep for 2s
delay(2000);
}
}

/**
* Print info as a structured log message (for automated log processing)
*/
Expand All @@ -298,6 +339,15 @@ void printInfo()
#ifndef PIO_UNIT_TESTING
void setup()
{

// initialize power HAL layer as early as possible
// for NRF52 this also initializes SoftDevice framework
powerHAL_init();

// prevent booting if device is in power failure mode
// boot sequence will follow when battery level raises to safe mode
waitUntilPowerLevelSafe();

#if defined(R1_NEO)
pinMode(DCDC_EN_HOLD, OUTPUT);
digitalWrite(DCDC_EN_HOLD, HIGH);
Expand Down Expand Up @@ -445,7 +495,7 @@ void setup()
serialSinceMsec = millis();

LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");

#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
#ifndef SENSECAP_INDICATOR
// use PSRAM for malloc calls > 256 bytes
Expand Down
51 changes: 51 additions & 0 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "NodeDB.h"
#include "PacketHistory.h"
#include "PowerFSM.h"
#include <power/PowerHAL.h>
#include "RTC.h"
#include "Router.h"
#include "SPILock.h"
Expand Down Expand Up @@ -1382,6 +1383,14 @@ void NodeDB::loadFromDisk()
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
bool fullAtomic)
{

// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
// and all writes will fail anyway. Device should be sleeping at this point anyway.
if(!powerHAL_isPowerLevelSafe()){
LOG_ERROR("Error: trying to saveProto() on unsafe device power level.");
return false;
}

bool okay = false;
#ifdef FSCom
auto f = SafeFile(filename, fullAtomic);
Expand All @@ -1408,6 +1417,14 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_

bool NodeDB::saveChannelsToDisk()
{

// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
// and all writes will fail anyway.
if(!powerHAL_isPowerLevelSafe()){
LOG_ERROR("Error: trying to saveChannelsToDisk() on unsafe device power level.");
return false;
}

#ifdef FSCom
spiLock->lock();
FSCom.mkdir("/prefs");
Expand All @@ -1418,6 +1435,15 @@ bool NodeDB::saveChannelsToDisk()

bool NodeDB::saveDeviceStateToDisk()
{

// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
// and all writes will fail anyway. Device should be sleeping at this point anyway.
if(!powerHAL_isPowerLevelSafe()){
LOG_ERROR("Error: trying to saveDeviceStateToDisk() on unsafe device power level.");
return false;
}


#ifdef FSCom
spiLock->lock();
FSCom.mkdir("/prefs");
Expand All @@ -1430,6 +1456,15 @@ bool NodeDB::saveDeviceStateToDisk()

bool NodeDB::saveNodeDatabaseToDisk()
{

// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
// and all writes will fail anyway. Device should be sleeping at this point anyway.
if(!powerHAL_isPowerLevelSafe()){
LOG_ERROR("Error: trying to saveNodeDatabaseToDisk() on unsafe device power level.");
return false;
}


#ifdef FSCom
spiLock->lock();
FSCom.mkdir("/prefs");
Expand All @@ -1442,6 +1477,14 @@ bool NodeDB::saveNodeDatabaseToDisk()

bool NodeDB::saveToDiskNoRetry(int saveWhat)
{

// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
// and all writes will fail anyway. Device should be sleeping at this point anyway.
if(!powerHAL_isPowerLevelSafe()){
LOG_ERROR("Error: trying to saveToDiskNoRetry() on unsafe device power level.");
return false;
}

bool success = true;
#ifdef FSCom
spiLock->lock();
Expand Down Expand Up @@ -1497,6 +1540,14 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
bool NodeDB::saveToDisk(int saveWhat)
{
LOG_DEBUG("Save to disk %d", saveWhat);

// do not try to save anything if power level is not safe. In many cases flash will be lock-protected
// and all writes will fail anyway. Device should be sleeping at this point anyway.
if(!powerHAL_isPowerLevelSafe()){
LOG_ERROR("Error: trying to saveToDisk() on unsafe device power level.");
return false;
}

bool success = saveToDiskNoRetry(saveWhat);

if (!success) {
Expand Down
19 changes: 19 additions & 0 deletions src/platform/nrf52/architecture.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@
//
// defaults for NRF52 architecture
//

/*
* Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
*
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
* VDD/4, VDD/2 or VDD for the ADC levels.
*
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
* Some variants overwrite it.
*/
#ifndef AREF_VOLTAGE
#define AREF_VOLTAGE 3.6
#endif

#ifndef BATTERY_SENSE_RESOLUTION_BITS
#define BATTERY_SENSE_RESOLUTION_BITS 10
#endif

#ifndef HAS_BLUETOOTH
#define HAS_BLUETOOTH 1
#endif
Expand Down
114 changes: 92 additions & 22 deletions src/platform/nrf52/main-nrf52.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,22 @@
#include "main.h"
#include "meshUtils.h"
#include "power.h"
#include <power/PowerHAL.h>

#include <hal/nrf_lpcomp.h>

#ifdef BQ25703A_ADDR
#include "BQ25713.h"
#endif

#ifndef SAFE_VDD_VOLTAGE_THRESHOLD
#define SAFE_VDD_VOLTAGE_THRESHOLD 2.7
#endif

#ifndef SAFE_VDD_VOLTAGE_THRESHOLD_HIST
#define SAFE_VDD_VOLTAGE_THRESHOLD_HOST 0.2
#endif

// Weak empty variant initialization function.
// May be redefined by variant files.
void variant_shutdown() __attribute__((weak));
Expand All @@ -38,12 +47,76 @@ void variant_shutdown() {}
static nrfx_wdt_t nrfx_wdt = NRFX_WDT_INSTANCE(0);
static nrfx_wdt_channel_id nrfx_wdt_channel_id_nrf52_main;

// This is a public global so that the debugger can set it to false automatically from our gdbinit
// @phaseloop comment: most part of codebase, including filesystem flash driver depend on softdevice
// methods so disabling it may actually crash thing. Proceed with caution.

bool useSoftDevice = true; // Set to false for easier debugging

static inline void debugger_break(void)
{
__asm volatile("bkpt #0x01\n\t"
"mov pc, lr\n\t");
}

// PowerHAL NRF52 specific function implementations
bool powerHAL_isVBUSConnected()
{
return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
}

bool powerHAL_isPowerLevelSafe()
{

uint16_t threshhold = SAFE_VDD_VOLTAGE_THRESHOLD * 1000; // convert V to mV



return true;
}

void powerHAL_platformInit()
{

// Enable POF power failure comparator. It will prevent writing to NVMC flash when supply voltage is too low.
// Set to 2.4V as last resort - powerHAL_isPowerLevelSafe uses different method and should manage proper node behaviour on its
// own.

// @phaseloop note: during my tests - setting threshold to 2.7V would still trigger POFWARN only at 2.5V fed to VDDH (??).
// According to datasheet, below 2.8V setting both VDD and VDDH level are covered by this register. So feeding 2.5V to VDDH
// would result at 2.2V at VDD (because LDO voltage drop) which is even weirder. Anyway we don't rely much on POFWARN.

// POFWARN is pretty useless for node power management because it triggers only once and clearing this event will not
// re-trigger it again until voltage rises to safe level and drops again. So we will use SAADC routed to VDD to read safely
// voltage.

NRF_POWER->POFCON =
((POWER_POFCON_THRESHOLD_V24 << POWER_POFCON_THRESHOLD_Pos) | (POWER_POFCON_POF_Enabled << POWER_POFCON_POF_Pos));

// remember to always match VBAT_AR_INTERNAL with AREF_VALUE in varian definition file
#ifdef VBAT_AR_INTERNAL
analogReference(VBAT_AR_INTERNAL);
#else
analogReference(AR_INTERNAL); // 3.6V
#endif
}

// get VDD voltage (in millivolts)
uint16_t getVDDVoltage()
{
// some variants use AREF_VOLTAGE as 3.0
// but this is fine as we usually hunt for VDD values less than 3V
// otherwise either set here the reference for each measure and make sure
// same is done for battery reading (which long term should be implemented here as HAL function)

// we use the same values as regular battery read so there is no conflict on SAADC
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);

uint16_t vddADCRead = analogReadVDD();
float voltage = ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * vddADCRead;
return voltage;
}

bool loopCanSleep()
{
// turn off sleep only while connected via USB
Expand Down Expand Up @@ -72,22 +145,6 @@ void getMacAddr(uint8_t *dmac)
dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack
}

static void initBrownout()
{
auto vccthresh = POWER_POFCON_THRESHOLD_V24;

auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled);
assert(err_code == NRF_SUCCESS);

err_code = sd_power_pof_threshold_set(vccthresh);
assert(err_code == NRF_SUCCESS);

// We don't bother with setting up brownout if soft device is disabled - because during production we always use softdevice
}

// This is a public global so that the debugger can set it to false automatically from our gdbinit
bool useSoftDevice = true; // Set to false for easier debugging

#if !MESHTASTIC_EXCLUDE_BLUETOOTH
void setBluetoothEnable(bool enable)
{
Expand All @@ -106,7 +163,6 @@ void setBluetoothEnable(bool enable)
if (!initialized) {
nrf52Bluetooth = new NRF52Bluetooth();
nrf52Bluetooth->startDisabled();
initBrownout();
initialized = true;
}
return;
Expand All @@ -120,9 +176,6 @@ void setBluetoothEnable(bool enable)
LOG_DEBUG("Init NRF52 Bluetooth");
nrf52Bluetooth = new NRF52Bluetooth();
nrf52Bluetooth->setup();

// We delay brownout init until after BLE because BLE starts soft device
initBrownout();
}
// Already setup, apparently
else
Expand Down Expand Up @@ -192,9 +245,24 @@ extern "C" void lfs_assert(const char *reason)
delay(500); // Give the serial port a bit of time to output that last message.
// Try setting GPREGRET with the SoftDevice first. If that fails (perhaps because the SD hasn't been initialize yet) then set
// NRF_POWER->GPREGRET directly.
if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS && sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) {
NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT;

// TODO: this will/can crash CPU if bluetooth stack is not compiled in or bluetooth is not initialized
// (regardless if enabled or disabled) - as there is no live SoftDevice stack
// implement "safe" functions detecting softdevice stack state and using proper method to set registers

// do not set GPREGRET if POFWARN is triggered because it means lfs_assert reports flash undervoltage protection
// and not data corruption. Reboot is fine as boot procedure will wait until power level is safe again

if (powerHAL_isPowerLevelSafe()) {
if (!(sd_power_gpregret_clr(0, 0xFF) == NRF_SUCCESS &&
sd_power_gpregret_set(0, NRF52_MAGIC_LFS_IS_CORRUPT) == NRF_SUCCESS)) {
NRF_POWER->GPREGRET = NRF52_MAGIC_LFS_IS_CORRUPT;
}
}

// TODO: this should not be done when SoftDevice is enabled as device will not boot back on soft reset
// as some data is retained in RAM which will prevent re-enabling bluetooth stack
// Google what Nordic has to say about NVIC_* + SoftDevice
NVIC_SystemReset();
}

Expand Down Expand Up @@ -232,6 +300,8 @@ void nrf52Loop()

checkSDEvents();
reportLittleFSCorruptionOnce();
LOG_INFO("VDD_VOLTAGE: %f", getVDDVoltage());

}

#ifdef USE_SEMIHOSTING
Expand Down
12 changes: 12 additions & 0 deletions src/power/PowerHAL.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

#include "PowerHAL.h"

void powerHAL_init(){
return powerHAL_platformInit();
}

__attribute__((weak, noinline)) void powerHAL_platformInit() {}

__attribute__((weak, noinline)) bool powerHAL_isPowerLevelSafe() { return true; }

__attribute__((weak, noinline)) bool powerHAL_isVBUSConnected() { return false; }
Loading
Loading