The Blackfin is a DSP chip from Analog Devices, today I shall go through one of its dynamic power management features, namely the deep sleep mode. The Blackfin provides four reduced power consumption modes of operation: active, sleep, deep sleep and hibernate. Deep sleep provides the second greatest power saving of the range. Hibernate provides the greatest power savings but also requires far more work to accomodate, as the processor's internal state is not preserved. I shall endeavour to cover hibernate another day, but if you are interested now there is a fairly good hibernation application note on the Analog Devices site.
The deep sleep mode turns off both the internal clocks of the processor (the core clock and the peripheral clock) and powers down the PLL. As such there are only two ways to wake the processor from deep sleep: a hardware reset or an RTC interrupt. The RTC has its own clock and thus can still function whilst the other clocks are stopped. If you are waking using a hardware reset the hibernation mode would also certainly make more sense than deep sleep so I shall only cover the RTC method in this post.
If you have one of the evaluation boards for the Blackfin (the code here was run on a BF533 EZ-KIT Lite) and a copy of Visual DSP, I would recommend modifing the LED Blink (C) example project that is distributed with your copy of Visual DSP. It provides nice feedback, you can see the processor sleeping when the LEDs have temporarily ceased strobing.
Ok so before we can do any sleeping we are going to have to configure the RTC. The gist (thanks Github) above shows a general purpose RTC configuration. Firstly we turn the prescalar on by writing the appropriate bit in the RTC_PREN register. This tells the RTC to run at 1Hz, as initially it is running at the raw crystal frequency of 32.768Hz. Notice that we cleared the Write Complete bit in the RTC_ISTAT register and then waited until it is set again. With the exception of RTC_ISTAT, writes to all the RTC control registers are synchronised to the ticks of the RTC clock and as such do not complete very quickly. The RTC_ISTAT register contains some bits that allow us to keep tabs on these writes. The Write Pending bit is set whilst writes are in progress, once said writes are all complete the Write Complete bit is set. The Write Pending bit clears automatically when a write completes and its falling edge is what sets the Write Complete bit, however the Write Complete bit requires manual clearing. This like all bits in the RTC_ISTAT with the exception of Write Pending is cleared by writing a 1 to it. If you are not diligent in clearing or checking the Write Complete bit, a few nasty things can happen but the most relevant to us being entering sleep mode with a write still pending. This generally results in getting stuck in sleep mode, but lets just say that "scary strange behaviour" will result.
The clock is now running at the right speed, next we need to set it up. Lets reset RTC_STAT, I shall not explain the encoding as it is covered perfectly well in the manual, but this holds the current time. Turn off the stopwatch (RTC_SWCNT), and deactivate all the interrupts (RTC_ICTL). The stopwatch counter is not cleared during a hardware reset and thus may contain any value, it consumes a small amount of additional power so it is good to zero it. Obviously, lets wait for these writes to complete, notice that we can happily write multiple registers in one go.
With the RTC running nice and normally lets set it up for use to wake the processor from deep sleep. A few things are going on here firstly, we configure the RTC with function we just wrote and then wait for 5 seconds. Wait... what? Why? Well your JTAG connection to the processor is lost when the processor enters deep sleep. This makes it rather hard to debug sleep applications and generally the program must be loaded onto whatever the system would normally run from, flash in the case of our BF533 EZ-KIT Lite. Now whilst playing with the sleep modes you will invariably managed to create a few programs that enter sleep mode and never return, now the processor can boot from flash rather quickly and if you can't connect with the JTAG to reprogram you have a problem. There are a few ways out of this hole once in it but far more convient just to put a small delay during the boot of the processor so you can reset and connect with the JTAG, at least until you have your code working smoothly. If you do get stuck, you probably have two options a direct connection to the flash and reprogramming or erasing, if this is supported, or as a backup prevent the processor from booting from flash (i.e. disconnect it, force a chip select low, etc.).
Anyway getting back on track, next we configure the RTC to trigger an appropriate event. In our case the event is a stopwatch interrupt in 5 seconds time. The 5 seconds goes into RTC_SWCNT and we set the enable for the stopwatch interrupt in RTC_ICTL. Of course we then need to wait for this write to complete, which will happen at the next 1Hz tick, this is worth remembering when using the stopwatch as you can end up waiting slightly longer than you expect to. If you program 5 seconds in you have 5 seconds plus upto 1 second of register writing time until the event actually triggers.
Now the astute amoung you will have notice that we cleared significantly more than just Write Complete when we wrote RTC_ISTAT; if we enter deep sleep with our choosen wake up event set in RTC_ISTAT already set, which it is in our case from the write of zero we did during initialisation, we once again stray into "scary strangle behaviour" land. Avoiding this problem is pretty easy with the stopwatch or alarm, simply clear the flag and enter sleep before the timer you set expires.
If you are adding this code into the LED Blink (C) project, the previous gist should be placed just before the infinite while loop in the main function.
Entering deep sleep is a simple matter of writing a 1 to the PWDN bit in the PLL_CTL register, then performing a PLL programming sequence. We then just wait in our deep sleep until the RTC fires its wake up event and restarts the process. A PLL programming sequence is a fancy way of staying issuing an idle instruction with the interrupts disabled. We can disable and enable the interrupts with the builtin functions cli and sti, idle is provided with the builtin function idle.
The only slight complication is that once we awake from deep sleep we awake in active mode rather than normal operation. In active mode the PLL is bypassed, in short the clock speed is a lot slower. To return to normal operation we need to clear the bypass bit in the PLL_CTL register and then perform another PLL programming sequence.
Congratulations, we have managed to sleep and wake up again.
Using the alarm, which matches a specific time rather than just a countdown like the stopwatch, is very simple. We use the RTC_ALARM register instead of RTC_SWCNT and enable the alarm interrupt event. The RTC_ALARM register represents a time in the same way as RTC_STAT, with bit fields for the days, hours, minutes and seconds. If in the unlikely event you want to wake up on a specific day as well as time you must use the day event rather than the alarm event. The alarm event only matches the hours, minutes and seconds between the two registers.
The RTC also has a 1Hz tick which can be used to wake up. The primary thing you will notice about the gist for this one is that we clear RTC_ISTAT again after the write has completed. This is because the write completes on the 1Hz tick which also sets the event in the register and as before if we enter deep sleep with the event already trigger chances are we are not waking up again. Indeed care must in general to ensure that there is no possibility of the tick occuring between the point you clear the RTC_ISTAT register and when you enter sleep.
So I think we can probably condense this post into a few golden rules of deep sleep:
Well thats probably enough for one post, it kinda ballooned from small chat about deep sleep into a full blow tutorial. I hope it helped someone out there.