This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Watchdog reset: determining cause

Hi,

I'd like to determine the cause of a watchdog reset. I was hoping to utilize the WDT event handler (passed in to nrf_drv_wdt_init()) to record the PC of the application code at the time the watchdog timeout occurred, and save that to a word in uninitialized RAM (I am aware of the time constraints in the handler, and the fact that RAM isn't guaranteed to be retained on a WDT reset).

I'd imagine the application PC code must be saved in a register or pushed on to the stack at some point before the event handler is called.

Do you know where I can find this?

  • If you look at the code for the WDT driver it's very simple, the function you pass in is called directly from the interrupt handler (it's only 4 lines long) so the stack will be set up just as it was when the interrupt was taken (standard Cortex-M interrupt stack) plus whatever was stacked in that function before it called you. That should be quite simple to unwind although you'll have to look at the actual generated assembly to see where on the stack you have to look (you could use the frame pointer but that's usually omitted in release code).

  • Thanks for the suggestion RK! I was able to successfully unwind the stack and find the PC at the time of the WDT timeout. Here's the code (posting as an answer since there's not enough room as a comment):

    /* 
     * wdt_event_handler() - watchdog timeout event handler
     *
     * This handler is called from the ISR WDT_IRQHandler, which has previously 
     * pushed lr onto the interrupt stack, then subtracted 12 from the SP before
     * calling this routine.  So we're adding 12 to get back to the lr
     * (which contains EXC_RETURN); then adding another 28 bytes to get back
     * to the stacked PC (since xPSR, PC, LR, R12, R3-R0 are pushed onto the 
     * interrupt stack in that order), and saving that address in the m_wdt_addr
     * variable.
     * 
     * NOTE: The max amount of time we can spend in WDT interrupt is two cycles 
     * of 32768[Hz] clock - after that, the WDT reset occurs.
    */
    
    static void wdt_event_handler(void)
    {
        __asm volatile (
                        "   ldr r0, [sp, #40]     \n" 
                        "   ldr r1, =m_wdt_addr   \n" 
                        "   str r0, [r1]          \n"
                    );
    }
    
  • If you're using gcc, aren't deeply coupled to the nRF5 SDK WDT API, and if necessary translate this to plain C, the following also works:

    extern "C" {
      /* This little joy loads r0 with the main or process stack pointer then
       * jumps to the systemState watchdog IRQ handler, which will then
       * pull the exception return address out and use that as the value
       * of last_pc showing where the program was when the reset occurred.
       *
       * See https://stackoverflow.com/questions/38618440
       */
      __attribute__((__naked__))
      void WDT_IRQHandler ()
      {
        __ASM (
    #if (NRF51 - 0) /* Cortex M0/M1 */
              "mrs r0, msp\n\t"
              "mov r1, lr\n\t"
              "mov r2, #4\n\t"
              "tst r1, r2\n\t"
              "beq 1f\n\t"
              "mrs r0, psp\n"
    #else /* Cortex M3/M4 */
              "tst lr, #4\n\t"
              "ite eq\n\t"
              "mrseq r0, msp\n\t"
              "mrsne r0, psp\n\t"
    #endif /* ARM Cortext Variant */
              "1:\tldr r1, =%0\n\t"
              "bx r1\n"
              : // no outputs
              : "i" (nrfcxx::systemState::wdt_irqhandler)
              );
      }
    } // extern C
    
    void
    systemState::wdt_irqhandler (void * sp)
    {
        auto statep = nrfcxx::systemState::statep_;
        if (statep) {
          struct exception_frame_type {
            uint32_t r0;
            uint32_t r1;
            uint32_t r2;
            uint32_t r3;
            uint32_t r12;
            uint32_t lr;
            uint32_t pc;
            uint32_t xpsr;
          } const * const efp = reinterpret_cast<const exception_frame_type *>(sp);
    
          statep->wdt_status = NRF_WDT->REQSTATUS;
          if (efp) {
            statep->last_pc = efp->lr;
            statep->reset_reas_ = state_type::RESET_REAS_WDTBARKED;
          }
        }
        do_reset(false);
    }
    

Related