The reasons that you want to use less power for a microcontroller project is easy. You want your project to run longer on a set of batteries. You want your solar powered project to work with the smaller, cheaper solar panel. You want your coin cell powered camera trigger to work for years, instead of months.
Last weekend I sat down to actually program the power saving feature that I had imaged a couple of years ago. My first few dozen tries failed, and on Sunday of last weekend all I had working was to put the processor in idle mode. Which I took as a win, because at least the sleep functions of my program were working.
I read a lot of various web pages about how to use the watch dog timer on Arduino, but the one that actually let me figure it out was the post that starts with "OK, I think I have it" on this web page. I used that example program as a simple example that worked to figure out what I was doing wrong. I found that example this weekend, after poking at things all week long and going to sleep thinking about the problem.
The first draft of my code that actually worked was just uploaded to github. There were just 2 tricky changes I had to make to my library code.
The first one was the fact that the watchdog timer can't be set to trigger at any arbitrary length of time. It only triggers at discrete units of time, sort of like a quantum energy emission. So I put a loop in that found the largest block of time that could fit in the scheduled time, and went to sleep for that amount of time, then it would wake up when the watch dog timer fired and find the next largest remaining block of time that could fit in the delay we needed. If there is a little bit of time left that is less than the 15ms minimum time that is left, I just delay.
The second thing was the fact that my code requires the use of the millis() function call, but that the timer in the micro-processor that keeps track of time is suspended when the chip is in power down mode. So to fix this I just drag all the scheduled tasks ahead by the amount of time that I just slept. A side effect of this is that because this timer almost never runs, I think I fixed the 50 day timer wrap problem. :D
One of the most difficult things to figure out was that for some reason the function to set the watch dog timer delay was not working, you had to set the register directly. From the example code that helped me figure this out.
void myWatchdogEnable() {
// turn on watchdog timer;
// interrupt mode every 2.0s cli();
MCUSR = 0;
WDTCSR |= B00011000;
WDTCSR = B01000111;
sei();}The line "WDTCSR = B01000111;" is doing two things. The B01000000 is closing changes to this register and the B00000111 is setting the time to 2 seconds. The flag WDTO_2S is 7 which in binary is B00000111.
For my own code I had to set the same register with this line:
WDTCSR = B01000000 | period;
The period was passed into the sleep function and was chosen from the list of flags in the wdt library header file:
0 = WDTO_15MS
1 = WDTO_30MS
2 = WDTO_60MS
3 = WDTO_120MS
4 = WDTO_250MS
5 = WDTO_500MS
6 = WDTO_1S
7 = WDTO_2S
8 = WDTO_4S
9 = WDTO_8S
The results.
My test rig, the clock is wired up in series with multi-meter measuring milli amps. |
After. |
Before. |
Improvements.
- Figure out why I could not use the 4 or 8 second delays with the old chip I am using. Waking up every 2 seconds uses 4 times the power of just waking up once every 8 seconds. Are these timings only usable on specific or newer chips? Does this need to be an option that someone can set if the bigger timings don't work for their platform?
- Get rid of the pointers for next/prev and instead just use a small array to track the tasks. Limit the number of tasks to less than 256 and we could just use byte pointers to the array for next/prev.
- The index i in the function that selects the period is always equal to the period value that is chosen. Just get rid of this column in the array to save memory and pass in the index as the period.
- Keep track of how much time we were in power down and just drag the time forward for the tasks once at the end. Add up each block of time in a variable and make the call with this variable once.
- Add different power down mode choices in the library to support various power saving features. One thing I would need to figure out is which modes need the time adjust for the tasks and which one the timer that millis() needs is still working. Would these various modes choices be compiled in? Or should it be done in runtime. The runtime option would give the arduino a dynamic processor control but at the cost of a larger runtime for the library.
- For the modes where the millis() timer still runs, fix the 50 day timer wrap problem. Basically everything needs pulled forward back to zero.
No comments:
Post a Comment