2
\$\begingroup\$

I am in the process of trying to get a PIC32MX795F512L to run with lower power consumption. I'm trying to get it to enter sleep mode and then be woken by the watchdog timer. All pretty standard stuff.

My code works on the first triggering of the watchdog timer, but the second triggering isn't triggered as if it were in sleep mode, but in normal running mode. As a result it doesn't continue execution but instead resets the whole chip.

On the PIC32 the watchdog timer, when in sleep or idle mode, causes an NMI with the same vector as reset. You then check some flags in a register to see if it was caused by the watchdog in sleep mode, etc.

My startup code looks like this:

_reset:
    la      k0, RCON        # Load address of RCON register

    lw      k1, 0(k0)       # Get contents of the register
    and     k1, k1, 0x18    # We are only interested in 0x18
    sub     k1, 0x18        # Subtract 0x18
    beqz    k1, _ret_nmi    # and if the result is 0 (i.e., equal to 0x18) then branch.

    lw      k1, 0(k0)       # Same again but looking for 0x14.
    and     k1, k1, 0x14
    sub     k1, 0x14
    beqz    k1, _ret_nmi
    nop

    la      k0, _startup
    jr      k0                      # Jump to startup code
    nop

_ret_nmi:
    lw      k1, 0(k0)
    and     k1, 0xFFE3
    sw      zero, 0(k0)
    eret

Basically it looks for 0x18 or 0x14 being set in the RCON register, then clearing those bits and returning from an interrupt if they are set.

Sleep mode is entered by setting the SLEEP bit in OSCCON (which needs unlocking first), and according to the power saving manual for PIC32 is done like this:

// Standard unlock sequence
SYSKEY = 0x0;
SYSKEY = 0xAA996655;
SYSKEY = 0x556699AA;
OSCCONSET = 0x10; // Enable sleep mode
SYSKEY = 0x0;

You then enable the watchdog timer, "kick the dog" as it's known, and stop the CPU with a wait instruction:

WDTCONSET = 1<<15; // Turn on
WDTCONSET = 0x01; // Kick the dog!
uint32_t i = disableInterrupts(); // We don't want any old interrupt waking us up
asm volatile("wait");
restoreInterrupts(i);

So that causes the CPU to stop and then the NMI triggers after 1.024 seconds. The CPU restarts from the reset vector, the startup code checks the flags, finds it's an NMI, and returns from interrupt continuing with the next line of code.

The first time.

The second time the RCON register contains 0x10 instead of 0x18, so it acts like a timeout from not kicking the dog.

Inspecting OSCCON after the failed timeout the SLEEP bit seems to have been reset. Setting the SLEEP bit every time through the main loop just before sleeping has no effect.

However

If I do the exact same thing but using idle mode instead of sleep mode, everything works perfectly. The wait instruction continues after 1.024 seconds every time without failure.

So why does this not work as it should do in sleep mode?

Is there something obvious I am missing?

Update

I have tried forcing the CPU priority level to be the lowest possible before sleeping, but it has had no effect. This is the code I am using for it:

asm volatile("mfc0 $8, $12");
asm volatile("ins $8, $0, 10, 3");
asm volatile("mtc0 $8, $12");
asm volatile("wait");
\$\endgroup\$
10
  • \$\begingroup\$ disclaimer: I have never programmed a PIC. Only AVR and ARM. According to page 10-8 of this, the code you have posted places the device in idle mode. The sleep mode code is on page 10-4. This could be a clue: On page 10-9 it only mentions using eret to treat the WDT as an interrupt to return from only during Sleep mode. Maybe if powering up from idle mode, WDT doesn't count as an interrupt so eret won't be valid? \$\endgroup\$
    – Evan
    Commented Jul 20, 2014 at 0:36
  • \$\begingroup\$ No, it's definitely sleep mode. The subtle difference between the two is that sleep is set for sleep mode (OSCCONSET = 0x10) and is cleared for idle mode (OSCCONCLR = 0x10). All WDT timeouts in sleep or idle are an NMI, as detailed here 9.4.2: When the WDT module expires in Sleep or Idle, a NMI is generated. The NMI causes the CPU code execution to jump to the device reset vector. Although the NMI shares the same vector as a device Reset, registers and peripherals are not reset. You'll notice the code with it is what I have. \$\endgroup\$
    – Majenko
    Commented Jul 20, 2014 at 9:16
  • \$\begingroup\$ ... And, idle mode works fine, it's sleep mode that doesn't. I would consider some timing issue or something with the oscillator restarting, but: 9.6.4: There will be a time delay between the WDT event in Sleep mode and the beginning of code execution. The duration of this delay consists of the start-up time for the oscillator in use and the PWRT delay, if it is enabled. \$\endgroup\$
    – Majenko
    Commented Jul 20, 2014 at 9:18
  • \$\begingroup\$ Page 8-23 of this it states: "If the proximity timer is enabled and the pending interrupt priority is less than the temporal proximity priority, the processor does not remain in sleep. It transitions to idle and then goes to run, once the TPT times out. If the priority of the interrupt request is less than or equal to the current processor priority level, the device will switch to Idle mode and the processor will remain halted.". Is by any chance the processor switching from sleep to idle but then it just stays there? \$\endgroup\$
    – Evan
    Commented Jul 20, 2014 at 21:02
  • \$\begingroup\$ As you can see from my code, all interrupts are disabled during the sleep period. When that isn't done, and I use idle mode instead of sleep mode, the wait terminates almost immediately due to other interrupts. With them disabled it behaves. Sleep doesn't though, it is the same. \$\endgroup\$
    – Majenko
    Commented Jul 20, 2014 at 21:06

1 Answer 1

1
\$\begingroup\$

Ok, I now have this working properly. And here's how:

  1. Read the manual.
  2. Throw the manual away.
  3. Write a working program in a different IDE / compiler.
  4. Disassemble the resultant program and see how it should be done.

XC32 builds in code to handle the NMI into its reset handler for you, and the code it generates bears no resemblance to what the manual says you should do. Instead it examines the NMI status of C0. If it was an NMI then do an eret, otherwise start the program normally.

Here's the relevant bits of the program disassembled:

bfc00000:   401a6000    mfc0    k0,c0_status
bfc00004:   7f5a04c0    ext k0,k0,0x13,0x1
bfc00008:   13400005    beqz    k0,bfc00020 <_no_nmi>
bfc0000c:   00000000    nop
bfc00010:   3c1a9d00    lui k0,0x9d00
bfc00014:   275a02a8    addiu   k0,k0,680
bfc00018:   03400008    jr  k0 <_nmi_handler>
bfc0001c:   00000000    nop

9d0002a8 <_nmi_handler>:
9d0002a8:   401a6000    mfc0    k0,c0_status
9d0002ac:   3c1bffbf    lui k1,0xffbf
9d0002b0:   377bffff    ori k1,k1,0xffff
9d0002b4:   035bd024    and k0,k0,k1
9d0002b8:   409a6000    mtc0    k0,c0_status
9d0002bc:   42000018    eret

So you see it doesn't care why the NMI happened - it just erets regardless.

With that style of doing it in my startup routine it now works!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.