22 April 2008

13. It's about time

Following on from our discussions on getting the temperature via i2c, we’ll look at talking to a real time clock chip such as the ds1307. It’s a little more complicated than just getting the temperature – but mostly because there’s just more data available. The way we interact with the chip is very similar.

Now, you might ask why we need a separate chip to look after the time. Why can’t we do it all on our pic with our nifty timer routines? Well, we could. In fact, you can even drive one of the timers from an external crystal designed for handling real time clock type problems. There are, however, some good reasons to keep it all separate.

The first is that the DS1307 has support for battery backup – from a 3v CR2032 or similar, which will run it for 10 years or more. That’s effectively lifetime of the device. So when the device as a whole is powered off, the time will still be current. The other neat aspect is that the DS1307 has a bunch of non volatile RAM slots left over (56 byte to be exact) which we can use for our own nefarious purposes. You could use a pic that has no EEPROM (these are generally a lot cheaper) and store your info in the DS1307. Neat!

In any case, it’s a good example of another external device we can talk to over the i2c buss. You could connect a DS1307 and an LN75 on the same buss and talk to them both using only two wires.

Open up the ds1307 demo project. You’ll notice that in our configure_system routine we have:


This in turn calls i2c_setup() to configure port and pins as inputs or outputs as appropriate, ready for communication.

In request_time() we do some simple requests:

minutes = rtc_get_minutes();
hours = rtc_get_hours();
seconds = rtc_get_seconds();

And print out the result. All pretty easy so far.

It gets a little more tricky in process_rx() which allows us to enter commands like:


over a serial connection, to set the minutes to 23. The same goes for hours and seconds, using


If you have a look at ds1307.c, you’ll notice that most of the routines are simply wrappers around the i2c routines. Some add a little bit of value, by masking out bits, but even then, they’re mostly one liners. This brings us to the challenge of programming such limited devices – you’ve really got a choice between stack size and code size. If you use a sub routine only once – then it’s probably worth turning it into a #define or using the inline keyword. That means the code it uses gets placed in the actual location it’s used, and you save a stack position. In 16f88 devices, there are only 8 places in the stack. If you use interrupts then you’ll use 1 when an interrupt is called, even before you call any subroutines. So you need to make sure the stack never exceeds the interrupt level + main program level. If you do exceed this, on the 16f88 it will happily wrap the stack around and you’ll have lost the first stack position. In reality, this normally results in generally strange behaviour. If your program is using interrupts, and sometimes crashes, prints out strange repetitive things to the serial port or seems to jump back to the boostbloader, then you’ve probably got a stack overflow.

You can check to see if this is possible by looking at the BoostC output. It will print out a quiet little warning that you’ve exceeded the maximum stack level if everything happens at once (remember, BoostC knows about the whole program structure at the start, but it can’t know if the interrupt happens at the deepest main program stack position). I keep getting caught in this – strange behaviour, start putting serial_print_str in to find out exactly where it crashes (strange, those routines have worked for ages) - only to discover that there’s a potential stack overflow lurking around.

Sourceboost provides some very handy tools for find out what to do here. If you press the Code button on the tool bar and open out the pane that the code shows in, you can see the entire call tree of the program. This feature is worth the price of Sourceboost alone – you can easily see which tree branches are the longest and start to do something about them.

The Function Info pane shows more information about each subroutine so you can see which ones are chomping up flash memory and RAM. My only wish here is in a future version that the #Global line will be expandable. I’d love to see where the global variables that are taking up RAM are coming from. The assembly tab then gives you a fantastic rundown of what assembly BoostC has come up with for your routines – and gives you the chance to optimise this. Often you have to be quite crafty to reduce your flash and RAM footprint. Sometimes its just tonnes of serial_print_str statements.

So, if you find that the ds1631 demo is too big for your chosen pic, the easiest way to trim it down is to take out or simplify some of the serial_print_str statements. The main program “help” printout would be a good place to start.

No comments: