The story so far: I’d been tinkering with an MCP795W20 real-time clock (RTC) chip and having a frustrating time of it. Weird things were happening. It seemed very finicky.
I’d put part of the problem down to using a breadboard (still think I’m not entirely wrong about that). It turns out, however, that the biggest problem was my own stupidity. I’d used entirely the wrong values for the load capacitors on the crystal. Once I’d put the chip on a soldered prototype board and used sensible capacitors, it settled right down.
In the meantime, though, I decided to try another route.
This is the DS3234. It’s a slightly larger chip, having 20 pins, although no fewer than nine of them are marked ‘NC’ – ie, ‘no connect’. This later transpired to be not entirely true.
Although offering slightly fewer features than the MCP795W20, the DS3234 is a tad more expensive. However, it comes with its own, built-in oscillator, so no need for a crystal and supporting capacitors. It also has a thermal sensor that is supposed to allow the chip to adjust automatically for temperature changes.
My first, breadboard-based efforts with the DS3234 were not entirely encouraging. I could set it and read it. But when left alone, the time seemed to drift more than I thought acceptable (like a couple of minutes a day).
Then I read the datasheet. Or, to be fair to myself, I read it a little more closely. You know those pins that were marked as ‘no connect’ on the pinout diagram? Well, it turns out you’re supposed to connect them to ground. Once I’d done this, it seemed to have a stabilising effect. The timekeeping has been rock-solid since.
Reading and writing
Setting and reading the RTC are simple operations involving writing to and reading from registers. But with one slight twist.
Each time- or date-related register stores its data in Binary Coded Decimal (BCD) format. The lower four bits store a binary value for the units value and the upper four bits the value for the tens.
To make this clearer, let’s say you want to store the value ’24’ in the date register. Expressed as four bits of data, ‘2’ is 0010 and ‘4’ is 0100. The first four bits go into the higher nibble of the byte and the second group into the lower nibble, so you effectively shunt these two together to get: 0010 0100. If you read that as a normal byte, you’d get 36 in decimal.
There’s nothing very complicated in handling this in whatever language you use. For example, to read that value, you could adopt the following approach (using the value 24 as an example):
- Take the byte (b0010 0100) and AND it with b0000 1111. This gives you the value of the lower nibble (b0000 0100, or 4, in this case). Store this for a moment.
- Take the original value (b0010 0100) again and do a binary shift right four times. This moves the top nibble into the lower four bits (giving you b0000 0010, or 2). Now multiply this by 10 (to get 20).
- Add the value you got in the first step to the value you got in the second step.
Going the other way is similar. Let’s use the example of 24 again.
- Take the number and divide by 10. Use integer division but store both the result (2) and the remainder (4).
- Take the result (2) and do a binary shift left four times. That gives you the upper nibble.
- AND the upper nibble with the remainder to get your BCD byte.
Not so fast
There is another complication, though. While all the date- and time-related registers use all four lower bits for the units, in many cases only one to three bits of the upper nibble are used for the tens.
In some cases, this doesn’t present a problem. In the case of the seconds and minutes time registers, for example, bits 4-6 are used for the tens, but bit 7 is just always 0, so you can simply not worry about it.
With the hour register, though, bit 6 is used to determine whether you’re using a 12-hour or 24-hour clock. The month register uses bit 7 for the ‘century’ setting. Similarly, the alarm registers use bit 7 for various settings.
What this means is that, when reading these registers, you’ll need to mask out these special bits to avoid accidentally changing your settings. Reading the hour, for example, (and assuming 24-hour mode) you would read the register and then AND it with b0011 1111 to mask out the top two bits before using the value.
Writing is slightly more complicated. With registers that use these upper bits for settings, you’ll almost certainly want to preserve those bits. That means reading the register first before combining it with the value you want to write and only then doing the actual write. Let’s take the hour register again, where we want to leave bits 6 and 7 unmolested. (Strictly speaking it’s just bit 6 because bit 7 is always 0, but let’s bundle them together for simplicity.)
- Read the register.
- AND it with b1100 0000 to get just the values for the top two bits.
- OR the result with the value you want to write.
- Save the result to the register.
On the boards
Once I was happy that the breadboard- and stripboard-based prototypes were working reliably, I designed a couple of PCBs – one each for the MCP795W20 and DS3234. I fired these would come in handy for future Raspberry Pi, Arduino and other microcontroller-based projects.
This experimentation also gave me the confidence to go ahead with the design of a new board for the Zolatron 64 – an SPI interface board that provides five SPI headers plus on-board RTC, battery-backed serial RAM and an SD card drive. But that deserves a post of its own.