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");
wait
terminates almost immediately due to other interrupts. With them disabled it behaves. Sleep doesn't though, it is the same. \$\endgroup\$