Files
MeshCore/src/helpers/NRF52Board.cpp
T
mikecarper 9de1373bd9 Add timed cw and cw2 commands for SX1262 and LR1110
Support fractional seconds, guard active packet work, keep the MCU awake, and restore reception after carrier tests. Include regression coverage and independent-radio RF validation for the V4 and T1000-E.
2026-09-14 19:23:23 -07:00

694 lines
22 KiB
C++

#if defined(NRF52_PLATFORM)
#include "NRF52Board.h"
#include "PowerManagementUtils.h"
#include "Nrf52BootloaderVersion.h"
#include <target.h>
#ifdef USER_GPIO_CONTROL
#include "UserGpioPinPolicy.h"
#endif
#include <bluefruit.h>
#include "ble_gap.h"
#include "ble_hci.h"
#include <nrf.h>
#include <nrf_soc.h>
#ifdef USE_TINYUSB
#include <Adafruit_TinyUSB.h>
#endif
#ifdef USE_CC310_HW_CRYPTO
#include <Adafruit_nRFCrypto.h>
#endif
static BLEDfu bledfu;
static uint16_t ota_conn_handle = BLE_CONN_HANDLE_INVALID;
static bool ota_active = false;
static bool ota_ble_started = false;
// A normal internal-flash operation completes in milliseconds. One minute is
// deliberately generous for other legitimate application work while still
// recovering an indefinitely blocked SoftDevice flash wait without requiring
// a physical power cycle. Builds can override this, or set it to 0 to disable
// the watchdog for diagnostics.
#ifndef NRF52_WATCHDOG_TIMEOUT_SECONDS
#define NRF52_WATCHDOG_TIMEOUT_SECONDS 60UL
#endif
#ifndef DFU_MAGIC_UF2_RESET
#define DFU_MAGIC_UF2_RESET 0x57
#endif
#if NRF52_WATCHDOG_TIMEOUT_SECONDS > 131071UL
#error "NRF52_WATCHDOG_TIMEOUT_SECONDS exceeds the nRF52 WDT counter range"
#endif
static void format_ota_reply(char reply[]) {
uint8_t mac_addr[6];
memset(mac_addr, 0, sizeof(mac_addr));
Bluefruit.getAddr(mac_addr);
sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", mac_addr[5], mac_addr[4], mac_addr[3],
mac_addr[2], mac_addr[1], mac_addr[0]);
}
#ifdef USER_GPIO_CONTROL
namespace {
bool isExposedNrf52UserGpio(uint8_t pin) {
#if defined(HELTEC_T096)
// Physical P2/P3 header GPIOs from the T096 schematic. Firmware-owned
// radio, display, GPS, power, button, and I2C pins are removed separately.
static const uint8_t exposed[] = {
2, 4, 7, 8, 9, 10, 13, 15, 17, 20, 22, 23, 24, 25, 27, 29, 31,
32, 33, 34, 35, 36, 37, 38, 39, 42, 43, 45, 47
};
#elif defined(PROMICRO)
// D0-D17 are broken out on the ProMicro form factor.
if (pin <= 17) return true;
return false;
#elif defined(RAK_3401) || defined(RAK_4631)
// GPIO and bus signals exposed by the WisBlock base/IO connector.
static const uint8_t exposed[] = {
2, 3, 4, 5, 9, 10, 13, 14, 15, 16, 17, 19, 20, 21, 24, 25,
26, 28, 29, 30, 31, 33, 34
};
#else
return false;
#endif
#if defined(HELTEC_T096) || defined(RAK_3401) || defined(RAK_4631)
for (size_t i = 0; i < sizeof(exposed) / sizeof(exposed[0]); i++) {
if (pin == exposed[i]) return true;
}
#endif
return false;
}
} // namespace
#endif
bool NRF52Board::isUserGpioAvailable(uint8_t pin) const {
#ifdef USER_GPIO_CONTROL
if (pin >= PINS_COUNT || digitalPinToPinName(pin) == 0xFF) return false;
#if defined(RAK_3401) || defined(RAK_4631)
// Sensor startup can toggle these WisBlock slot pins while detecting GPS,
// and WB_IO2 also controls the switched peripheral rail on supported bases.
if (pin == WB_IO2 || pin == WB_IO4 || pin == WB_IO5) return false;
#endif
return isExposedNrf52UserGpio(pin) && !UserGpioPinPolicy::isFirmwareReserved(pin);
#else
(void)pin;
return false;
#endif
}
bool NRF52Board::rebootToUf2Bootloader() {
uint8_t sd_enabled = 0;
if (sd_softdevice_is_enabled(&sd_enabled) != NRF_SUCCESS) return false;
if (sd_enabled) {
if (sd_power_gpregret_clr(0, 0xFF) != NRF_SUCCESS
|| sd_power_gpregret_set(0, DFU_MAGIC_UF2_RESET) != NRF_SUCCESS) {
return false;
}
} else {
NRF_POWER->GPREGRET = DFU_MAGIC_UF2_RESET;
}
NVIC_SystemReset();
return true;
}
static void connect_callback(uint16_t conn_handle) {
ota_conn_handle = conn_handle;
MESH_DEBUG_PRINTLN("BLE client connected");
}
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
(void)reason;
if (ota_conn_handle == conn_handle) {
ota_conn_handle = BLE_CONN_HANDLE_INVALID;
}
MESH_DEBUG_PRINTLN("BLE client disconnected");
}
void NRF52Board::begin() {
startup_reason = BD_STARTUP_NORMAL;
#ifdef USE_CC310_HW_CRYPTO
// CC310 TRNG is higher quality and environment-independent vs radio RSSI noise.
nRFCrypto.begin();
#endif
}
#if NRF52_WATCHDOG_TIMEOUT_SECONDS > 0
static void reloadWatchdogChannels() {
// Ordinarily only RR0 is enabled. If a bootloader left the watchdog running
// with a different reload channel, service every enabled channel.
const uint32_t enabled_channels = NRF_WDT->RREN & 0xFFUL;
for (uint8_t channel = 0; channel < 8; channel++) {
if (enabled_channels & (1UL << channel)) {
NRF_WDT->RR[channel] = WDT_RR_RR_Reload;
}
}
}
#endif
void NRF52Board::feedWatchdog(bool enabled) {
#if NRF52_WATCHDOG_TIMEOUT_SECONDS > 0
// The nRF52 watchdog cannot be stopped after it starts. When the persisted
// setting is turned off, deliberately stop reloading it; the resulting
// watchdog reset is the only software-only way to return it to the stopped
// state. On that next boot the disabled preference prevents it starting.
if (!enabled) return;
const bool running = NRF_WDT->RUNSTATUS != 0;
if (!running) {
// Keep running during CPU sleep: the flash-driver failure this protects
// against sleeps in sd_app_evt_wait(). Pause while halted so breakpoints
// do not reset a board being debugged.
NRF_WDT->CONFIG =
(WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos) |
(WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos);
NRF_WDT->CRV = (uint32_t)(NRF52_WATCHDOG_TIMEOUT_SECONDS * 32768UL);
NRF_WDT->RREN = WDT_RREN_RR0_Msk;
NRF_WDT->TASKS_START = 1;
}
reloadWatchdogChannels();
#else
(void)enabled;
#endif
}
void NRF52Board::serviceWatchdog() {
#if NRF52_WATCHDOG_TIMEOUT_SECONDS > 0
if (NRF_WDT->RUNSTATUS != 0) reloadWatchdogChannels();
#endif
}
#ifdef NRF52_POWER_MANAGEMENT
// Power Management global variables
uint32_t g_nrf52_reset_reason = 0; // Reset/Startup reason
uint8_t g_nrf52_shutdown_reason = 0; // Shutdown reason
// Early constructor - runs before SystemInit() clears the registers
// Priority 101 ensures this runs before SystemInit (102) and before
// any C++ static constructors (default 65535)
static void __attribute__((constructor(101))) nrf52_early_reset_capture() {
g_nrf52_reset_reason = NRF_POWER->RESETREAS;
g_nrf52_shutdown_reason = NRF_POWER->GPREGRET2;
}
void NRF52Board::initPowerMgr() {
if (power_mgr_initialized) return;
// Copy early-captured register values
reset_reason = g_nrf52_reset_reason;
shutdown_reason = g_nrf52_shutdown_reason;
boot_voltage_mv = 0; // Will be set by checkBootVoltage()
// Clear registers for next boot
// Note: At this point SoftDevice may or may not be enabled
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
sd_power_reset_reason_clr(0xFFFFFFFF);
sd_power_gpregret_clr(1, 0xFF);
} else {
NRF_POWER->RESETREAS = 0xFFFFFFFF; // Write 1s to clear
NRF_POWER->GPREGRET2 = 0;
}
// Log reset/shutdown info
if (shutdown_reason != SHUTDOWN_REASON_NONE) {
MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX); Shutdown = %s (0x%02X)",
getResetReasonString(reset_reason), (unsigned long)reset_reason,
getShutdownReasonString(shutdown_reason), shutdown_reason);
} else {
MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX)",
getResetReasonString(reset_reason), (unsigned long)reset_reason);
}
power_mgr_initialized = true;
}
const char* NRF52Board::getResetReasonString(uint32_t reason) {
if (reason & POWER_RESETREAS_RESETPIN_Msk) return "Reset Pin";
if (reason & POWER_RESETREAS_DOG_Msk) return "Watchdog";
if (reason & POWER_RESETREAS_SREQ_Msk) return "Soft Reset";
if (reason & POWER_RESETREAS_LOCKUP_Msk) return "CPU Lockup";
#ifdef POWER_RESETREAS_LPCOMP_Msk
if (reason & POWER_RESETREAS_LPCOMP_Msk) return "Wake from LPCOMP";
#endif
#ifdef POWER_RESETREAS_VBUS_Msk
if (reason & POWER_RESETREAS_VBUS_Msk) return "Wake from VBUS";
#endif
#ifdef POWER_RESETREAS_OFF_Msk
if (reason & POWER_RESETREAS_OFF_Msk) return "Wake from GPIO";
#endif
#ifdef POWER_RESETREAS_DIF_Msk
if (reason & POWER_RESETREAS_DIF_Msk) return "Debug Interface";
#endif
return "Cold Boot";
}
const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
switch (reason) {
case SHUTDOWN_REASON_NONE: return "None";
case SHUTDOWN_REASON_LOW_VOLTAGE: return "Low Voltage";
case SHUTDOWN_REASON_USER: return "User Request";
case SHUTDOWN_REASON_BOOT_PROTECT: return "Boot Protection";
}
return "Unknown";
}
bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) {
initPowerMgr();
if (config == nullptr) return true;
// Use the median of three readings. A single unsettled ADC sample during a
// brownout must not put the device into a persistent SYSTEMOFF boot lock.
uint16_t samples[3];
for (uint8_t i = 0; i < 3; i++) {
samples[i] = getBattMilliVolts();
if (i != 2) delay(5);
}
boot_voltage_mv = mesh::power::medianVoltage(samples[0], samples[1], samples[2]);
if (config->voltage_bootlock == 0) return true; // Protection disabled
// Skip check if externally powered
if (isExternalPowered()) {
MESH_DEBUG_PRINTLN("PWRMGT: Boot check skipped (external power)");
return true;
}
MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage = %u mV (threshold = %u mV)",
boot_voltage_mv, config->voltage_bootlock);
// Only trigger shutdown if reading is valid (>1000mV) AND below threshold
// This prevents spurious shutdowns on ADC glitches or uninitialized reads
if (mesh::power::shouldBootLock(boot_voltage_mv, config->voltage_bootlock, false)) {
MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage too low - entering protective shutdown");
initiateShutdown(SHUTDOWN_REASON_BOOT_PROTECT);
return false; // Should never reach this
}
return true;
}
void NRF52Board::initiateShutdown(uint8_t reason) {
enterSystemOff(reason);
}
void NRF52Board::enterSystemOff(uint8_t reason) {
MESH_DEBUG_PRINTLN("PWRMGT: Entering SYSTEMOFF (%s)", getShutdownReasonString(reason));
// Record shutdown reason in GPREGRET2
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
sd_power_gpregret_clr(1, 0xFF);
sd_power_gpregret_set(1, reason);
} else {
NRF_POWER->GPREGRET2 = reason;
}
// Flush serial buffers
Serial.flush();
delay(100);
// Enter SYSTEMOFF
if (sd_enabled) {
uint32_t err = sd_power_system_off();
if (err == NRF_ERROR_SOFTDEVICE_NOT_ENABLED) { //SoftDevice not enabled
sd_enabled = 0;
}
}
if (!sd_enabled) {
// SoftDevice not available; write directly to POWER->SYSTEMOFF
NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter;
}
// If we get here, something went wrong. Reset to recover.
NVIC_SystemReset();
}
void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
// USB power should always be able to recover a device from SYSTEMOFF, even
// if voltage comparator setup is unavailable or invalid.
armVbusWake();
if (!power_mgr_initialized || !supportsVoltageWake()) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake skipped (power manager not ready/unsupported)");
return;
}
if (ain_channel > 7 || refsel > 15) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake skipped (invalid AIN/ref)");
return;
}
// LPCOMP is not managed by SoftDevice - direct register access required
// Halt and disable before reconfiguration
NRF_LPCOMP->TASKS_STOP = 1;
NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Disabled;
// Select analog input (AIN0-7 maps to PSEL 0-7)
NRF_LPCOMP->PSEL = ((uint32_t)ain_channel << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk;
// Reference: REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16)
NRF_LPCOMP->REFSEL = ((uint32_t)refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk;
// Detect UP events (voltage rises above threshold for battery recovery)
NRF_LPCOMP->ANADETECT = LPCOMP_ANADETECT_ANADETECT_Up;
// Do not add comparator hysteresis here. On divided battery inputs it can
// shift the effective wake point enough to strand a valid low-voltage cell.
NRF_LPCOMP->HYST = LPCOMP_HYST_HYST_NoHyst;
// Clear stale events/interrupts before enabling wake
NRF_LPCOMP->EVENTS_READY = 0;
NRF_LPCOMP->EVENTS_DOWN = 0;
NRF_LPCOMP->EVENTS_UP = 0;
NRF_LPCOMP->EVENTS_CROSS = 0;
NRF_LPCOMP->INTENCLR = 0xFFFFFFFF;
NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk;
// Enable LPCOMP
NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Enabled;
NRF_LPCOMP->TASKS_START = 1;
// Wait for comparator to settle before entering SYSTEMOFF
for (uint8_t i = 0; i < 20 && !NRF_LPCOMP->EVENTS_READY; i++) {
delayMicroseconds(50);
}
if (refsel == 7) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=ARef)", ain_channel);
} else if (refsel <= 6) {
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/8 VDD)",
ain_channel, refsel + 1);
} else {
uint8_t ref_num = (uint8_t)((refsel - 8) * 2 + 1);
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/16 VDD)",
ain_channel, ref_num);
}
}
void NRF52Board::armVbusWake() {
// Configure VBUS (USB power) wake alongside (or instead of) LPCOMP.
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
sd_power_usbdetected_enable(1);
} else {
NRF_POWER->EVENTS_USBDETECTED = 0;
NRF_POWER->INTENSET = POWER_INTENSET_USBDETECTED_Msk;
}
MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake configured");
}
#endif
void NRF52BoardDCDC::begin() {
NRF52Board::begin();
// Enable DC/DC converter for improved power efficiency
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE);
} else {
NRF_POWER->DCDCEN = 1;
}
}
bool NRF52Board::isExternalPowered() {
// Check if SoftDevice is enabled before using its API
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
uint32_t usb_status;
sd_power_usbregstatus_get(&usb_status);
return (usb_status & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
} else {
return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
}
}
bool NRF52Board::isUsbDataConnected() {
#if defined(USE_TINYUSB)
#if defined(CFG_TUD_CDC) && CFG_TUD_CDC
return tud_mounted() && tud_cdc_connected();
#else
return tud_mounted();
#endif
#else
return false;
#endif
}
bool NRF52Board::isUsbHostConnected() {
#if defined(USE_TINYUSB)
return tud_mounted();
#else
return false;
#endif
}
void NRF52Board::sleep(uint32_t secs) {
if (isRadioTestActive()) { delay(1); return; }
// Clear FPU interrupt flags to avoid insomnia
// see errata 87 for details https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_87.html
#if (__FPU_USED == 1)
__set_FPSCR(__get_FPSCR() & ~(0x0000009F));
(void) __get_FPSCR();
NVIC_ClearPendingIRQ(FPU_IRQn);
#endif
// On nRF52, we use event-driven sleep instead of timed sleep
// The 'secs' parameter is ignored - we wake on any interrupt
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
// A single call is required here. If an interrupt arrived since the last
// wait, SoftDevice returns immediately so the main loop can service the
// flag or BLE queue before sleeping again.
sd_app_evt_wait();
} else {
// SoftDevice is disabled. Match the Adafruit core's race-preserving
// waitForEvent() sequence: the first WFE sleeps or consumes an already-
// pending event, then SEV/WFE reliably clears the event register before
// returning. SEV/WFE/WFE can erase the only GPIO edge that arrived between
// the caller's predicate and here.
__WFE();
__SEV();
__WFE();
}
}
// Temperature from NRF52 MCU
float NRF52Board::getMCUTemperature() {
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
uint32_t err_code;
int32_t temp;
err_code = sd_temp_get(&temp);
if (err_code == NRF_SUCCESS) {
return (float)temp * 0.25f;
} else {
return NAN;
}
} else {
NRF_TEMP->TASKS_START = 1; // Start temperature measurement
long startTime = millis();
while (NRF_TEMP->EVENTS_DATARDY == 0) { // Wait for completion. Should complete in 50us
if(millis() - startTime > 5) { // To wait 5ms just in case
NRF_TEMP->TASKS_STOP = 1;
return NAN;
}
}
}
NRF_TEMP->EVENTS_DATARDY = 0; // Clear event flag
int32_t temp = NRF_TEMP->TEMP; // In 0.25 *C units
NRF_TEMP->TASKS_STOP = 1;
return temp * 0.25f; // Convert to *C
}
void NRF52Board::shutdownPeripherals() {
// Power off the display if any
#ifdef DISPLAY_CLASS
if (display.isOn()) {
display.turnOff();
}
#endif
// Prep LoRa radio for power down
#ifdef P_LORA_RESET
digitalWrite(P_LORA_RESET, HIGH); // preload OUT latch so pinMode can't glitch NRESET low
pinMode(P_LORA_RESET, OUTPUT);
digitalWrite(P_LORA_RESET, LOW); // deliberate hardware reset (datasheet: >=100us)
delayMicroseconds(200);
digitalWrite(P_LORA_RESET, HIGH);
#endif
#if defined(P_LORA_SCLK) && defined(P_LORA_MISO) && defined(P_LORA_MOSI)
SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);
SPI.begin(); // SPI may not be started on some shutdown paths, need it to shut down radio
#endif
#ifdef P_LORA_BUSY
pinMode(P_LORA_BUSY, INPUT);
uint32_t started_at = millis();
while (digitalRead(P_LORA_BUSY) && millis() - started_at < 10) {} //wait for radio to be ready
#endif
#ifdef P_LORA_NSS
pinMode(P_LORA_NSS, OUTPUT);
digitalWrite(P_LORA_NSS, HIGH);
#endif
// Power off LoRa
radio_driver.powerOff();
// RadioLib's IRQ setup can leave DIO1 as a GPIO wake source. Once the radio
// is asleep that line may float or remain asserted, immediately waking an
// nRF52 that is trying to enter SYSTEMOFF. Release the interrupt channel and
// explicitly disable pin sensing; board-specific code can then arm only its
// intended wake source (for example, the SenseCAP user button or LPCOMP).
#ifdef P_LORA_DIO_1
detachInterrupt(P_LORA_DIO_1);
pinMode(P_LORA_DIO_1, INPUT);
#endif
// Keep LoRa inactive during deepsleep
#ifdef P_LORA_NSS
digitalWrite(P_LORA_NSS, HIGH);
#endif
// Power off GPS if any
if(sensors.getLocationProvider() != NULL) {
sensors.getLocationProvider()->stop();
}
#ifdef USE_CC310_HW_CRYPTO
nRFCrypto.end();
#endif
// Flush serial buffers
Serial.flush();
delay(100);
}
void NRF52Board::powerOff() {
shutdownPeripherals();
// Enter SYSTEMOFF
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) { // SoftDevice is enabled
sd_power_system_off();
} else { // SoftDevice is not enable
NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter;
}
}
bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) {
if (!out || max_len == 0u) return false;
out[0] = 0;
mesh::Nrf52BootloaderRegion region;
const uint32_t mbr_start = *(const volatile uint32_t*)(uintptr_t)0xFF8u;
if (!mesh::nrf52BootloaderRegion(NRF_FICR->CODEPAGESIZE, NRF_FICR->CODESIZE,
mbr_start, NRF_UICR->NRFFW[0], region)) return false;
return mesh::nrf52BootloaderVersion((const uint8_t*)(uintptr_t)region.start,
region.end - region.start, region.start,
bootloaderVersion, out, max_len);
}
bool NRF52Board::startOTAUpdate(const char *id, char reply[], bool force_ap) {
(void)id;
(void)force_ap;
if (ota_active) {
format_ota_reply(reply);
return true;
}
if (!ota_ble_started) {
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
if (!Bluefruit.begin(1, 0)) {
return false;
}
ota_ble_started = true;
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
}
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
Bluefruit.setTxPower(4);
// Set the BLE device name
Bluefruit.setName(ota_name);
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
Bluefruit.Advertising.clearData();
Bluefruit.ScanResponse.clearData();
// Set up and start advertising
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
Bluefruit.Advertising.addName();
/* Start Advertising
- Enable auto advertising if disconnected
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
- Timeout for fast mode is 30 seconds
- Start(timeout) with timeout = 0 will advertise forever (until connected)
For recommended advertising interval
https://developer.apple.com/library/content/qa/qa1931/_index.html
*/
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
ota_active = true;
format_ota_reply(reply);
return true;
}
bool NRF52Board::stopOTAUpdate(char reply[]) {
if (!ota_active) {
strcpy(reply, "OK - OTA not running");
return true;
}
Bluefruit.Advertising.restartOnDisconnect(false);
Bluefruit.Advertising.stop();
if (ota_conn_handle != BLE_CONN_HANDLE_INVALID) {
sd_ble_gap_disconnect(ota_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
ota_conn_handle = BLE_CONN_HANDLE_INVALID;
}
ota_active = false;
strcpy(reply, "OK - OTA stopped");
return true;
}
#endif