Tuesday, February 4, 2014

attiny screen driver for parallel controlled character displays

This is the "details" post for controlling a 7 bit parallel OLED character display using an attiny as a display driver. See the previous post for an overview of the setup and video.

And now... the code. Apologies for some of the names of directories. Instead of branching and whatnot in git I just copied the code to library(n+1) and added the next feature in line as I went. I might at some stage do a write up detailing the stepping stones to get from the bare attiny84 to the code below.

To make it simpler, I'll call the "main" arduino the Uno. That is the one what wants to run the show. The screen is attached to the attiny84 which I'll just call the tiny84.

This is the code that one uses on the Uno: oled_clientspireal.ino It is designed to look very close to the example it was based on (linked in the second line of its header comment).

The attiny84 runs this attiny_oled_server_spi2.ino The heavy lifting is done in SimpleMessagePassing5 which I link next. The main part of loop() is to process each full message that we get from SimpleMessagePassing5. There is a timeout() logic in there after that which allows the tiny84 to somewhat turn off the screen after a period of no activity on the SPI bus. noDisplay() doesn't turn off the OLED internal power, so you are only down to about 6mA there.
The SimpleMessagePassing5 does two main things (coupling SPI into message byte boundaries in a single file, the academics would be unhappy) SimpleMessagePassing5.cpp Earlier version of SMP did
USICR |= (1 << USIOIE); 
in init(). That line turns on the ISR for USI SPI overflow. But that is itself now done in a pin change ISR so that the USI is only active when the attiny84 has been chip selected.

serviceInput() is a fairly basic state machine, mopping up bytes until we have a whole "message" from the SPI master. If there is a complete message available then takeMessage() will return true. It is then up to the caller to do smp.buffer().get() to actually grab and disregard the bytes that comprise a message.

I'm guessing that at some stage I'd add a "Message" class that takeMessage could then return. The trick will be to do that without needing to copy from smp.buffer() so that sram is kept fairly low.
The Uno uses the shim class Shim_CharacterOLEDSPI2.cpp which just passes the commands along to the attiny84 for execution.
This all seems to smell a bit like it wants to use something like Google protocol buffers for marshaling but the hand crafted code works :) In some ways using GPB for such a simple interface as CharacterOLEDSPI2 might be well be considered over engineering, but I'm still tempted.