Sunday, 11 May 2014

Nokia 3310 display fun

Recently I wrote a driver for a nokia 3310 (pcd8544 based) lcd display which I had lying around.Again, I decided to create my own driver just to learn something about it and to see how much effort it will require - turned out that this driver is pretty easy to handle. Despite the fact that it's suggested to use 3V3 logic (maximum accepted voltage accordingly to the datasheet it's 7V), it works great with arduino powered with 5V and using 5V logic outputs.

Adafruit's Library

I had a look on Adafruit's library for that display just as a reference (github) and it seems very primitive to be honest - it allocates a huge display buffer without asking the user whether it is really needed (it consumes more than 25% of available RAM on Arduino) and what's even worse it uses bit banged SPI to communicate with the display. Why ? Why bother when there is fast & reliable hardware bus available on that MCU (and of course like all Arduino IDE libraries it's written in C++ which makes things even more slower). And it doesn't come with any font, so in fact the user must take care about it himself. Not being very humble - the implementation in libpca is far more superior.

Hello World

Just to test the driver & the display - let's first play around with a code template and generate some easy graphics (and of course mandatory for every project - "Hello World" program)

Simple, isn't it ? This code in a couple of lines initializes the SPI bus in the simplest manner, initializes the display itself, installs it as an STDOUT output and prints the "Hello World" string. That's fancy, plain and simple, no unnecesary C++ mumbo jumbo (don't get me wrong I love C++, but the way it is used in the Arduino libraries - it's a joke really). This is how it looks on the display:

I'll use that code as a template and will enhance it further on to generate some other effects. I'll be simply replacing the "Hello World" printf line with some other code.

Let's start with something simple - checker board. Each byte in the display represents 8 rows, 48 rows are distributed amongst 6 bytes. The memory organization of the display looks the following way:

Which means that if we'll fill the display with a pattern of 0b10101010 (0xaa) - we'll get something like a very high grained stripes. Let's test that. Replacing the 37th line (printf("Hello world !!!");) line with the following code:

pcd8544_fill(&lcd, 0xaa);

You'll get:

Ok, let's do something fancier. There's an old trick which I love to use (back from the dos 7.0 world and the famous software interrupt 13 which brought you into the 320x240 graphics mode) - generating the Sierpinski's triangle with just logical AND operation. Let's replace the line 37th again with the following code:

The result:

Yeah, that's nice, but we can still do better without much effort. Each pixel on the display can be either turned on or off - it's a 1-bit color depth. In other words it's physically impossible to display any shade of grey. But this is not a problem really - that limitation has been overcome long time ago with dithering alghorithms - we could implement simple error diffusion dithering or floyd-steinberg dithering to get awesome effects - but that would require some more effort than I really want to spend at the moment. Let's focus on an ordered dithering using an 8x8 Bayer lookup table - thanks to that method we'll be able to effectively simulate a 6-bit color depth (0 - 63) on a 1-bit color depth capable hardware and guess what - yes libpca implements that method for your convenience as well all you have to do is simply use it.

I recommend having a look on those articles if you are really interested in dithering - it's a lot of fun.

First, let's generate a gradient. This will be the code:

And the effect:

Ok. That's pretty nice. We can tell how this method works - it generates different patterns (with different intensity) in order to simulate a particular shade - it's not as good as floyd-steinberg but good enough and what's most important - fast enough.

Let's see if good old atmega is fast enough to generate another old-school graphics effect - famous plasma effect This will be a challenging task for small Atmega - this effect heavilly depends on the sinus function and sqrt, power of two functions - everything of which our CPU is not very good at. Let's try anyway. 

The general alghorithm for the basic plasma effect is:

for every x
for every y
color = sin( sqrt(x*x + y*y) );
putpixel (x,y,color);

Surprisingly previously I was using a "buffer" variable as a display buffer, but for the plasma effect it's not really needed. First thing which I've done was generating the sin function lookup table:

    for (uint16_t i = 0; i < 256; i++) {
        sin_table[i] = (cos(i/2)*sin(i))*31 + 32;

In order to avoid two loops, one which generates the image in the buffer and the other one which copies the data to the display I'm trying to do everything in one loop (just to make it quicker). The complete code looks the following way:

What I'm trying to achieve is to generate a byte of image (vertical) and copy it to the display, so in fact I'm using a single byte as a processing buffer. I'm using the ordered dithering function the same way as previously. This code on my Arduino is able to generate the plasma effect with something around 2, 3 frames per second. Have a look:

The code is available on github. You need libpca:

git clone pca

and the my avr projects repository:

git clone projects

In the projects directory you'll find a directory called nokia_spi.

A snapshot is available also here.

1 comment: