chipKIT® Development Platform

Inspired by Arduino™

Power saving mode on the chipKIT

Created Mon, 21 Jul 2014 10:40:43 +0000 by majenko


majenko

Mon, 21 Jul 2014 10:40:43 +0000

I have been having a bit of a tinker with the sleep mode of the PIC32 and using the watchdog to wake it up periodically. I have modified the bootloader to properly handle the NMI caused by the watchdog wakeup signal (code now merged into the main bootloader repository, so you can build your own if you have the tools, or wait for the next mass re-compile of all the bootloader .hex files), and implemented a small Watchdog library.

After much confusion caused as far as I could tell by the PIC32 manuals basically being completely wrong, I have it working.

But what are the results?

Well, with the watchdog set to a 1.024 second wakeup (the default on the chipKIT bootloader), I have a little sketch which just outputs a line of text to the serial once a second.

Comparing a version using delay() with a version using sleep mode, the differences are quite astounding.

Here I have the sketch running powered from a 12V source through the barrel jack of the MAX32. This is the delay() version as a baseline: [attachment=2]12v-nosleep.png[/attachment] It's running up around the 150mA - 160mA range. The sharp rise at the start is where I release the reset button to start the sketch off so you can see how much is due to the CPU executing.

Of course, 12V is not very efficient with a linear regulator, so what if we power it from 5V and bypass the regulator? Well, this is what we get: [attachment=1]5v-nosleep.png[/attachment] A bit better - certainly less noisy isn't it? Those linear regulators are a bit of a pest really. And we're down to 93mA. Much nicer. Still a long way to go though.

So now... the big question... how much saving would we get using the sleep mode of the PIC32? Drum roll please... [attachment=0]5v-sleep.png[/attachment] Wow, that's quite something isn't it? A thing of beauty. You can see the big spike where the bootloader loads up and executes, running at full power, then as soon as sleep mode kicks in, wallop! Down goes the current! You can even see where it's waking up every second to send out through the serial port. Averaging just 13mA!

These are very crude results though, and I am not 100% certain of the values (I just knocked up a quick 1Ω shunt and ADC combination to measure it). My DMM is telling me that in power saving mode it's running at just 3.8mA, so it might be even lower than I am showing here. I could do with doing some more scientific tests really. Some better equipment is needed to measure these lower currents :)

Still, the results are impressive enough at 13mA.

If you want the library it's here: [url]https://github.com/majenkotech/Watchdog[/url]


tcsaba101

Mon, 21 Jul 2014 12:26:23 +0000

Dear Majenko!

Just in time! I have entered the site to collect the chipKIT series lowpower info, because I want to setup a datalogger application with a Fubarino SD, and since I need about only 10smps, a plenty of time could be idle. This will be in a car battery operated datalogger, the consumption is important when not running.

Thanks for the post.

Recently I have finished (almost) my battery tester project using uno32 board + mpide (elf file), with MPLABX + REALICE debugger (also tried PicKit3, chipKitPGM and ICD3). It works well, but very uncomfortable because mpide and the temporary generated elf file.

I haven't scanned the forum recently, pls inform: did you have andvanced to have the solution of the integration of uecide and MPLABX (elf file generation)?

Thanks, Csaba


majenko

Mon, 21 Jul 2014 13:36:48 +0000

Just working on that now :P


majenko

Mon, 21 Jul 2014 15:28:28 +0000

This might be of interest to you: [url]http://uecide.wordpress.com/2014/07/21/debugging-chipkit-sketches-in-mplab-x/[/url]


tcsaba101

Mon, 21 Jul 2014 16:04:15 +0000

It is excellent, thanks. Just in time again, nice day today.

I have had difficulties to start the beta and work side by side with the existing version. It was a month ago I tried last time and probably now it has been settled. I will try as I finished the present job.

I am using the editor of MPLABX with the external editor config of the IDE. This editor very convenient, 3-4 paralell editor window instances, enables me to make faster coding.

What is the main difference between the mpide and uecide integration besides the permanent elf file?

Am I thinking right?: if we don't attach the bootloader to the code at the final programming, the config bits are can (must) be set by us, and the watchdog and low power operation is fully functional? Then it cant be accessed only by the ICSP connection. A chipKIT HW based embedded solution......


majenko

Mon, 21 Jul 2014 16:50:39 +0000

There is two ways you can do it:

  1. Use "pickit2" as the programmer, which uses a linker script with embedded config bits, so you don't need to worry about it, or
  2. Do it Keith's way where you use the normal programmer settings and also load in the bootloader file to be combined together.

The choice is yours. Personally I like having it all in one and just using the pickit2 generated elf file, but the avrdude file, plus the bootloader file, is just as good.

Either way you would want to put the bootloader back on after you have finished debugging.

The beta is very soon going to be the main version, so feel free to ditch your old copy. I think I have cured all the old->beta upgrade bugs, but downgrading takes some manual intervention (not that you'd want to).


tcsaba101

Mon, 21 Jul 2014 18:00:23 +0000

To understand your description I need to follow it in practice.

I suppose the mpide integration using Keith's way.

The bootloader many times makes problems in the final product. When you connect to serial it is reseting, what I don't need in most of the cases. The config bits the other what sometimes I should change.

This way we have the choice.


majenko

Mon, 21 Jul 2014 22:47:32 +0000

The serial reset thing is a hardware function, not a bootloader function. To disable it just place a 22µF capacitor between RESET and GND on the power header. You won't be able to auto-upload then though, so take the capacitor away when you're developing.


pito

Tue, 22 Jul 2014 20:18:09 +0000

How the stuff actually works? My understanding is we need ie. a delay(x) function run in the sleep mode. A fixed 1.024sec wdt delay is not much useful, though.. Or something like narcoleptic lib..


majenko

Tue, 22 Jul 2014 20:28:47 +0000

It's very useful if you care more about power consumption that precise timing. The time is set by the config flags as a postscaler of the 32KHz internal oscillator.

00000  1:1        1ms
00001  1:2        2ms
00010  1:4        4ms
00011  1:8        8ms
00100  1:16       16ms
00101  1:32       32ms
00110  1:64       64ms
00111  1:128      128ms
01000  1:256      256ms
01001  1:512      512ms
01010  1:1024     1.024s (default for our bootloaders)
01011  1:2048     2.048s
01100  1:4096     4.096s
01101  1:8192     8.192s
01110  1:16384    16.384s
01111  1:32768    32.768s
10000  1:65536    65.536s
10001  1:131072   131.072s
10010  1:262144   262.144s
10011  1:524288   524.288s
10100  1:1045876  1045.876s (about 17 3/4 minutes).

If you want a different time that 1.024s then build yourself a new bootloader (Damn we need a way to set those flags from within the bootloader...)


pito

Tue, 22 Jul 2014 20:32:49 +0000

I do not understand what you mean. In practice you need a delay(x) to be run in sleep. With wdt you can set any delay with an 1ms resolution, what covers 99% applications. You need simply mimic the narcoleptic library - that is it (the narcoleptic lib has got 16ms resolution because of how the wdt works in atmega - you can do 1ms with 32mx fortunately). Narcoleptic:

void NarcolepticClass::delay(int milliseconds) {
  while (milliseconds >= 8000) { sleep(WDTO_8S); milliseconds -= 8000; }
  if (milliseconds >= 4000)    { sleep(WDTO_4S); milliseconds -= 4000; }
  if (milliseconds >= 2000)    { sleep(WDTO_2S); milliseconds -= 2000; }
  if (milliseconds >= 1000)    { sleep(WDTO_1S); milliseconds -= 1000; }
  if (milliseconds >= 500)     { sleep(WDTO_500MS); milliseconds -= 500; }
  if (milliseconds >= 250)     { sleep(WDTO_250MS); milliseconds -= 250; }
  if (milliseconds >= 125)     { sleep(WDTO_120MS); milliseconds -= 120; }
  if (milliseconds >= 64)      { sleep(WDTO_60MS); milliseconds -= 60; }
  if (milliseconds >= 32)      { sleep(WDTO_30MS); milliseconds -= 30; }
  if (milliseconds >= 16)      { sleep(WDTO_15MS); milliseconds -= 15; }
}

You will have 21 if(milliseconds>=..) with pic32mx covering 1ms to 1048secs with 1ms resolution .. Cool! :ugeek: I do not understand for what is 1.024sec in sleep good for.. Blinking a led?


majenko

Tue, 22 Jul 2014 20:51:40 +0000

It's good for waking up after "around 1 second" to do something. If you want to sample a sensor roughly once a second, and the rest of the time you're just twiddling your thumbs, then you could sleep for that roughly 1 second and save battery power.

Admittedly 1 second-ish isn't much time, and sleeping for say 10 seconds or 30 seconds, or 5 minutes, etc would be more useful...


pito

Tue, 22 Jul 2014 21:01:33 +0000

Forget to mess with the fixed delay in the bootloader, just enable the stuff such the "wdelay()" will work after you leave the bootloader. Make the Narcoleptic lib for pic32mx, what is 5min of work for you and the people will be happy :) You can set "any" delay with 1ms resolution then. When doing a delay shorter than 2096secs the pic32mx wakes up shortly max 21x times worst case within that sleep period. And you get a programmable sleep delay with 1ms resolution.. ;)


majenko

Tue, 22 Jul 2014 21:12:33 +0000

Thing is, it's not 1ms.

You need to set the config flags for 1ms in the bootloader, as they cannot be modified by the sketch (bit silly that design choice, but it's what we're stuck with). Then the 1ms is the timeout of the WDT. On top of that you then have the startup time for the oscillator, any startup timer delay, etc, so the 1ms is just how long it will sleep for, not how long it will be before the 'wait' instruction finishes.

So even with a 1ms granularity you're not going to get precise programmable delays, and you're at the mercy of the bootloader that's installed, and everyone who has a board at the moment has a 1.024s delay pre-programmed in.

I wonder what happens if you erase and re-program the config bits "on the fly" from inside a sketch? What would happen to the running sketch when you blank the oscillator configuration? Are those config bits read at power up, or are they referenced all the time for the current configuration? Would the whole thing just go up in smoke?


pito

Tue, 22 Jul 2014 21:26:31 +0000

I still do not understand the topic.. You want to introduce a power saving mode. That is great! So the power saving mode you have created means I can go into the sleep for 1.024sec long. Then the stuff wakes up. My Q is for what is it good??? I can build a blinker with 1.024sec period with two transistors.. :) In reality, the only way how you can help people with the power decrease is to remake the Narcoleptic library for chipkit. The 1ms resolution will not be precise, no problem, we all know it comes from internal LPRC oscillator.. When somebody needs to decrease the power consumption, he simply replaces all the delays with narco ones and the power decreases significantly. Who needs 1.024sec delay actually?? I need 880ms one for example.. :)


majenko

Tue, 22 Jul 2014 21:38:22 +0000

I have not created a power saving mode with 1.024 seconds.

I have created an interface to the power saving mode - the DEFAULT to which is 1.024 seconds. That was not chosen by me.

A power saving mode this extreme is NOT designed for short period sleeps, like a few hundred milliseconds.

For that use idle mode and use a timer to wake yourself up. You won't save as much power, but then you're not going to be saving much power anyway when only sleeping for such a brief period.

If you are running on a battery, and you want to run for a long long time, and you are going to be spending seconds, minutes, even hours at a time when you're not doing anything, then sleep with this extreme power saving mode. Change your bootloader so it has a delay that is around the time you want.

The watchdog is only one way of waking up. You could wake up from an external interrupt - say a DS1306's alarm pin at a specific time.

What you cannot do in this power saving mode is wake up from any of the internal interrupts from peripherals that require the oscillator to be running, like timers, etc.

This sleep mode is not, I repeat NOT, for sleeping for a precise number of milliseconds. It is for sleeping for long periods.

Take for instance a temperature data logger. You want to take temperature measurements every 15 minutes at a very remote site and store them to an SD card, for example. Roughly every 15 minutes you want to wake up, enable the SD card and temperature sensor, take your readings, write them to SD, turn off the temperature sensor and SD card, then go to sleep. The whole operation may have taken 15 seconds. Say you used 100mA for those 15 seconds. When you then go to sleep for the next quarter of an hour or so. During that remaining 14 3/4 minutes you're then using just a couple of mA. So over the course of an hour you'd have 1 minute of 100mA and 59 minutes of say 5mA. That's a massive power saving, and your CR2032 would run it for a few days instead of a few minutes.


pito

Tue, 22 Jul 2014 22:01:30 +0000

I am very sorry, but I still do not understand why the ~msecs delays are not important when talking power saving mode..

I can have an application where I do something for 300usecs long with a period of 18msecs. The timing precision is not important so plus/minus 2msecs diff in the period are ok with me. When using sleep I can save a lot of energy, indeed.

So my point is the Narcoleptic delay would be a significant contribution to the power saving topic.


majenko

Tue, 22 Jul 2014 22:30:33 +0000

Set your watchdog to the 1:16 postscaler and you will get 16ms delay. You will get the full 16ms in sleep mode then. Setting it to 1ms delay means you will get 1ms of sleep, then an unknown time of wakeup (including its high power consumption), 16 times. That defeats the object of being asleep if you wake up all the time to see if it's time to wake up yet.

When you're asleep in bed you don't wake up every minute to look at your alarm clock, do you? No. You set your alarm clock for 8:30 and trust that it will wake you at the right time.


majenko

Wed, 23 Jul 2014 01:19:09 +0000

I have just created a github repo fir the small Flash library I have been working on - allows you to read and write the PIC32's flash (tested on a '795). In that I have an example sketch - ConfigBits. It allows you to tweak certain config bits without having to recompile and upload a new bootloader. [attachment=0]ConfigBits.png[/attachment] I have only tested it on a 795 (my Max32) and so far it has behaved, but I will not be held responsible for it breaking your bootloader!

Best to have a programmer handy for reflashing your bootloader just in case ;)

[url]https://github.com/majenkotech/Flash[/url]

And yes, it does let you change the watchdog timeout to any of the values I listed above.


pito

Wed, 23 Jul 2014 07:17:15 +0000

Setting it to 1ms delay means you will get 1ms of sleep, then an unknown time of wakeup (including its high power consumption), 16 times. That defeats the object of being asleep if you wake up all the time to see if it's time to wake up yet.

The Narcoleptic lib does not work that way you describes.

The N.lib "cleverly assembles the WDT timeouts" such it minimizes the number of wake ups needed (see the code below I did with pic32mx wdt settings):

16ms delay means it sets wdt for 16ms, so NO waking up in between. 
Others ie.:
1ms = 1  = no waking up
3ms = 2 + 1  = only 1 waking up
5ms = 4 + 1  = only 1 waking up
50ms = 32 + 16 + 2  =  only 2 wakings up
128ms = 128  =  no waking up in between needed,
200ms = 128 + 64 + 8  =  only 2 wakings up
1000ms = 512 + 256 + 128 + 64 + 32 + 8  =  only 5 wakings up in between needed,
1024ms = 1024  =  no waking up in between needed,
60000ms =  6 wakings up in between,
500000ms =  6 wakings up in between,
1000000ms =  6 wakings up in between,
etc.

The "waking up in between" means a few microseconds for the "if (..)" - that is always negligible against the Xmsecs WDT timeout in sleep.

// Pito 7/2014 for pic32mx WDT settings
void NarcolepticClass::delay(int milliseconds) {
  while (milliseconds >= 1045876) { sleep(WDTO_1045876MS); milliseconds -= 1045876; }
  if (milliseconds >= 524288)    { sleep(WDTO_524288MS); milliseconds -= 524288; }
  if (milliseconds >= 262144)    { sleep(WDTO_262144MS); milliseconds -= 262144; }
  if (milliseconds >= 131072)    { sleep(WDTO_131072MS); milliseconds -= 131072; }
  if (milliseconds >= 65536)     { sleep(WDTO_65536MS); milliseconds -= 65536; }
  if (milliseconds >= 32768)     { sleep(WDTO_32768MS); milliseconds -= 32768; }
  if (milliseconds >= 16384)     { sleep(WDTO_16384MS); milliseconds -= 16384; }
  if (milliseconds >= 8192)      { sleep(WDTO_8192MS); milliseconds -= 8192; }
  if (milliseconds >= 4096)      { sleep(WDTO_4096MS); milliseconds -= 4096; }
  if (milliseconds >= 2048)      { sleep(WDTO_2048MS); milliseconds -= 2048; }
  if (milliseconds >= 1024)      { sleep(WDTO_1024MS); milliseconds -= 1024; }
  if (milliseconds >= 512)      { sleep(WDTO_512MS); milliseconds -= 512; }
  if (milliseconds >= 256)      { sleep(WDTO_256MS); milliseconds -= 256; }
  if (milliseconds >= 128)      { sleep(WDTO_128MS); milliseconds -= 128; }
  if (milliseconds >= 64)      { sleep(WDTO_64MS); milliseconds -= 64; }
  if (milliseconds >= 32)      { sleep(WDTO_32MS); milliseconds -= 32; }
  if (milliseconds >= 16)      { sleep(WDTO_16MS); milliseconds -= 16; }
  if (milliseconds >= 8)      { sleep(WDTO_8MS); milliseconds -= 8; }
  if (milliseconds >= 4)      { sleep(WDTO_4MS); milliseconds -= 4; }
  if (milliseconds >= 2)      { sleep(WDTO_2MS); milliseconds -= 2; }
  if (milliseconds >= 1)      { sleep(WDTO_1MS); milliseconds -= 1; }  
}

Only what you need is to define a macro/function for setting up the wdt divider to above values ie. "WDTO_4096MS"... :)


majenko

Wed, 23 Jul 2014 09:40:34 +0000

Except, there is one small flaw in that.

That's not how the PIC32 watchdog works! You have one timeout value, and that value is configured in the config fuses.

It can not be changed on the fly.

THAT is why I was saying about waking up every 1ms to "check the clock", because your watchdog timeout is burned into flash.


pito

Wed, 23 Jul 2014 10:56:16 +0000

Ok, then get rid of that chip :)


majenko

Wed, 23 Jul 2014 11:12:41 +0000

It was a really silly design decision if you ask me. A bit short sighted of them. I don't know if it's any different in the MZ chips - I'd hope so.


EmbeddedMan

Wed, 23 Jul 2014 12:19:17 +0000

My understanding was that the watchdog prescaler was implemented in config bits so that an errant program couldn't accidentally change the watchdog timeout value, because this could have safety critical implications for certain products.

*Brian


majenko

Wed, 23 Jul 2014 12:25:15 +0000

That's what I thought, but then there is the SYSKEY thing to protect the ON/OFF setting of the watchdog in a normal register - why couldn't they have done the same thing with the postscaler? If it's safe enough to protect the watchdog from being turned off, it should be safe enough to prevent the postscaler being changed.


pito

Wed, 23 Jul 2014 16:35:12 +0000

Frankly, knowing the mx nuances, I would try to write the "wdt postscaler shadow register" SWDTPS<4:0> of WDTCON. Why they need to copy the postscaler setting from config bits to the Read only register??

Shadow Copy of Watchdog Timer Postscaler Value from Device Configuration bits On reset, these bits are set to the values of the WDTPS <4:0> Configuration bits.

And what after the reset?? Maybe it is writeable.. Maybe it works like the oscal in atmega - during the reset the hardcoded value is written to oscal register, then you can overwrite the oscal to your like.. (I am doing that in order to set clock to 7.37MHz).

PS: the same with MZ - when considering the 15Nov2013 update of PIC32 WDT ref manual relevant. They introduced wdt window and deadman something, but the SWDTPS remains the same.

BTW - the last update of pic32 family ref manual is dated 18Nov2013, this calendar year they updated pic30/24/33 families RMs only. None pic32 family update this year.. It seems their RMs for MZ are final (or, maybe, they have lost an interest in the MZ??) :) :)


majenko

Wed, 23 Jul 2014 17:42:46 +0000

I guess they copy the bits to the shadow register purely because reading them from flash all the time would be silly. The shadow register isn't real memory, but is an interface to the peripheral. You can't do that very easily with flash stored values, so they get copied.

They are flagged as read only in the data sheet, but some part of the system must be able to write into them to configure them in the first place.


Machine_2

Thu, 14 Jan 2016 22:59:35 +0000

[...]For that use idle mode and use a timer to wake yourself up[...]

Is this doable? I've tried it before but just couldn't pull it off. I kind of need to save some energy for a couple ms.

I have configured the timer 1 to give me about .2 seconds between interrupts

setup(){
	T1CON = 0x0;//Apagamos el timer
	T1CONSET = 0x30;//Prescaler 256
	TMR1 = 0x0;//Reiniciamos la cuenta
	PR1 = 0xFFFF;//Periodo Max	
	IPC1SET = 0x1C ;//Establecemos prioridad = 7
	IPC1SET = 0x0003;//Establecemos subprioridad = 3
	IFS0CLR = 0x00000010;//Limpiamos la bandera de interrupción
	IEC0SET = 0x10;//Habilitamos la interrupción
	T1CONSET = 0x8000; // Start Time
}

For the interrupt I had to use the 'not so cool' way since the answers given in this thread [url]http://chipkit.net/forum/viewtopic.php?t=2495[/url] are mainly focused on 32 bit timers

void __ISR(_TIMER_1_VECTOR, ipl7) Timer1Handler(void){
	Serial.println("-&gt;");
	IFS0CLR = 0x00000010;//Limpiamos la bandera de interrupción
}

The method used for sleep is pretty standard, however it doesn't work when used on the main loop.

void duerme(){
	//Descansos
	SYSKEY = 0x0;
	SYSKEY = 0xAA996655;
	SYSKEY = 0x556699AA;
	OSCCONCLR = 0x10;//Idle
	SYSKEY = 0x0;
	asm volatile("wait");
}

So, here's the problem. When using just "duerme()" in the main loop it doesn't go Idle at all, but if I call "duerme()" inside an interrupt called through "attachInterrupt" it goes Idle and never wakes up.

uint32_t inter2(uint32_t currentTime){
	detachCoreTimerService(inter2);
	
	duerme();
	
	attachCoreTimerService(inter1);
	return (currentTime + CORE_TICK_RATE*(times[1]));
}

I've even tried something like this, but it never goes Idle

void loop(){
	duerme();
}

Any help is really appreciated.