nRF52840: How do I read the "Event Register"?

nRF52840, SDK 17.0.2, SD140 v7.0.1

My firmware application is happily sleeping via calls to nrf_pwr_mgmt_run(), waking up to service interrupts and do work, then going to back to sleep.

Sometimes, something happens shortly after a BLE central connects to my device- for some reason, my calls to nrf_pwr_mgmt_run() immediately return, and the nRF52840 spins in the main loop instead of sleeping. This causes a large amount of energy from the battery to be wasted doing nothing, and the product does not last as long.

I'm trying to trace down the source of what's immediately waking the nRF52840 back up, so I can fix it.

I would like to know if SoftDevice is constantly waking up my application, or if I'm calling something like app_timer_start / app_timer_stop that fires a software interrupt, which sets the event flag.

For logging purposes, how do I read the "Event Register" that WFE and SEV operate on? I don't want to set it, but I do want to read it. If it's set before I call into nrf_pwr_mgmt_run(), then I know it's something I did in my loop.

Thanks,

Charles Nicholson

  • Hello Charles,

    Like the __WFE instruction, a single call to nrf_pwr_mgmt_run() will not make the device enter sleep if the event register has been set (e.g. after servicing an ISR). This is why you will see that this function is always placed within a loop in our code examples.

    In your case, could it be an option to place the nrf_pwr_mgmt_run() function in a spinlock loop? Something like this:

    do {
        nrf_pwr_mgmt_run();
    } while (app_wakeup == false);

    where "app_wakeup" is a state flag set in your interrupt handler(s) to tell the program when to exit sleep.

    I'm trying to trace down the source of what's immediately waking the nRF52840 back up, so I can fix it.

    You can try to check which interrupt it was woken up by with this code:

        uint8_t cnt; 
        /*Send Event on Pending interrupts*/
        SCB->SCR |= SCB_SCR_SEVONPEND_Msk;
    
        // Enter main loop.
        for (;;)
        {
            while(NRF_LOG_PROCESS());
        
            cnt=0;
    
            CRITICAL_REGION_ENTER();
            /* Call nrf_pwr_mgmt_run() repeatedly as long as there are no pending interrupts */
            do {
              nrf_pwr_mgmt_run();
              cnt++;
            } while (0 == (NVIC->ISPR[0] | NVIC->ISPR[1]));
            
            /* Loop through the ISPR registers to check which IRQn woke the CPU */
            for (int irqn = POWER_CLOCK_IRQn; irqn <= SPIM3_IRQn; irqn++)
            {
                if (NVIC_GetPendingIRQ(irqn)) 
                {
                    NRF_LOG_INFO("IRQ pending %d", irqn);
                }
            }
    
            CRITICAL_REGION_EXIT();
            NRF_LOG_INFO("nrf_pwr_mgmt_run() was run %d times", cnt);
        }
    }

    For logging purposes, how do I read the "Event Register" that WFE and SEV operate on?

    The event register is not accessible to the CPU, unfortunately.

    Best regards,

    Vidar

  • Thanks very much for the detailed and quick response, I appreciate it!

    Best,

    Charles

  • As an update for anyone searching for IRQ activity data, I wrote the following assembly function to capture all IRQ activity, including SoftDevice.

    In C, make a 65-element table of uint32_t containing `cpu_isr_shim` for every entry. Set SCB->VTOR to the address of this array (it's a vector table), and the histogram will populate on every IRQ. Call cpu_isr_get_histogram to retrieve the histogram of uint32_t entries, one per IRQ #.

    It's cool to see the SoftDevice RADIO interrupt etc.

    .syntax unified
    
    .section .data
    .align 4
    s_irq_histogram:
      .word 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
      .word 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
      .word 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
      .word 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    
    .section .text
    
    .align 2
    .globl cpu_isr_get_histogram
    .thumb_func
    cpu_isr_get_histogram:
      ldr r0, [pc, #0]
      bx lr
      .word s_irq_histogram
    
    .globl cpu_isr_shim
    .thumb_func
    .align 2
    cpu_isr_shim:             // preserves regs except r12, thunks to ISR
      push {r0, r1}           // store r0 + r1
      ldr r12, [pc, #32]      // r12 = SCB
      ldr r12, [r12, #4]      // r12 = SCB->ICSR
      ubfx r12, r12, #0, #9   // r12 = (SCB->ICSR & 0x1FF) <irqn in 1-64>
      lsl r12, r12, #2        // r12 = irqn * 4
      ldr r0, [pc, #20]       // r0 = s_irq_histogram
      ldr r1, [r0, r12]       // r1 = s_irq_histogram[irqn * 4]
      adds r1, #1             // ++r1
      str r1, [r0, r12]       // s_irq_histogram[irqn * 4] = r1
      pop {r0, r1}            // restore r0 and r1
      ldr pc, [r12]           // unconditional jump to VTOR[irqn * 4]
      .word 0xe000ed00        // Address of SCB in the memory map
      .word s_irq_histogram

  • This looks like clever way to do it! Just curious, does the Softdevice IRQ vector get forwarded back to the softdevice again after it has been run through your 'cpu_isr_shim'?

  • Ha, thanks :) 

    On line 29, the "r12" register is loaded up with the offset into a vector table that represents the ISR corresponding to the current IRQ.

    This function assumes that the "true" (SoftDevice) vector table lives at address 0, which means that the function we want to call lives at "0 + *r12". Line 35 simply forces an unconditional branch to that ISR, so SoftDevice (and our application) get all of their respective interrupt handlers called.

    I'm actually planning on leaving this in our production firmware, so we can get IRQ histograms to better understand the system.

Related