Voltage drop triggers BOR reset; MCU occasionally completely freezes

Hi,

I'm facing a critical issue with our sensor's power setup and would really appreciate any insights or suggestions.

Our sensor is currently using a CR2477 battery, and I'm running into problems due to the load resistance of the battery. When transmitting data, especially after switching from PHY_1M to PHY_CODED_S8, the battery voltage drops enough to trigger the Brown Out Reset (BOR), which then resets the sensor. I noticed this specifically with PHY_CODED_S8 since the transmission takes longer, causing a larger voltage drop.

Occasionally, the sensor also completely hangs—even after the voltage recovers back to 3V again. From my understanding, this seems to be expected behavior with the nRF52 when experiencing a brownout, as only a full power cycle can bring it back online - why is that so and can I prevent this?

Has anyone experienced similar issues or found software and/or hardware solutions to prevent these resets? I’d be especially interested in any software adjustments to handle the BOR more gracefully or hardware tips for improving power stability with high load resistance batteries in future versions of the sensor.

Currently I'm powering directly via the CR2477 to VDD and VDDHV with a 47µF and a 4.7µF capacitor (and of course the standard 100nF on each power input)
DCDC on µC is enabled

Setup:
NRF52840, NCS2.6.1, Zephyr

Thanks in advance for any advice!

Best Regards,
Phobios

  • Hi Einar,

    I'm certain it is a BOR; the RESETREAS is cleared and also the RTT logging doesn't show any information about another reset. The plot was only a measurement of different capacitors with a known load; it was not the critical battery which created the BOR.

    I'm thinking of setting up the power-fail-comparator, and if the voltage gets critical (say about 2V) I'll only sending sporadically the urgent messages. Is there an example of using the POR in Zephyr?

    Additionally to the POR I'm decreasing the TX power. Currently I'm using +8dBm. Is there an automatic adaptive way to alter the TX power depending on the RSSI e.g. with good RSSI values I can decrease the TX power one step? Or do I have to program this in my user firmware?

  • Open-circuit voltage measurement on a coin cell is almost meaningless; a coin cell discharged by over 90% of capacity can measure 2.95 volts after suitable rest period, so looking like almost full capacity but actually nearly fully discharged. I have seen this on batteries over 6 years old. I'll post some measurement code when I get a moment, hopefully tomorrow.

  • Here's some code which will characterise a Lithium coin cell; the same code can be used to disallow radio transmissions when the battery is determined to be exhausted. The code turns on a high-current load (CPU plus Radio Tx or CPU plus Radio Rx) for 10 seconds every 2 minutes, and measures the difference in battery voltage between the start and end of transmission. In a real application this code would be run after a reset and periodically by disabling any higher-level firmware such as a SoftDevice. Note here I've enabled the HFCLK to get accurate UART timing, but that could be turned off in sleep.

    Sample output on a CR2032; the first reading is low since a rest time was not used prior to the load (note using nRF52840 CPU sleep Radio Tx Only 11mA):

    Starting Battery Pulse load test, Tx Pulse: 10 Secs, Rest: 110 Secs
    Coin Cell at     10 secs: After Rest: 2822mV, After Tx Pulse: 2600mV, Droop:  222mV
    Coin Cell at    130 secs: After Rest: 2876mV, After Tx Pulse: 2608mV, Droop:  268mV
    Coin Cell at    250 secs: After Rest: 2882mV, After Tx Pulse: 2605mV, Droop:  277mV
    ...
    Coin Cell at  15354 secs: After Rest: 2809mV, After Tx Pulse: 2505mV, Droop:  304mV
    Coin Cell at  15474 secs: After Rest: 2817mV, After Tx Pulse: 2505mV, Droop:  312mV
    ...
    Coin Cell at  54313 secs: After Rest: 2745mV, After Tx Pulse: 2403mV, Droop:  342mV
    Coin Cell at  54433 secs: After Rest: 2742mV, After Tx Pulse: 2401mV, Droop:  341mV
    ...
    Coin Cell at 100106 secs: After Rest: 2746mV, After Tx Pulse: 2418mV, Droop:  328mV
    Coin Cell at 100226 secs: After Rest: 2748mV, After Tx Pulse: 2416mV, Droop:  332mV
    

    Main code:

    void TestRadioAsBatteryLoad(void)
    {
       // 11.35mA NRF_RADIO->TXPOWER = 0x08
       // 23.17mA NRF_RADIO->TXPOWER = 0x04
       // 17.42mA NRF_RADIO->TXPOWER = 0x00, nRF52840 CPU sleep Radio Tx Only 11mA
    
       char CoinInfoPacket[240] = "?";
       uint16_t BatteryVoltsA = 0, BatteryVoltsB = 0;
       mClocksInit();
       mRtcInit();
       snprintf(CoinInfoPacket, sizeof(CoinInfoPacket)-1, "Starting Battery Pulse load test, Tx Pulse: %hu Secs, Rest: %hu Secs\r\n", BATTERY_LOAD_TIME_MSECS, BATTERY_REST_TIME_MSECS);
       uartSend(CoinInfoPacket, strlen(CoinInfoPacket));
       while (true)
       {
          // Check for load on time
          if (mStartLoadTest)
          {
             BatteryVoltsA = GetBatteryVoltageLocal();
             // Start HFCLK crystal oscillator for radio
             NRF_CLOCK->TASKS_HFCLKSTART = 1;
             __DSB();
             while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) ;
             NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
             NRF_RADIO->MODE = 0; // 1 Mbps Nordic proprietary radio mode
          // NRF_RADIO->TXPOWER = 0x08; // +8dBm 11.35mA
          // NRF_RADIO->TXPOWER = 0x04; // +4dBm 23.17mA
             NRF_RADIO->TXPOWER = 0x00; //  0dBm 17.42mA, nRF52840 CPU sleep Radio Tx Only 11mA
             NRF_RADIO->TASKS_TXEN = 1;
             mStartLoadTest = false;
          }
          // Check for load turn-off time
          if (mStopLoadTest)
          {
             BatteryVoltsB = GetBatteryVoltageLocal();
             NRF_RADIO->TASKS_STOP = 1;
             __DSB();
             NRF_RADIO->TASKS_DISABLE = 1;
             __DSB();
             while (NRF_RADIO->EVENTS_DISABLED == 0) ;
             NRF_RADIO->POWER = 0; // Set RADIO back to defaults
             __DSB();
             NRF_RADIO->POWER = 1;
             // Drops to 1uA after Radio power test with Radio powered down
             // Starting Battery Pulse load test, Tx Pulse: 10 Secs, Rest: 110 Secs
             // Coin Cell at     10 secs: After Rest: 2817mV, After Tx Pulse: 2612mV, Droop:  205mV
             // Coin Cell at    130 secs: After Rest: 2881mV, After Tx Pulse: 2619mV, Droop:  262mV
             // Coin Cell at    250 secs: After Rest: 2884mV, After Tx Pulse: 2614mV, Droop:  270mV
             // Coin Cell at    370 secs: After Rest: 2877mV, After Tx Pulse: 2619mV, Droop:  258mV
             snprintf(CoinInfoPacket, sizeof(CoinInfoPacket)-1, "Coin Cell at %6u secs: After Rest: %4humV, After Tx Pulse: %4humV, Droop: %4humV\r\n", mBatteryTestTimer/RTC_TICKS_PER_SECOND, BatteryVoltsA, BatteryVoltsB, BatteryVoltsA-BatteryVoltsB);
             uartSend(CoinInfoPacket, strlen(CoinInfoPacket));
             mStopLoadTest = false;
             // Stop HFCLK crystal oscillator to reduce sleep current
             NRF_CLOCK->TASKS_HFCLKSTOP = 1;
             __DSB();
          }
          // Wakeup RTC_TICKS_PER_SECOND (8) times per second
          while(!mRTC_Tick)
          {
             // Errata 220: CPU: RAM is not ready when written - Disable IRQ while using WFE
             // Enable SEVONPEND to disable interrupts so the internal events that generate the interrupt cause wakeup in __WFE context and not in interrupt context
             // Before: ENABLE_WAKEUP_SOURCE -> __WFE -> WAKEUP_SOURCE_ISR -> CONTINUE_FROM_ISR  next line of __WFE
             // After:  ENABLE_WAKEUP_SOURCE -> SEVONPEND -> DISABLE_INTERRUPTS -> __WFE -> WAKEUP inside __WFE -> ENABLE_interrupts -> WAKEUP_SOURCE_ISR
             //
             // Errata 75: MWU: Increased current consumption
             // This has to be handled by turning off MWU but it is used in SoftDevice
             // see https://infocenter.nordicsemi.com/index.jsp?topic=%2Ferrata_nRF52832_EngB%2FERR%2FnRF52832%2FEngineeringB%2Flatest%2Fanomaly_832_75.html
             //
             // Errata 220: Enable SEVONPEND
             SCB->SCR |= SCB_SCR_SEVONPEND_Msk;
             __disable_irq();
             // Clear the internal event register and wait for event - note an interrupt is required
             __WFE(); __SEV(); __WFE(); __NOP(); __NOP(); __NOP(); __NOP();
             __enable_irq();
          }
          // Get here RTC_TICKS_PER_SECOND (8) times per second
          mRTC_Tick = false;
       }
    }

    Supporting functions:

    #define BATTERY_LOAD_TIME_MSECS  10     // mSecs: Hard load on battery, CPU plus with Radio Tx on
    #define BATTERY_REST_TIME_MSECS 110     // mSecs: Rest time for battery, CPU sleep with Radio Tx off
    #define RTC_TICKS_PER_SECOND      8UL   // RTC ticks per second using 8 Hz
    
    static NRF_RTC_Type *pBatteryLoadRTC  = NRF_RTC2;
    IRQn_Type BatteryLoadRTC_IRQn         = RTC2_IRQn;
    volatile bool mRTC_Tick               = true;
    volatile bool mLoadTestIsActive       = true;
    volatile bool mStartLoadTest          = true;
    volatile bool mStopLoadTest           = false;
    volatile uint32_t mBatteryTestTimer   = (RTC_TICKS_PER_SECOND/2);  // Elasped test time, add rounding for seconds
    volatile uint32_t mBatteryLoadTimer   = 0UL;
    volatile uint32_t mBatteryRestTimer   = 0UL;
    
    void RTC2_IRQHandler(void)
    {
        if (pBatteryLoadRTC->EVENTS_TICK == 1)
        {
            pBatteryLoadRTC->EVENTS_TICK = 0;
            mRTC_Tick = true;
            // Update elapsed test timer, 8 ticks/second
            mBatteryTestTimer++;
            // Check for end-of-test
            if (mLoadTestIsActive && (++mBatteryLoadTimer >= BATTERY_LOAD_TIME_MSECS*RTC_TICKS_PER_SECOND))
            {
               mLoadTestIsActive = false;
               mStopLoadTest = true;
               mBatteryRestTimer = 0UL;
            }
            // Check for start-of-test
            if (!mLoadTestIsActive && (++mBatteryRestTimer >= BATTERY_REST_TIME_MSECS*RTC_TICKS_PER_SECOND))
            {
               mLoadTestIsActive = true;
               mStartLoadTest = true;
               mBatteryLoadTimer = 0UL;
            }
        }
        // Clear any pending hardware register bus operations
        __DSB();
    }
    
    static void mRtcInit(void)
    {
       // Start RTC, enable TICK event
       // Select tick: 12 bit prescaler for COUNTER frequency (32768/(PRESCALER+1)). Must be written when RTC is stopped
       pBatteryLoadRTC->PRESCALER = 4095; // max prescaler 0xFFF -> 8 Hz "Tick"
       pBatteryLoadRTC->EVTENSET = RTC_EVTENSET_TICK_Msk;
       // Set interrupt priority and enable interrupt
       NVIC_SetPriority(BatteryLoadRTC_IRQn, 6);
       NVIC_ClearPendingIRQ(BatteryLoadRTC_IRQn);
       NVIC_EnableIRQ(BatteryLoadRTC_IRQn);
       // Enable tick interrupt
       pBatteryLoadRTC->INTENSET = 0x00001;
       // The update of COUNTER relies on a stable LFCLK, TASKS_START while LFCLK is not running will start
       // LFCLK, but the update will be delayed by up to ~250 us
       pBatteryLoadRTC->TASKS_START = 1;
    }
    
    // Input range = (0.6 V)/(1/6) = 3.6 V
    #define ADC12_COUNTS_PER_VOLT 1138  // 12-bit Mode
    uint16_t GetBatteryVoltageLocal(void)
    {
       uint16_t result = 9999;
       uint32_t timeout = 10000;
       volatile int16_t buffer[8];
       uint32_t i=0;  // SAADC Channel
       // Configure SAADC singled-ended channel, Internal reference (0.6V) and 1/6 gain
       NRF_SAADC->CH[i].CONFIG = (SAADC_CH_CONFIG_GAIN_Gain1_6    << SAADC_CH_CONFIG_GAIN_Pos)   |
                                 (SAADC_CH_CONFIG_MODE_SE         << SAADC_CH_CONFIG_MODE_Pos)   |
                                 (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) |
                                 (SAADC_CH_CONFIG_RESN_Pullup     << SAADC_CH_CONFIG_RESN_Pos)   |
                                 (SAADC_CH_CONFIG_RESP_Pullup     << SAADC_CH_CONFIG_RESP_Pos)   |
                                 (SAADC_CH_CONFIG_TACQ_40us       << SAADC_CH_CONFIG_TACQ_Pos);
       NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_12bit << SAADC_RESOLUTION_VAL_Pos;
       // Analog positive and negative input channels have strange numbering:
       //  PSELP_NC           0UL  Not connected
       //  PSELP_AnalogInput0 1UL  AIN0
       //  PSELP_AnalogInput1 2UL  AIN1
       //  PSELP_AnalogInput2 3UL  AIN2
       //  PSELP_AnalogInput3 4UL  AIN3
       //  PSELP_AnalogInput4 5UL  AIN4
       //  PSELP_AnalogInput5 6UL  AIN5
       //  PSELP_AnalogInput6 7UL  AIN6
       //  PSELP_AnalogInput7 8UL  AIN7
       //  PSELP_VDD          9UL  VDD
       NRF_SAADC->CH[i].PSELP = SAADC_CH_PSELN_PSELN_VDD << SAADC_CH_PSELN_PSELN_Pos;
       NRF_SAADC->CH[i].PSELN = SAADC_CH_PSELN_PSELN_NC << SAADC_CH_PSELN_PSELN_Pos;
       // Enable SAADC
       NRF_SAADC->ENABLE = 1;
       // Enable command
       nrf_saadc_enable();
       NRF_SAADC->RESULT.PTR = (uint32_t)buffer;
       NRF_SAADC->RESULT.MAXCNT = 1;
       nrf_saadc_event_clear(NRF_SAADC_EVENT_END);
       nrf_saadc_task_trigger(NRF_SAADC_TASK_START);
       nrf_saadc_task_trigger(NRF_SAADC_TASK_SAMPLE);
       while (0 == nrf_saadc_event_check(NRF_SAADC_EVENT_END) && timeout > 0)
       {
          timeout--;
       }
       nrf_saadc_task_trigger(NRF_SAADC_TASK_STOP);
       nrf_saadc_event_clear(NRF_SAADC_EVENT_STARTED);
       nrf_saadc_event_clear(NRF_SAADC_EVENT_END);
       // Disable command
       nrf_saadc_disable();
       if (timeout != 0)
       {
          // Calculate mVolt value, assume 16-bit unsigned for prints
          result = (uint16_t)((((int32_t)buffer[0] * 1000L)+(ADC12_COUNTS_PER_VOLT/2)) / ADC12_COUNTS_PER_VOLT);
       }
       return result;
    }

    See also jzone post  creating-a-load-on-a-battery-using-the-radio and other older posts where I propose load tests

  • Hi,

    I see. There are no official samples of using the power-fail-comparator in the nRF Connect SDK, this is a small code snippet that demonstrate how it can be used (with CONFIG_NRFX_POWER=y added to prj.conf or similar):

    #if CONFIG_NRFX_POWER
    #include <nrfx_power.h>
    
    void pof_cb(void)
    {
    	printk("POF event\n");
    }
    
    static void pof_enable(void)
    {
    	nrfx_power_pofwarn_config_t pof_config = {
    		.handler = pof_cb, 
    		.thr = NRF_POWER_POFTHR_V22,
    		.thrvddh = NRF_POWER_POFTHRVDDH_V29
    	};
    
    	nrfx_power_pof_init(&pof_config);
    	nrfx_power_pof_enable(&pof_config);
    }
    #endif 

    The HCI Power Control sample demonstrate settign the Tx power runtime. You could do it adaptively based on RSSI (and/or battery condition), but that would be up to you. 

  • Thanks a lot hmolesworth and Einar with your code examples. Both works fine and helped me a lot.

Related