Computing stuff tied to the physical world

Generalized LiquidCrystal library

In AVR, Software on Sep 26, 2009 at 00:01

Let’s dive into some C++ code for a change…

The Arduino version 0017 IDE has a nice library for driving standard character LCD’s, called… “LiquidCrystal”. The nice thing is that it mixes in the print() / println() functionality which is also used in the Serial library.

That makes it very easy to change a sketch to use either the serial port or the LCD screen to display results. Here’s how – let’s say you used this code in your sketch to print a value to the serial port:

Picture 2.png

The first thing to do is to generalize this slightly:

Picture 3.png

Functionally, nothing has changed. That’s the whole point, because you can develop and extend the sketch while testing everything via the serial port, as usual. Just use “Output” everywhere.

To change the output to the LCD, replace that “#define” line by the following lines of code:

Picture 4.png

This adds a bit of logic to easily switch between serial and LCD, without having to change a thing in the rest of the sketch. The “(2, 3, 4, 5, 6, 7)” parameters have to be set to the pin numbers actually used for your specific setup, of course.

So far, so good, although none of this applies to the I2C-connected LCD I’m working on. But by extending and re-organizing it a bit, it is possible to re-use most of the code of the original LiquidCrystal library.

The first thing I had to do was to rip apart the general and the pin-specific logic. That’s where C++’s abstract base classes and virtual member functions come in. I created a new “LiquidCrystalBase” class which has all the original code except for the pin-specific parts:

FlySketchExport.png

The “virtual … =0″ is C++’s funny way of saying it doesn’t need definitions for these three functions yet. The result is an incomplete class – you can’t create objects of type LiquidCrystalBase, you can only derive other subclasses from it. Tricky stuff, but bear with me…

Note that all the pin-specific member variables have been removed as well.

The next step is to re-define the original LiquidCrystal class in terms of LiquidCrystalBase:

Picture 5.png

If you compare this to the original code, you’ll see that most functions member definitions are missing. That’s because they have already been defined in what is now LiquidCrystalBase, and the new LiquidCrystal inherits everything from it.

What LiquidCrystal does add, is a definition and implementation for each of the virtual config(), send(), and write4bits() members. It is sort of “filling in” the missing functionality. So the result is… a LiquidCrystal class which does exactly the same as the original.

That seems pretty useless, but it isn’t – what we now have is a generic part and a specialized part. Which means it is now very easy to implement a variant which works exactly like the original, but going through the I2C port interface instead:

Picture 6.png

Nice. Only a little bit of new code was needed to create a completely different hardware interface which is fully compatible with the original LiquidCrystal class.

Here’s the original example, using this new LCD-via-I2C library:

Picture 8.png

Note that the I2C port is available as “myI2C” and can be shared with other devices by defining them here as well. You could even daisy-chain multiple LCD displays on the same port if you wanted to.

That’s all there is to it. The sketch code itself remains the same. Welcome to the world of abstraction, à la C++ !

Update – the final version and source code are described in this followup post.

  1. Hello JC, Until now I only used standard C because I thought there’s a big size overhead on the compiled code if using C++. Am I wrong? Can it be ignored if thinking about the gain in using an object oriented programming?

    • No… I wouldn’t ignore it ;)

      But you’ll only get the overhead you ask for. In this case, I wanted the virtual member overloading, so I looked for a minimal set of members to make virtual. I don’t think classes by themselves have any overhead at all – just try to keep v-tables, initializers, automatic casts, temporary objects, and such to a minimum would be my suggestion.

      This I2C version takes 3326 bytes, vs. 2474 for the original code. But there’s quite a bit of extra logic in there to handle the I2C protocol (the PortI2C and DeviceI2C classes), so most of those 852 extra bytes are probably unavoidable anyway.

      C++ templates might even allow getting rid of the virtual call overhead. Haven’t tried it – I find templates (and their error messages when you get it wrong) really tedious, it kills all the fun :)

Comments are closed.