Using the alarms on the MCP7940NPublished: April 08, 2017
Tags: attiny electronics hacking hardware rtc mcp7940
Lately I've been tinkering with an ATTiny-based microcontroller project, exploring the possibilities for ultra low power operation after being inspired by this wonderful series of blog posts by Heliosoph. Part of the setup is using a real time clock chip to periodically wake the microcontroller from a very deep sleep state. The darling RTCs of the hacker/maker world are unquestionably the DS13XX chips from Maxim, most commonly the DS1307 - these are the chips you'll find for sale at places like Sparkfun, Adafruit, Tayda, etc. However, I prefer to use the MCP7940N from Microchip.
The MCP7940 is considerably cheaper than any of the DS13XX chips, at least from my suppliers, which is my main reason for using it. However, it also has excellent power specs (works down to 1.8V and draws 1.2 microamps at 3.3V, compared to the DS1307 which requires 4.5V and draws 200 microamps - admittedly the DS1337 is much more closely matched) and a lot of nice additional features, like a digital trimmer which lets you correct for the tolerance of the quartz crystal, a multifunction output pin which can generate alarm interrupts or squarewaves at various frequencies, and logging timestamps to battery-backed SRAM recording when the chip switches between its main power supply and the battery backup. Paradoxically, I think it is simultaneously the cheapest and the most featureful DIP-packaged RTC chip on the market. Of course, it has some drawbacks, too, which is presumably the reason it is not widely used. The DS13XX chips have internal capacitors for their oscillator circuit, whereas these need to be external components on the MCP7940. Microchip also recommend (in a guide for those migrating from the DS1307 to the MCP7940) connecting the backup battery to the RTC via a diode, resistor and capacitor network, whereas the DS13XX chis permit a direct connection. The need for these external components offsets the cheaper chip price to some extent, but I still think the MCP7940 has the lower total BOM cost.
However, the biggest drawback is that the programming interface for the MCP7940's alarms is frankly a bit of a pig to work with, which is the motivation for this post. One user on Microchip's forum declared the alarms to be "unusable". The specific complaint was that it's very hard to set an alarm to fire each day at a specific time, say 0730, the way you'd expect a traditional alarm clock for sleeping humans to work (which, I acknowledge, is not really the kind of use case an RTC chip is designed for). To set an alarm on the MCP7940, you specify a value for seconds, minutes, hours, day of the week, date and month, and then select one of the following six matching conditions:
- seconds match
- minutes match
- hours match
- day of week match
- date match
- seconds, minutes, hour, day of week, date and month match
Conspicuously absent is a "minutes and hours match" mode (which the DS1337 does provide, or rather it provides a "seconds, minutes and hours match" mode). This means it's easy to trigger alarms at 0630, 0730, 0830, etc. (by having the minutes match 30) and easy to trigger alarms at 0700, 0701, 0702, etc. (by having the hours match 07), but apparently impossible to trigger an alarm at 0730. The only option is to use the last mode, where everything has to match. This lets you trigger your alarm at 0730 on Sunday the 9th of April 2017, but the drawback is that to reenable that alarm for 0730 on Monday you'll have to increment the day of week and date settings for the alarm - and then check to see if you've rolled over to a new month (requiring you to write "30 days has September..." code), and if you have to check if you've rolled over to a new year. Don't forget about leap years! This is rather too much mucking about for any sane person's taste to achieve as simple a task as triggering an alarm once per day at the same time each day.
It turns out that with a more careful reading of the datasheet, there is an easier way than this, but it's a little odd. The MCP7940 has an
ALMPOL bit in the
ALM0WKDAY register (at address
0x0D) which specifies the polarity of the alarm output pin (
MFP). When only one of the two alarms is used, this functions sensibly. If
ALMPOL is set, then the alarm output is active high (i.e. the output is low while waiting for the alarm to trigger, and goes high once triggered), and if
ALMPOL is cleared, then the alarm output is active low. However, if both alarms are used at once, things get interesting. If
ALMPOL is set, then the output on the MFP pin is low by default and goes high when either of the alarms fires. You get two independent alarms with this configuration. However, If
ALMPOL is cleared, then the output on MFP is high by default and goes low only when both of the alarms fire. So if you set alarm 0 to fire whenever the hour matches 07, and alarm 1 to fire whenever the minutes match 30, then you essentially end up with a single active low alarm which fires at 0730. It's one alarm for the price of two, but it's better than worrying about all that date incrementing nonsense.
An alternative solution raised in the forum thread is to implement some of the alarm matching in software. You can set an alarm to trigger whenever the minutes match 30, say, and in the interrupt service routine which is triggered by this alarm you can read the current hour out of the RTC and check for yourself whether its 0730 or half past some other hour. If its 0730, do what you have to do, and if its not put the microcontroller back to sleep. Unlike the "native" solution using both alarms described above, this approach lets you use both alarms independently, and also lets you retain the choice between an active high and active low alarm. You can also generalise this quite far: set an alarm to fire whenever the seconds match zero and your ISR will be called once per minute, then you can check any other condition in software. In this fashion you can have as many alarms as you like, and they can be as unusual as you like, like trigger at 0730 on the second Tuesday of the month but only if the date is divisible by three. The sky is the limit!
That's not the end of the story, though, as both this solution and the native solution suffer from a common additional problem, which in my opinion is the real pitfall of the MCP7940's alarm system. This is that the alarms trigger repeatedly as long as the matching condition holds true. If you set an alarm to trigger when the seconds match 00 and in response your microcontroller wakes up, does some work for 10 milliseconds and then goes back to sleep, it will be immediately awoken again because the seconds still match 00. This will in fact repeat one hundred times until the seconds value finally ticks over from 00 to 01 and only then will the microcontroller finally stay asleep. The DS1337 does not suffer from this problem at all, since it tests for a match only once per second, when the time and date registers are updated. The problem is even worse if you are matching on a particular value of the minutes. Instead of your ISR firing constantly for a whole second, it will fire constantly for an entire minute! Of course, your ISR can simply disable the alarm the first time it is called, in which case it will only fire the once, but then your alarm won't fire at all when your condition matches again one minute or one hour later.
So, what do we do if we want to use the MCP7940 to, say, trigger an alarm exactly once each hour when the minutes match 00? The solution is to use both of the alarms in a "leapfrog" configuration:
- Configure alarm 0 to fire when the minutes match 00 and enable the alarm.
- Configure alarm 1 to fire when the minutes match 01 and disable the alarm.
- Put the microcontroller to sleep, where it will remain until alarm 0 fires at the start of a new hour.
- When the microcontroller awakes due to alarm 0, perform your hourly task, then do the following in this order:
- Disable alarm 0
- Clear the
- Enable alarm 1
- Go back to sleep
- The microcontroller will then stay asleep for the remainder of the first minute of the hour.
- When the microcontroller awakes due to alarm 1, do the following in this order:
- Disable alarm 1
- Clear the
- Enable alarm 0
- Go back to sleep
- The microcontroller will then stay asleep until the start of the next hour, whereupon it will be awoken due to alarm 0 and we rinse and repeat.
Triggering an alarm exactly once each minute when the seconds match 00 can, of course, be done in the same way. Its very important that you disable an alarm before clearing its interrupt flag. If you clear the interrupt flag with the alarm still enabled, the flag will immediately be set again and your ISR will unexpectedly fire a second time. This approach is, once again, one alarm for the price of two, but because it is well behaved you can easily implement a software alarm on top of it and have an arbitrary number of alarms which occur every day at particular times specified by an hour and minute. Note that if your repeating task takes longer than two seconds (if it happens every minute) or two minutes (if it happens every hour), then you may miss alarm 1, and then alarm 0 will never be reenabled and your controller will fall into an endless slumber! In this case adjust the match value for alarm 1 accordingly.