Reseting SPI pin after GPS cycle

I have an issue with my monitor one using the expansion board with a microphone attached. I have a Max9814 connected using A5 as out and A4 for gain. The issue I have is after doing a GPS sync and restarting the device the voltage on A5 stays at 2.8 volts. Prior to the GPS sync it is 1.25 which is where it should be. The only way I can get the voltage to go back to 1.25 is to flash new software. I have tried various reset and hibernate options and the it stays at 2.8 volts most likely due to SPI. Any ideas?

Thanks in advance. I put half the code in here since it was too long.


Copy

// =============================== main.cpp ===============================
#include "Particle.h"
#include <math.h>
#include "ubloxGPS.h"
#include "tracker_config.h"
 
#define USE_USER_BUTTON 1
#if USE_USER_BUTTON
#include "DebounceSwitchRK.h"
#endif
 
#include "FftComplex.h"
#include <complex>
#include <vector>
#include <cmath>
 
// DMA ADC on Gen3
#include "ADCDMAGen3_RK.h"
#include "AB1805_RK.h"
 
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
 
using namespace std;
using namespace std::chrono;
 
SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);
 
// AB1805 RTC/Watchdog - used to trigger hardware reset after GPS
// which cycles the PMIC power rails and resets the MAX9814 AGC
AB1805 ab1805(Wire);  // Tracker SoM AB1805 is on Wire (I2C0, D0/D1)
 
// ================= Feature toggles =================
#define ENABLE_FFT          1
#define ENABLE_PUBLISH      1
#define LOG_CELL_EVERY_MS   0
 
// ---- diagnostic & stability toggles ----
#define USE_HW_WATCHDOG     1
#define SAFE_BACKOFF_MAX    10
#define SAFE_BACKOFF_MS     120000
 
// ================= Pins / Hardware =================
#define GPS_CS_PIN        (GPS_CS)
#define GPS_PWR_PIN       (GPS_PWR)
#define GPS_RST_PIN       (GPS_RST)
#define GPS_INT_PIN       (GPS_INT)
#define GPS_INT_GPS_PIN   (14)
 
#define MIC_PIN           A5
#define GAIN_PIN          A4
 
#ifndef MONITORONE_USER_BUTTON
#define MONITORONE_USER_BUTTON D2
#endif
 
// ================== Logging ==================
SerialLogHandler logHandler(115200, LOG_LEVEL_WARN, {
    {"app",              LOG_LEVEL_INFO},
    {"app.gps.nmea",     LOG_LEVEL_NONE},
    {"app.gps.ubx",      LOG_LEVEL_NONE},
    {"system",           LOG_LEVEL_WARN},
    {"system.nm",        LOG_LEVEL_NONE},
    {"comm.dtls",        LOG_LEVEL_NONE},
    {"comm.protocol",    LOG_LEVEL_NONE},
    {"net",              LOG_LEVEL_NONE}
});
 
// =================== Watchdog ======================
static const uint32_t WD_TIMEOUT_MS = 300000;
 
// =================== Reset reason helpers ======================
static const char* resetReasonName(int rr) {
    switch (rr) {
        case 10: return "POWER";
        case 20: return "PIN";
        case 50: return "BROWNOUT";
        case 60: return "RTC_WATCHDOG";
        case 70: return "INTERNAL_WATCHDOG";
        case 80: return "UPDATE";
        case 90: return "SOFT";
        default: return "?";
    }
}
static const char* resetReasonHint(int rr) {
    switch (rr) {
        case 20: return "pin_reset_possible_device_or_noise";
        case 60: return "ext_watchdog_ab1805";
        case 70: return "mcu_watchdog_internal";
        default: return "";
    }
}
 
// =================== Breadcrumbs ===================
struct Crumb { uint32_t magic, stage, ms, rr, rrData; };
static constexpr uint32_t CRUMB_MAGIC = 0xA5A5A5A5;
retained Crumb bc_prev = {0};
Crumb bc_last = {0};
Crumb bc_curr = {0};
 
enum { ST_BOOT=1, ST_SETUP, ST_GPS_ON, ST_GPS_LOOP, ST_PUBLISH, ST_FFT_SAMPLE, ST_FFT_PUBLISH };
static inline const char* stageName(uint32_t s) {
    switch (s) {
        case ST_BOOT:        return "BOOT";
        case ST_SETUP:       return "SETUP";
        case ST_GPS_ON:      return "GPS_ON";
        case ST_GPS_LOOP:    return "GPS_LOOP";
        case ST_PUBLISH:     return "PUBLISH";
        case ST_FFT_SAMPLE:  return "FFT_SAMPLE";
        case ST_FFT_PUBLISH: return "FFT_PUBLISH";
        default:             return "?";
    }
}
static inline void setStage(uint32_t s) { bc_curr.stage = s; bc_curr.ms = millis(); }
static inline void rollCrumbForNextBoot() { bc_prev = bc_curr; }
 
static void capturePrevAndInitCurr() {
    if (bc_prev.magic == CRUMB_MAGIC) {
        bc_last = bc_prev;
        Log.info("Last boot: stage=%lu (%s) ms=%lu rr=%lu",
            (unsigned long)bc_last.stage, stageName(bc_last.stage),
            (unsigned long)bc_last.ms, (unsigned long)bc_last.rr);
    } else {
        Log.info("No previous crumb");
    }
    bc_curr.magic  = CRUMB_MAGIC;
    bc_curr.rr     = (uint32_t)System.resetReason();
    bc_curr.rrData = (uint32_t)System.resetReasonData();
    setStage(ST_BOOT);
    rollCrumbForNextBoot();
    Log.info("Reset reason=%lu (%s) data=0x%08lx",
             (unsigned long)bc_curr.rr, resetReasonName((int)bc_curr.rr),
             (unsigned long)bc_curr.rrData);
}
 
// ============= Retained reboot window =============
struct RetainedDiag { uint32_t magic, bootCount, lastBootMs, lastBootMillisStamp; };
retained RetainedDiag rdiag = {0};
 
static void updateRebootWindow() {
    uint32_t now = millis();
    if (rdiag.magic != 0xBEEFCAFE) {
        rdiag.magic = 0xBEEFCAFE;
        rdiag.bootCount = 0;
        rdiag.lastBootMs = now;
        rdiag.lastBootMillisStamp = now;
    }
    if ((now - rdiag.lastBootMillisStamp) <= SAFE_BACKOFF_MS)
        rdiag.bootCount++;
    else
        rdiag.bootCount = 1;
    rdiag.lastBootMillisStamp = now;
}
static bool inSafeBackoff() { return (rdiag.bootCount >= SAFE_BACKOFF_MAX); }
 
// ============= Retained GPS lock status =============
struct RetainedGPS { uint32_t magic; uint32_t lastPublishEpoch; };
static constexpr uint32_t GPS_MAGIC = 0xC0FFEE01;
retained RetainedGPS gpsStatus = {0};
 
static inline bool hasEverHadGPSLock() {
    return (gpsStatus.magic == GPS_MAGIC && gpsStatus.lastPublishEpoch > 0);
}
 
// Retained hibernate wake counter - tracks how many hibernate cycles
// have occurred since GPS session ended. MAX9814 AGC needs multiple
// full power cycles to fully reset after SPI1 drives A5 to 3.3V.
struct RetainedHibernate { uint32_t magic; uint32_t wakeCount; };
static constexpr uint32_t HIB_MAGIC = 0xDECAFBAD;
retained RetainedHibernate hibStatus = {0};
static const uint32_t HIB_CYCLES_NEEDED = 3; // number of power cycles to reset MAX9814
 
// ================= GPS model =================
struct GPSData {
    double latitude = 0.0, longitude = 0.0, altitude = 0.0;
    double speed = 0.0, heading = 0.0;
    bool   locked = false, stable = false;
    uint8_t satellites = 0;
    double hdop = 0.0, vdop = 0.0;
    double horizontalAccuracy = 0.0, verticalAccuracy = 0.0;
    time_t utcTime = 0;
};
ubloxGPS *gps = nullptr;
GPSData   gpsData;
 
// ================= GPS state =================
// GPS starts at boot automatically. Triple-tap or timeout shuts it down.
// No hibernate, no Particle.disconnect() - same stable architecture as
// the version that ran for months without PIN reset issues.
bool          g_gpsActive        = false;
bool          g_gpsShutdownDone  = false;
bool          g_gpsLockAchieved  = false;
bool          g_forceGPSShutdown  = false;
bool          g_startGPSRequested  = false; // set by triple-tap to start GPS on demand
bool          g_flushFFTBuffers    = false; // set after GPS shutdown to discard stale ADC data
unsigned long g_gpsStartMs       = 0;
unsigned long g_gpsFirstLockMs   = 0;
 
const unsigned long GPS_TIMEOUT_MS = 5UL * 60UL * 1000UL;
const unsigned long GPS_DWELL_MS   = 60000;
 
int g_reqId = 1;
 
// ================= LED state machine =================
// Red          = FFT running, no GPS lock ever recorded - needs sync
// Green        = FFT running, GPS lock confirmed at least once
// Orange flash = GPS active, searching
// Green solid  = GPS locked during dwell
inline void ledNoGPS() {
    RGB.control(true);
    RGB.color(255, 0, 0);
}
inline void ledFFTWithGPS() {
    RGB.control(true);
    RGB.color(0, 255, 0);
}
inline void ledSearching() {
    static unsigned long lastToggle = 0;
    static bool ledOn = false;
    if (millis() - lastToggle >= 500) {
        ledOn = !ledOn;
        RGB.control(true);
        RGB.color(ledOn ? 255 : 0, ledOn ? 165 : 0, 0);
        lastToggle = millis();
    }
}
inline void ledLocked() {
    RGB.control(true);
    RGB.color(0, 255, 0);
}
inline void ledFFTActive() {
    if (hasEverHadGPSLock())
        ledFFTWithGPS();
    else
        ledNoGPS();
}
 
// ================= FFT config & state =================
constexpr uint32_t SAMPLE_FREQ       = 8000;
constexpr size_t   NUM_SAMPLES       = 1024;
constexpr unsigned long WINDOW_MS    = 2000;
unsigned long publishIntervalMs      = 10UL * 60000UL;
 
constexpr int FFT_SKIP_FACTOR = 1;
static int fftSkipCounter = 0;
 
double lowHz1  = 900.0,  highHz1 = 1200.0;
double lowHz2  = 1700.0, highHz2 = 2200.0;
 
double hitThreshold1 = 800.0;
double hitThreshold2 = 900.0;
 
int hitCount1 = 0;
int hitCount2 = 0;
 
double cleanupHz = 3500.0;
static float lp_prev  = 0.0f;
static float lp_alpha = 0.0f;
 
vector<complex<float>> fftBuf(NUM_SAMPLES);
static float floatBuffer[NUM_SAMPLES];
 
double winMax1=0, winFreq1=0, winMax2=0, winFreq2=0;
double pubMax1=0, pubFreq1=0, pubMax2=0, pubFreq2=0;
 
unsigned long windowStart = 0, publishStart = 0;
 
static const unsigned long FFT_LOG_PERIOD_MS = 2000;
static unsigned long lastFftLog = 0;
 
bool fftEnabled = false; // enabled after GPS shuts down
int  gainState  = 1;
 
// ================= Cellular cooldown =================
// Simple timer-based suppression of FFT data during/after RF activity.
// No modem state polling - avoids interference with the nRF52840.
static unsigned long cellCooldownUntil = 0;
static const unsigned long CELL_COOLDOWN_MS = 5000;
 
static inline bool cellIdle()       { return (millis() > cellCooldownUntil); }
static inline void setCellCooldown(){ cellCooldownUntil = millis() + CELL_COOLDOWN_MS; }
 
// Restore A5 (SPI1 SCK = MIC_PIN) to SAADC-compatible analog input mode.
// Particle OS cellular/publish operations reset A5 pin config to digital
// input (INPUT=Connect) which loads down the analog signal and kills ADC.
// nRF52840 SAADC requires INPUT=Disconnect on the sampled pin.
static inline void restoreA5ForADC() {
    // P0.31 = A5: Fully release then set for SAADC
    // Step 1 removes any residual output driver state left by cellular/SPI ops.
    // Step 2 sets INPUT=Disconnect required by nRF52840 SAADC.
    NRF_P0->PIN_CNF[31] = 0x00000000UL; // input, driver off, no pull
    NRF_P0->PIN_CNF[31] = 0x00000002UL; // INPUT=Disconnect for SAADC
}
 
// ================= FFT diagnostic state =================
static float maxEverSeen = 0;
static int   frameCount  = 0;
 
// ================= DMA ADC (ping-pong) =================
ADCDMAGen3 adc;
const size_t SAMPLES_IN_BUFFER = NUM_SAMPLES;
static nrf_saadc_value_t buffer0[SAMPLES_IN_BUFFER];
static nrf_saadc_value_t buffer1[SAMPLES_IN_BUFFER];
 
static volatile const nrf_saadc_value_t *g_readyBuf  = nullptr;
static volatile size_t                   g_readyCount = 0;
static volatile uint32_t                 g_dmaOverruns= 0;
 
void adcBufferCallback(nrf_saadc_value_t *buf, size_t size) {
    if (g_readyBuf != nullptr) g_dmaOverruns++;
    g_readyBuf   = buf;
    g_readyCount = size;
}
 
// ================= Helpers =================
static inline void computeAlpha() {
    const double nyq   = SAMPLE_FREQ / 2.0;
    const double maxLP = nyq * 0.8;
    if (cleanupHz > maxLP) cleanupHz = maxLP;
    const float dt = 1.0f / SAMPLE_FREQ;
    const float RC = 1.0f / (2.0f * M_PI * cleanupHz);
    lp_alpha = dt / (RC + dt);
}
 
static inline void peakInBand(
    const vector<complex<float>>& X, size_t N,
    float lo, float hi,
    float& outMag, size_t& outBin)
{
    size_t s = (size_t)(lo * N / SAMPLE_FREQ);
    size_t e = (size_t)(hi * N / SAMPLE_FREQ);
    float mSq = 0.0f; size_t mb = s;
    for (size_t k = s; k <= e && k < N; ++k) {
        float re = X[k].real(), im = X[k].imag();
        float aSq = re*re + im*im;
        if (aSq > mSq) { mSq = aSq; mb = k; }
    }
    outMag = sqrtf(mSq);
    outBin = mb;
}
 
bool isGoodFix(const GPSData& d) {
    bool vdopValid = (d.vdop > 0.0 && d.vdop < 50.0);
    bool dopOk = (d.hdop > 0.0 && d.hdop <= 2.5) && (!vdopValid || d.vdop <= 3.0);
    bool accOk = (d.horizontalAccuracy > 0 && d.horizontalAccuracy <= 25.0);
    return d.locked && (dopOk || accOk);
}
 
float readCellStrength() {
#if HAL_PLATFORM_CELLULAR
    CellularSignal sig = Cellular.RSSI();
    if (sig.isValid()) return sig.getStrength();
#endif
    return NAN;
}
float readBatteryPct() { return System.batteryCharge(); }
 
inline void safeDelay(unsigned long ms) {
    unsigned long start = millis();
    while (millis() - start < ms) {
        if (USE_HW_WATCHDOG) Watchdog.refresh();
        delay(10);
    }
}
 
// ================= GPS helpers =================
bool gpsPowerControl(bool en) { digitalWrite(GPS_PWR_PIN, en ? HIGH : LOW); return true; }
bool gpsSpiSelect(bool sel)   { digitalWrite(GPS_CS_PIN,  sel ? LOW  : HIGH); return true; }
 
void updateGPSData() {
    if (!gps) return;
    if (USE_HW_WATCHDOG) Watchdog.refresh();
    gps->lock();
    gpsData.locked             = gps->getLock();
    gpsData.stable             = gps->isLockStable();
    gpsData.satellites         = gps->getSatellites();
    gpsData.hdop               = gps->getHDOP();
    gpsData.vdop               = gps->getVDOP();
    gpsData.horizontalAccuracy = gps->getHorizontalAccuracy();
    gpsData.verticalAccuracy   = gps->getVerticalAccuracy();
    gpsData.utcTime            = (time_t)gps->getUTCTime();
    if (gpsData.locked) {
        gpsData.latitude  = gps->getLatitude();
        gpsData.longitude = gps->getLongitude();
        gpsData.altitude  = gps->getAltitude();
        gpsData.speed     = gps->getSpeed(GPS_SPEED_UNIT_MPS);
        gpsData.heading   = gps->getHeading();
    }
    gps->unlock();
    if (USE_HW_WATCHDOG) Watchdog.refresh();
}
 
void printGPSData() {
    Log.info("=== GPS === lock:%s sats:%d HDOP:%.2f HAcc:%.2f m",
        gpsData.locked ? "YES":"NO", gpsData.satellites,
        gpsData.hdop, gpsData.horizontalAccuracy);
    if (gpsData.locked)
        Log.info("Lat:%.6f Lon:%.6f Alt:%.2f",
            gpsData.latitude, gpsData.longitude, gpsData.altitude);
}
 
// Shut down GPS cleanly - no hibernate, no modem changes
// Mirrors the stable approach from old version that ran for months
void shutdownGPS() {
    if (!g_gpsActive) return;
    if (gps && gps->isOn()) {
        gps->off();
        Log.info("GPS powered OFF");
    }
    // Release SPI1's ownership of A5 (SPI SCK = MIC_PIN), then immediately
    gps->off();
    delete gps;
    gps = nullptr;
 
    // Use AB1805 hardware watchdog to trigger a hardware RESET pulse.
    // The AB1805 nRST pin is wired to the nRF52840 RESET line - same as
    // the expansion card reset button. This causes the PMIC to do a full
    // power-on sequence which cycles the 3V3 rail and resets the MAX9814 AGC.
    // System.reset() and hibernate do NOT do this - only a hardware RESET pulse works.
    Log.info("GPS session complete - triggering AB1805 hardware reset to recover MAX9814");
    if (USE_HW_WATCHDOG) Watchdog.stop();
    delay(500); // let log flush
 
    // Set AB1805 WDT to minimum (4s) and stop servicing it - it will fire and
    // pull nRST low via the hardware RESET line, cycling the PMIC rails
    // ab1805 already initialized in setup() - just set WDT directly
    ab1805.setWDT(4); // 4 second watchdog - minimum value
    // Do NOT service the watchdog - let it expire and pull RESET low
    // This does not return normally - device resets via hardware watchdog
    unsigned long wdtStart = millis();
    while (true) {
        // Deliberately NOT calling Watchdog.refresh() or ab1805 service
        delay(100);
        if (millis() - wdtStart > 30000) {
            // Fallback - if AB1805 WDT didn't fire after 30s, force system reset
            Log.info("AB1805 WDT fallback - forcing System.reset()");
            System.reset();
        }
    }
}
 
// Start GPS on demand - called from loop when triple-tap received
void startGPS() {
    if (g_gpsActive) return; // already running
    Log.info("Starting GPS session on demand...");
    // Initialize SPI1 here - not at boot - to avoid driving A5 (MIC_PIN) as
    // SCK output before the mic session begins
    SPI1.begin();
    gps = new ubloxGPS(SPI1, gpsSpiSelect, gpsPowerControl, GPS_INT_PIN, GPS_INT_GPS_PIN);
    if (gps) {
        digitalWrite(GPS_RST_PIN, LOW);  safeDelay(10);
        digitalWrite(GPS_RST_PIN, HIGH); safeDelay(20);
        gps->on();
        Log.info("GPS powered ON");
        g_gpsActive        = true;
        g_gpsShutdownDone  = false;
        g_gpsLockAchieved  = false;
        g_gpsFirstLockMs   = 0;
        g_gpsStartMs       = millis();
        fftEnabled         = false; // pause FFT during GPS
        setStage(ST_GPS_ON); rollCrumbForNextBoot();
        ledSearching();
    } else {
        Log.error("Failed to create GPS object");
    }
}
 
// ================= Location publish =================
static unsigned long lastPublishAt = 0;
 
bool publishTrackerJSON() {
#if !ENABLE_PUBLISH
    return true;
#endif
    setStage(ST_PUBLISH); rollCrumbForNextBoot();
 
    if (!Particle.connected()) {
        Particle.connect();
        unsigned long t0 = millis();
        while (!Particle.connected() && millis() - t0 < 60000) {
            if (USE_HW_WATCHDOG) Watchdog.refresh();
            delay(25);
        }
    }
    if (!Particle.connected()) {
        Log.warn("publishTrackerJSON: not connected, skipping");
        return false;
    }
 
    float cell = readCellStrength();
    float batt = readBatteryPct();
 
    char buf[384];
    snprintf(buf, sizeof(buf),
        "{"
        "\"cmd\":\"loc\",\"time\":%lu,"
        "\"loc\":{"
        "\"lck\":%d,\"time\":%lu,"
        "\"lat\":%.6f,\"lon\":%.6f,\"alt\":%.2f,"
        "\"hd\":%.2f,\"h_acc\":%.2f,\"v_acc\":%.2f,"
        "\"cell\":%s,\"batt\":%s"
        "},\"trig\":[\"button\"],\"req_id\":%d}",
        (unsigned long)Time.now(),
        gpsData.locked ? 1 : 0,
        (unsigned long)(gpsData.utcTime ? gpsData.utcTime : Time.now()),
        gpsData.latitude, gpsData.longitude, gpsData.altitude,
        gpsData.heading, gpsData.horizontalAccuracy, gpsData.verticalAccuracy,
        (isnan(cell) ? "null" : String(cell, 1).c_str()),
        (isnan(batt) ? "null" : String(batt, 1).c_str()),
        g_reqId++);
 
    if (USE_HW_WATCHDOG) Watchdog.refresh();
    Log.info("Publishing location...");
    bool ok = Particle.publish("loc", buf, PRIVATE);
    lastPublishAt = millis();
    safeDelay(3000); // let publish transmit before GPS shutdown begins
    restoreA5ForADC(); // cellular ops reset A5 pin config - restore for SAADC
    if (USE_HW_WATCHDOG) Watchdog.refresh();
    Log.info("Location publish %s", ok ? "queued" : "failed");
 
    if (ok) {
        gpsStatus.magic            = GPS_MAGIC;
        gpsStatus.lastPublishEpoch = (uint32_t)Time.now();
        Log.info("GPS lock recorded in retained memory");
    }
    return ok;
}
 
// ================= Cloud API =================
int fn_startFFT(String)  { fftEnabled = true;  Log.info("FFT enabled");  return 1; }
int fn_stopFFT(String)   { fftEnabled = false; Log.info("FFT disabled"); return 1; }
int fn_setB1L(String s)  { double v=s.toFloat(); if(v>0&&v<highHz1){ lowHz1=v; return 1;} return -1; }
int fn_setB1H(String s)  { double v=s.toFloat(); if(v>lowHz1&&v<SAMPLE_FREQ/2){ highHz1=v; return 1;} return -1; }
int fn_setB2L(String s)  { double v=s.toFloat(); if(v>0&&v<highHz2){ lowHz2=v; return 1;} return -1; }
int fn_setB2H(String s)  { double v=s.toFloat(); if(v>lowHz2&&v<SAMPLE_FREQ/2){ highHz2=v; return 1;} return -1; }
int fn_setCleanup(String s){ double v=s.toFloat(); if(v>100.0&&v<SAMPLE_FREQ/2){ cleanupHz=v; computeAlpha(); return 1;} return -1; }
int fn_setPubInterval(String s){ unsigned long v=s.toInt(); if(v>=1&&v<=1440){ publishIntervalMs=v*60000UL; return 1;} return -1; }
int fn_setGain(String s) {
    String cmd=s; cmd.toLowerCase();
    if(cmd=="high"||s.toInt()==1){ digitalWrite(GAIN_PIN,HIGH); gainState=1; return 1; }
    if(cmd=="low" ||s.toInt()==0){ digitalWrite(GAIN_PIN,LOW);  gainState=0; return 1; }
    return -1;
}
int fn_simulateTriple(String) {
    if (!g_gpsActive) {
        g_startGPSRequested = true;
        Log.info("simulateTriple: GPS start requested via cloud");
    } else {
        g_forceGPSShutdown = true;
        Log.info("simulateTriple: GPS shutdown requested via cloud");
    }
    return 1;
}
int fn_setHitThresh1(String s) {
    double v=s.toFloat();
    if(v>0&&v<100000){ hitThreshold1=v; Log.info("Hit threshold 1: %.1f", hitThreshold1); return 1;}
    return -1;
}
int fn_setHitThresh2(String s) {
    double v=s.toFloat();
    if(v>0&&v<100000){ hitThreshold2=v; Log.info("Hit threshold 2: %.1f", hitThreshold2); return 1;}
    return -1;
}
int fn_enterShippingMode(String s) {
    Log.info("Entering shipping mode");
    if (Particle.connected()) { Particle.publish("status","entering_shipping_mode",PRIVATE); delay(1000); }
    if (g_gpsActive) shutdownGPS();
    adc.stop();
    RGB.control(false);
    SystemSleepConfiguration config;
    config.mode(SystemSleepMode::HIBERNATE).gpio(MONITORONE_USER_BUTTON, FALLING);
    delay(100);
    System.sleep(config);
    return 1;
}
 
// ================= Button =================
#if USE_USER_BUTTON
void buttonHandler(DebounceSwitchState *s, void*) {
    if (s->getPressState() == DebouncePressState::TAP) {
        int taps = s->getTapCount();
        Log.info("Button: %d taps", taps);
        if (taps == 3) {
            if (!g_gpsActive && !g_gpsShutdownDone) {
                g_startGPSRequested = true;
                Log.info("Triple-tap: starting GPS session");
            } else if (g_gpsActive) {
                g_forceGPSShutdown = true;
                Log.info("Triple-tap: forcing GPS shutdown");
            } else {
                Log.info("Triple-tap: GPS already completed this boot");
            }
        }
        if (taps == 5) {
            Log.info("5-tap: entering shipping mode");
            fn_enterShippingMode("");
        }
    }
}
#endif
 
// ================= Setup =================
void setup() {
    Serial.begin(115200);
    waitFor(Serial.isConnected, 8000);
 
    capturePrevAndInitCurr();
    updateRebootWindow();
 
    if (USE_HW_WATCHDOG) {
        particle::WatchdogConfiguration cfg;
        cfg.timeout(milliseconds(WD_TIMEOUT_MS));
        Watchdog.init(cfg);
        Watchdog.start();
        Log.info("Watchdog started (timeout=%lums)", (unsigned long)WD_TIMEOUT_MS);
    } else {
        Watchdog.stop();
        Log.warn("Watchdog DISABLED for test");
    }
 
    // Initialize AB1805 RTC/watchdog - used to trigger hardware reset after GPS
    ab1805.setup();
    ab1805.resetConfig();
 
    Particle.keepAlive(30);
#if ENABLE_PUBLISH
    Particle.connect();
#endif
 
    pinMode(GPS_CS_PIN,  OUTPUT);
    pinMode(GPS_PWR_PIN, OUTPUT);
    pinMode(GPS_RST_PIN, OUTPUT);
    pinMode(GPS_INT_PIN, INPUT);
    digitalWrite(GPS_CS_PIN,  HIGH);
    digitalWrite(GPS_PWR_PIN, LOW);
    digitalWrite(GPS_RST_PIN, HIGH);
 
    // SPI1 is initialized on demand in startGPS() - not at boot.
    // Calling SPI1.begin() here configures A5 as SCK output immediately,
    // which drives the MAX9814 mic output to 3.3V and damages the capsule.
 
    Particle.variable("b1L",       lowHz1);
    Particle.variable("b1H",       highHz1);
    Particle.variable("b2L",       lowHz2);
    Particle.variable("b2H",       highHz2);
    Particle.variable("cleanupHz", cleanupHz);
    Particle.variable("pubMin",    publishIntervalMs);
    Particle.variable("gainState", gainState);
    Particle.variable("hitThresh1",hitThreshold1);
    Particle.variable("hitThresh2",hitThreshold2);
    Particle.variable("hits1",     hitCount1);
    Particle.variable("hits2",     hitCount2);
 
    Particle.function("fftStart",       fn_startFFT);
    Particle.function("fftStop",        fn_stopFFT);
    Particle.function("setB1L",         fn_setB1L);
    Particle.function("setB1H",         fn_setB1H);
    Particle.function("setB2L",         fn_setB2L);
    Particle.function("setB2H",         fn_setB2H);
    Particle.function("setCleanup",     fn_setCleanup);
    Particle.function("setPubMin",      fn_setPubInterval);
    Particle.function("setGain",        fn_setGain);
    Particle.function("simulateTriple", fn_simulateTriple);
    Particle.function("setHitTh1",      fn_setHitThresh1);
    Particle.function("setHitTh2",      fn_setHitThresh2);
    Particle.function("shipMode",       fn_enterShippingMode);
 
#if USE_USER_BUTTON
    pinMode(MONITORONE_USER_BUTTON, INPUT_PULLUP);
    DebounceSwitch::getInstance()->setup();
    DebounceSwitch::getInstance()->addSwitch(
        MONITORONE_USER_BUTTON,
        DebounceSwitchStyle::PRESS_LOW_PULLUP,
        buttonHandler,
        nullptr);
#endif
 
    pinMode(GAIN_PIN, OUTPUT);
    digitalWrite(GAIN_PIN, HIGH);
    gainState = 1;
 
    computeAlpha();
    lp_prev = 0.0f;
 
    windowStart  = millis();
    publishStart = millis();
 
    setStage(ST_SETUP); rollCrumbForNextBoot();
 
    bool safeMode = inSafeBackoff();
    if (safeMode) {
        Log.error("SAFE MODE: too many reboots (%lu in %lu ms). Skipping GPS and ADCDMA.",
                  (unsigned long)rdiag.bootCount, (unsigned long)SAFE_BACKOFF_MS);
        ledFFTActive();
        return;
    }
 
    // GPS is ON-DEMAND only - starts when user triple-taps button outdoors.
    // Boot goes straight to FFT. This prevents SPI1 activity from stressing
    // the MAX9814 mic capsule on every power cycle.
    fftEnabled = true;
    ledFFTActive();
 
    safeDelay(2000);
 
    // Start ADC - runs continuously through GPS and FFT phases
    ret_code_t err = adc
        .withSampleFreqHz(SAMPLE_FREQ)
        .withDoubleBuffer(SAMPLES_IN_BUFFER, buffer0, buffer1)
        .withSamplePin(MIC_PIN)
        .withBufferCallback(adcBufferCallback)
        .init();
 
    if (err == NRF_SUCCESS) {
        adc.start();
        Log.info("ADCDMA started @ %lu Hz", (unsigned long)SAMPLE_FREQ);
    } else {
        Log.error("ADCDMA init failed err=%lu", (unsigned long)err);
    }
 
    if (USE_HW_WATCHDOG) Watchdog.refresh();
}
 
// ================= Loop =================
void loop() {
    if (USE_HW_WATCHDOG) Watchdog.refresh();
 
#if ENABLE_PUBLISH
    if (!Particle.connected()) Particle.connect();
#endif
 
    // ---------- Boot diag publish (once) ----------
#if ENABLE_PUBLISH
    static bool bootDiagSent = false;
    if (Particle.connected() && !bootDiagSent) {
        if (USE_HW_WATCHDOG) Watchdog.refresh();
        char j[256];
        if (bc_last.magic == CRUMB_MAGIC) {
            snprintf(j, sizeof(j),
                "{\"rr\":%d,\"rr_name\":\"%s\",\"rrd\":%lu,"
                "\"hint\":\"%s\",\"boots_in_%lus\":%lu,"
                "\"prev_stage\":%lu,\"prev_stage_name\":\"%s\",\"prev_ms\":%lu,"
                "\"free\":%lu}",
                (int)System.resetReason(), resetReasonName((int)System.resetReason()),
                (unsigned long)System.resetReasonData(),
                resetReasonHint((int)System.resetReason()),
                (unsigned long)(SAFE_BACKOFF_MS/1000), (unsigned long)rdiag.bootCount,
                (unsigned long)bc_last.stage, stageName(bc_last.stage),
                (unsigned long)bc_last.ms, (unsigned long)System.freeMemory());
        } else {
            snprintf(j, sizeof(j),
                "{\"rr\":%d,\"rr_name\":\"%s\",\"rrd\":%lu,"
                "\"hint\":\"%s\",\"boots_in_%lus\":%lu,\"free\":%lu}",
                (int)System.resetReason(), resetReasonName((int)System.resetReason()),
                (unsigned long)System.resetReasonData(),
                resetReasonHint((int)System.resetReason()),
                (unsigned long)(SAFE_BACKOFF_MS/1000), (unsigned long)rdiag.bootCount,
                (unsigned long)System.freeMemory());
        }
        Particle.publish("boot_diag", j, PRIVATE);
        // Only restore A5 after GPS is done - writing PIN_CNF while SPI1 is
        // actively clocking the GPS module causes a SCK glitch and PIN reset
        if (!g_gpsActive) restoreA5ForADC();
        bootDiagSent = true;
        if (USE_HW_WATCHDOG) Watchdog.refresh();
    }
#endif
 
    // ================= GPS start on demand =================
    if (g_startGPSRequested) {
        g_startGPSRequested = false;
        startGPS();
    }
 
    // ================= GPS phase =================
    if (g_gpsActive) {
        setStage(ST_GPS_LOOP); rollCrumbForNextBoot();
        if (USE_HW_WATCHDOG) Watchdog.refresh();
 
        updateGPSData();
        ledSearching(); // flashing orange until lock
 
        static unsigned long lastPrintGPS = 0;
        static bool lastLock = false;
        bool nowLock = gpsData.locked;
        if (nowLock != lastLock || millis() - lastPrintGPS >= 30000UL) {
            printGPSData();
            lastLock = nowLock;
            lastPrintGPS = millis();
        }
 
        if (!g_gpsLockAchieved && isGoodFix(gpsData)) {
            g_gpsLockAchieved = true;
            g_gpsFirstLockMs  = millis();
            ledLocked(); // solid green
            Log.info("GPS lock achieved! Waiting %lus for accuracy refinement...",
                     (unsigned long)(GPS_DWELL_MS / 1000));
        }
 
        if (g_gpsFirstLockMs > 0) {
            static unsigned long lastAccLog = 0;
            if (millis() - lastAccLog >= 10000) {
                Log.info("GPS refining: HAcc=%.1fm, sats=%d, time since lock=%lus",
                    gpsData.horizontalAccuracy, gpsData.satellites,
                    (unsigned long)((millis() - g_gpsFirstLockMs) / 1000));
                lastAccLog = millis();
            }
        }
 
        bool timeout       = (millis() - g_gpsStartMs >= GPS_TIMEOUT_MS);
        bool dwellDone     = (g_gpsFirstLockMs > 0 &&
                              millis() - g_gpsFirstLockMs >= GPS_DWELL_MS);
        bool forceShutdown = g_forceGPSShutdown;
 
        if (timeout || dwellDone || forceShutdown) {
            g_forceGPSShutdown = false;
 
            if (timeout)       Log.warn("GPS timeout after %lus", GPS_TIMEOUT_MS/1000);
            if (dwellDone)     Log.info("GPS dwell complete. HAcc=%.1fm", gpsData.horizontalAccuracy);
            if (forceShutdown) Log.info("GPS force shutdown (triple-tap)");
 
            // Publish location if we have a good fix
            if (isGoodFix(gpsData)) {
#if ENABLE_PUBLISH
                publishTrackerJSON();
#endif
            } else {
                Log.warn("GPS shutdown without good fix - no loc publish");
            }
 
            shutdownGPS();
        }
    }

I don't know the answer but I'd look at SPI as being a possible culprit. A5 is the SPI CS pin (SS) and it will be set as an OUTPUT pin when SPI is used. Note that you might not have SPI enabled in your code; if you for example had Ethernet detection enabled, that would enable SPI. The symptoms don't fit perfectly so I'm not positive this is the cause, but I'd at least first investigate that.

Try using SPI.end(), and if that doesn't work, also try using pinMode(A5, INPUT) to see if that makes a difference.

Reinserting pinMode seemed to have helped, but I am still getting some pin resets which I think is due to the SPI artifact. Is A7 the best pin on the development board for an analog input when using GPS?

I think I have two things going on. The first was that I messed with PMIC settings a long time ago on my test units. I reset that and it helped, but didn’t completely remove the issue. What also helped was putting a resistor between A5 (out on my mic) and ground. However, the mic is way less sensitive now especially after the GPS sync.

What I think is happening is the what when I do the one time GPS sync it is firing up A5 for SPI and then when I release SPI there is still voltage on the pin from the mic. So A5 never recovers. I have tried a system reset in my code and that doesn’t seem to remove the voltage on that rail.

Can you think of any other way to completely cut off A5 in code?

In that case, I'd try something like this Not positive it will help, but worth a try:

SPI.end();
pinMode(A5, OUTPUT);
digitalWrite(A5, LOW);
pinMode(A5, INPUT);

I tried that yesterday and it shorted out my microphone board. Is there any deep sleep modes that will allow that pin to go back to normal? I don’t think there is from what I have read.