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

Parents
  • Hi,

    If I read the plot you had correctly, the voltage only drops down to 2.735, so I do not see how this could be a brown out reset. Did you read the RESETREAS register and confirm that it was a BOR or power-on reset (in both cases the register will be cleared), or do you just see a reset and assumed a BOR?

    If you just see a reset, the first thing I would suspect was some form of firmware error, where the error handler triggers a soft reset, which is the default behaviour (and particularily usefull in release builds in order to recover from unhandled errors). I suggest starting with basic debugging to see what is happening. If you have loggign enabled, you can check the log. You can also add CONFIG_RESET_ON_FATAL_ERROR=n in your prj.conf to not get a reset when an error has occured).

  • 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?

  • 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.

  •  Hi  ,

    sry I have to dig out the post again, but I've now prepared some Sensors with additional 100µF/6V3 and 47µF/10V ceramic capacitors. I've made some comparison measurements with NRF Power Profiler 2. I'm a little puzzled, cause with the additional capacitors, the current spikes are higher, than without.

    Measurement 1: Sensor with 47µF

    Measurement 2: Sensor with 2x47µF and 100µF

    The really odd thing is: If I meassure the voltage drop, the more capacitors helps (and I've not swaped any measurements, I checked it three times)

    Any suggestions?

    Thanks and Best Regards,

    Phobios

  • The curves are as expected; pity the PPK-2 doesn't allow voltage measurement, and thence power calculations, which would illustrate why. Current is a don't care here, voltage dip is all that matters as the voltage dip is what causes brownout issues. For a given power burst (typically BLE Tx transmit) higher available current (bigger caps) means lower voltage dip: volts x amps will be similar in both cases, except losses will be higher with the smaller capacitance as more current during the pulse is sourced via the battery resistive internals which is bad for the battery, bad for the planet and reduces anticipated product life.

Reply
  • The curves are as expected; pity the PPK-2 doesn't allow voltage measurement, and thence power calculations, which would illustrate why. Current is a don't care here, voltage dip is all that matters as the voltage dip is what causes brownout issues. For a given power burst (typically BLE Tx transmit) higher available current (bigger caps) means lower voltage dip: volts x amps will be similar in both cases, except losses will be higher with the smaller capacitance as more current during the pulse is sourced via the battery resistive internals which is bad for the battery, bad for the planet and reduces anticipated product life.

Children
No Data
Related