Have you considered ScreenKeys?

Program a small 8pin micro (pic or AVR) to control the screenkey.

interface the micro with i2c, then you can add lots of screenkeys onto the i2c bus easily !

I dont know why they didnt make it i2c in the first place. (6 spare pins on the micro gives you plenty for two i2c lines, clk/dat to the screenkey and even add the actual button push lines into the pic to be read from i2c).

Problem solved.

Hi, I just want to let everyone know that I am am engineer from ScreenKeys and would be happy to answer any questions/queries that anyone may have.

Items mentioned above are I2C and permanent clock. I2C isn’t on the key and the need for a permanent clock are because of legacy design issues. Some people have made good suggestions above for how to interface ScreenKeys to I2C. Our designs either include a separate crystal to generate the clock or we use an internal clock generator from the MCU.

Regarding comparison to E3 (hello Roland!) and NKK, there are multiple ways to compare such products, e.g. display characteristics, resolution, tactile feeling, interfacing, support tools, build quality, build consistency, volume availablility etc. ScreenKeys have been in the market for many years and have proven themselves in many testing environments.

We are currently finishing the design of a new ScreenKey based on a full-color TFT screen. This has a resolution of 128x128 pixels with a color palette of 65,536 colors per pixel. The interface to this key is SPI (no permanent clock) and it can report keypresses over the SPI bus. We plan to bring this product into production before end Q1 2008. Please watch our website for further news (www.screenkeys.com).

If anyone has any specific questions or queries, please post them here and I will attempt to answer them.

Mark

Hi Mark,

Are there any plans to develop a militerized version of the Screenkey? I ordered a sample to play around with at work and I can think of a dozen different products that it could be incorporated in but I would need a more robust version. The company I work for deals almost exclusively in US Naval contracts so everything I build needs to be able to survive a gauntlet of tests including shock, vibration, temperature, salt spray, etc. The pricepoint of the Screenkey is very good compared to my alternatives and having to pay extra for a tougher model will still probably be a great deal.

On the TFT version you are planning, will the backlight be dimmable?

-Bill

I just received my sample of an RGB24T. :smiley: I can hardly wait to connect it up to a PIC. :smiley:

8-Dale

Hi Bill,

There are no plans to make any of our products to a MIL spec. The new TFT key will have an IP65 option for water and dust ingress. However, we are currently selling the RG and RGB products to military equipment manufacturers and they are used in military aerospace and other products. As an option, some people have used sealants and membranes to cover groups of keys for better protection.

Electronically, the TFT backlight controller is just on/off but we plan to offer some dimming capability using PWM control of the LED. This will be controlled by the onboard MCU so the user does not have to worry about this. Its not clear yet just how much brightness control will be possible for the user.

I hope you can consider using our products even in your demanding environment. Sometimes the value benefit outweighs the lack of MIL spec!

Mark

Hi Mark, I got a sample of these keys. I was thinking of pluggin them into a pcb with the use of headers (for testing and for easy replacement) but the pins on the keys are springs loaded. I noticed this is what makes the button springy. Will I have to solder them in or is there another way of doing it?

I was thinking of using these for small machines we build at work that would be overkill to use HMI’s. So far i’ve put that project on hold, but want to restart soon.

Brainwav.

brainwav:
Hi Mark, I got a sample of these keys. I was thinking of pluggin them into a pcb with the use of headers (for testing and for easy replacement) but the pins on the keys are springs loaded. I noticed this is what makes the button springy. Will I have to solder them in or is there another way of doing it?

I was thinking of using these for small machines we build at work that would be overkill to use HMI’s. So far i’ve put that project on hold, but want to restart soon.

Brainwav.

Check out the breakout board I designed on page one. It addresses this issue. I can send you the Eagle files if you are interested.

Internally, the springs bring the comms and power to the PCB and they provide the return pressure after a keypress.

For testing in production we use spring loaded contacts to make this connection without having to solder. However this is not a usable solution for

field usage.

MarkS suggestion of the breakout board he designed is probably your best way forward.

Hi there, I received a sample of the RG screenkey, and I am having a bit of trouble getting started with this. I am using an AtMega644v running at 8Mhz with the internal oscillator. I am also using the sample code provided by Sparkfun on the product listing page, When I apply power to the circuit, all that shows up is one solid black line at the top of the screen, 1 pixel tall, all the way across.

Any ideas?

Hi ratmandu, the sample code provided by Sparkfun was written by themselves for the AtMega. I believe this is fully functional and was used to generate the pictures used on their site.

The fact that you are seeing a line of pixels means that you are sending the clock and some data. I will have a look at the sample code download and see if I notice anything unusual. Perhaps you could forward your schematic and your own code and maybe I can help with the problem.

I was playing around a bit more with it last night, and thought of a couple things to try. I Changed the image headers around a bit to see if I could get the pattern of pixels to change, but it was no use. Also, something I left out of my previous post was that the backlight refused to turn on, even after attempting to turn it on first thing in the code. Also, when I press on the button, or manually connect the PB7 pin to GND, the line of pixels dims for about half a second then comes back up, like it is refreshing the screen.

I ruled out power problems, I was previously using batteries to power it, and thought that might be the problem, So I changed that to a regulated 5 volt power supply, but it still had the same problem. As for the schematic, I made a simple breakout board in eagle, then connected it to a breadboard containing the AtMega644, and using a 4.7K pullup resistor on the clk and data lines. Ill be able to do more with it later on tonight when I get home. And i’m hoping to get this working, as I have a few project I would like to integrate some of these into. And also been thinking about making a piggyback board with SPI or plain old RS-232.

Thanks for the help!

I have looked through the example code from Sparkfun and it looks ok. We do not use this micro so I cannot readily run it live. If you can PM me with your code and exact info on how you have hooked up between the key and your micro maybe I can spot something.

Also, have a look at the application notes on the ScreenKeys website (click on “Support” then “Application Notes”). There is some sample code to download from here as well. The first thing to get working should be the backlight. I would concentrate on this. You only need to have one successful command to enable the backlight (whereas the pixel area requires proper setup of the init registers.

The fact that the line of pixels dims when you press the key makes me think there is something wrong with the connections. This should not happen. The switch circuit is electrically isolated and has no connection to the rest of the switch circuitry, not even the power.

Please recheck the connections and PM me with your specifics and hopefully we can get to the bottom of it.

Currently the code is exactly as in the sparkfun example code. The connections are set up as outlined in the file, PB0 to Clk, PB1 to data with 4.7k pullup resistors. I will try, as you said, to get the backlight working, and will post back with any code changes.

Thanks for your help.

Unfortunately, I have accidentally set bad fuses on my AtMega644, and an unable to correct it, but while I had it up, I still was not able to get anything but the single row of pixels.

I am going to be moving over to either the tiny2313 to tiny84 and I will see about getting it working there.

Hi ratmandu,

I have looked as closely as possible at the code from Sparkfun (without being able to run it) and it all looks OK. However, note that this code is written for the RG/LC24 product type (36x24 pixels). This is identified by the setup of the MUX register (0xEF/0xF0 = 0x07/0x00). For LC16 product type (32x16 pixels) the MUX register must be set to 0x02/0x05. Setting the type incorrectly will cause the pixel display to appear corrupted but would have no impact on color selection. I do not know what type of ScreenKey you are working with.

Also remember that if you are using LC16 ScreenKeys then the pixel mapping is completely defined by 64 bytes of data (LC24 uses 108 bytes) and the Sparkfun examples are prepared for the LC24 format.

If you can concentrate on getting the backlight selection working first (comment out any accesses to the pixel display initially) then you can confirm that you are correctly talking to the key.

Ok, I’ve managed to figure out what was wrong with the code, at least for the Tiny861 I am now using. It was writing the wrong bit to a register for the interrupt, and I have also fixed the timing for it. I got it changing through the different colors, and displaying a pattern of vertical lines moving back and forth. It is working pretty good, though some of the pixels kinda seem to "stick"for a VERY short amount of time, barely noticable though. Thanks for all of your help, and heres the code with all the touchups.

The AVR is a ATtiny861, running with the internal oscillator with the CKDIV fuse turned off, so it runs at 8Mhz. PB0 is connected to Clock, PB1 is connected to data. The timer is set to run at 50khz.

The Screenkey is an LC24.

Also, I should have written a good delay loop, but I just wanted to get this working :lol:

main.c

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "line1.h"
#include "line2.h"

volatile uint8_t phase;
volatile uint16_t bits;
volatile uint8_t cnt;
volatile uint16_t fcnt=0;

// This file is designed to be used on an atmega 640, running @ 16mhz,
// but should be portable to other processors
// Pin assignments are:
// 	PB7 - Button input [button drives pin low]
// 	PB1 - Data
// 	PB0 - Clock

ISR(TIMER0_OVF_vect){
	fcnt++;
	TCNT0H = 0xFF;
	TCNT0L = 0x60;
	
	phase = ~phase;
	
	PORTB = (PORTB & 0XFE) | (phase?1:0 );
	
	if (phase)
	{
		if (!cnt)
		{
			PORTB |= 0x2;
			return;
		}
		
		PORTB = (PORTB & 0xFD) | ((bits & 0x1) ? 2:0);
		cnt --;
		bits = bits >> 1;
	}
	
}

/* Prepares a word to be sent to the lcd */
/* Parity is 1 for odd parity, 0 for even */
void screenkey_write(uint8_t data, uint8_t parity)
{
	/* Block while theres still a word being sent */
	while (cnt);
	
	/* Calculate parity */
	uint8_t pb = data ^ (data >> 1) ^ (data >> 2) ^ (data >> 3) ^
	(data >> 4) ^ (data >> 5) ^ (data >> 6) ^ (data >> 7)
	^ parity;
	
	/* Setup the bits to send */
	bits = 0x0C00 | (data << 1) | ((pb & 0x1) << 9);
	
	/* queue it up */
	cnt = 12;
}

/* Start / Stop characters */
void screenkey_start(void)
{
	screenkey_write(0x00, 0);
}

void screenkey_stop(void)
{
	screenkey_write(0xAA, 0);
}

// Write a 1 byte register
void screenkey_reg_1(uint8_t reg, uint8_t val)
{
	screenkey_start();
	screenkey_write(reg,1);
	screenkey_write(val,1);
	screenkey_stop();
	
}

// Write a 2 byte register
void screenkey_reg_2(uint8_t reg, uint8_t val1, uint8_t val2)
{
	screenkey_start();
	screenkey_write(reg,1);
	screenkey_write(val1,1);
	screenkey_write(val2,1);
	screenkey_stop();
}


// Write a 108 byte image to the screen
// Data is a pointer to 108 bytes of image data to display
void screenkey_write_img(uint8_t * data)
{
	
	uint8_t i;
	
	screenkey_start();
	screenkey_write(0x80,1);
	for (i=0; i<108; i++)
		screenkey_write(data[i], 1);
	
	screenkey_stop();
	
}


void screenkey_set_color(uint8_t color)
{
	screenkey_reg_1(0xED, color);
}

int main(void)
{
	cli();
	cnt = 0;
	DDRB = 0x3;
	PORTB = 0x83;
	DDRA = 0xFF;
	PORTA = 0xFF;
	
	TIMSK= (1<<TOIE0);
	
	TCCR0A = (1<<TCW0);
	
	TCCR0B = (1<<CS00);
	
	sei();
	
	// Setup the control registers
	screenkey_reg_1(0xEE, 0x00);
	screenkey_reg_2(0xEF, 0x07, 0x00);
	
	while (1) {
		screenkey_set_color(0x03);
		screenkey_write_img(line1_img);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		screenkey_set_color(0x33);
		screenkey_write_img(line2_img);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		screenkey_set_color(0x0C);
		screenkey_write_img(line1_img);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		screenkey_set_color(0xCC);
		screenkey_write_img(line2_img);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		screenkey_set_color(0x0F);
		screenkey_write_img(line1_img);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		screenkey_set_color(0xFF);
		screenkey_write_img(line2_img);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		screenkey_set_color(0x3F);
		screenkey_write_img(line1_img);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		screenkey_set_color(0xCF);
		screenkey_write_img(line2_img);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		_delay_ms(200);
		
	}
			
}

line1.c

uint8_t line1_img[108] = {
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 
	0x55, 0x55, 0x55, 0x55, 0x55, 
};

line2.c

uint8_t line2_img[108] = {
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 
	0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 
};

EDIT: Also, How hard would it be, for a pretty much beginner, to write some code to place an integer, with an image, on the screen dynamically, as in a temperature application. I am planning on replacing some of the lightswitches in my home with an ATtiny84, and a solid state relay. Then use the screen to display the temperature (taken from the built in temperature sensor on the tiny84) and change the backlight depending on where the temperature is, like green for cooler (or blue, if i decide to get the RGB ones), orange for good, room temp, and red for a warm room. Then use the button to toggle the lights in the room. The SSR would be optically isolated of course.

Am I going completely out of the capability of this microcontroller? Or even out of my programming capability? :lol:

Thats great that you managed to get your code working. If you are agreeable I would like to post your code on our website in case others can benefit from working code for this type of CPU (credit posted accordingly of course). If you agree, please advise name to use for credits.

Here is a conversion routine for generating suitable bitmaps from ASCII characters.

Font/character set definition:

BYTE code g_lpCharLU[128][5] = {
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 000      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 001      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 002      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 003      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 004      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 005      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 006      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 007      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 008      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 009      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 010      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 011      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 012      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 013      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 014      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 015      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 016      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 017      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 018      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 019      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 020      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 021      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 022      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 023      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 024      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 025      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 026      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 027      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 028      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 029      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 030      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 031      
    { 0x0, 0x0, 0x0, 0x0, 0x0, },   // 032      Space
    { 0x0, 0x0, 0x79,0x0, 0x0, },   // 033      !
    { 0x0, 0x70,0x0, 0x70,0x0, },   // 034      "
    { 0x14,0x7F,0x14,0x7F,0x14,},   // 035      Hash mark
    { 0x24,0x2A,0x7F,0x2A,0x12,},   // 036      $
    { 0x23,0x13,0x08,0x64,0x62,},   // 037      %
    { 0x05,0x22,0x55,0x49,0x36,},   // 038      &
    { 0x0, 0x0, 0x60,0x50,0x0, },   // 039      '
    { 0x0, 0x41,0x22,0x1c,0x0, },   // 040      (
    { 0x0, 0x1c,0x22,0x41,0x0, },   // 041      )
    { 0x14,0x08,0x3E,0x08,0x14,},   // 042      *
    { 0x08,0x08,0x3E,0x08,0x08,},   // 043      +
    { 0x0, 0x0, 0x06,0x05,0x0, },   // 044      ,
    { 0x08,0x08,0x08,0x08,0x08,},   // 045      -
    { 0x0, 0x0, 0x03,0x03,0x0, },   // 046      .
    { 0x20,0x10,0x08,0x04,0x02,},   // 047      forward slash
    { 0x3E,0x51,0x49,0x45,0x3E,},   // 048      0
    { 0x0, 0x01,0x7f,0x21,0x0, },   // 049      1
    { 0x31,0x49,0x45,0x43,0x21,},   // 050      2
    { 0x46,0x69,0x51,0x41,0x42,},   // 051      3
    { 0x04,0x7F,0x24,0x14,0x0C,},   // 052      4
    { 0x4E,0x51,0x51,0x51,0x72,},   // 053      5
    { 0x06,0x49,0x49,0x29,0x1E,},   // 054      6
    { 0x60,0x50,0x48,0x47,0x40,},   // 055      7
    { 0x36,0x49,0x49,0x49,0x36,},   // 056      8
    { 0x3C,0x4A,0x49,0x49,0x30,},   // 057      9
    { 0x0, 0x0, 0x36,0x36,0x0, },   // 058      :
    { 0x0, 0x0, 0x36,0x35,0x0, },   // 059      ;
    { 0x0, 0x41,0x22,0x14,0x08,},   // 060      <
    { 0x14,0x14,0x14,0x14,0x14,},   // 061      =
    { 0x08,0x14,0x22,0x41,0x0, },   // 062      >
    { 0x30,0x48,0x45,0x40,0x20,},   // 063      ?
    { 0x3C,0x55,0x5D,0x41,0x3E,},   // 064      @
    { 0x3F,0x48,0x48,0x48,0x3F,},   // 065      A
    { 0x36,0x49,0x49,0x49,0x7F,},   // 066      B
    { 0x22,0x41,0x41,0x41,0x3E,},   // 067      C
    { 0x1C,0x22,0x41,0x41,0x7F,},   // 068      D
    { 0x41,0x49,0x49,0x49,0x7F,},   // 069      E
    { 0x40,0x48,0x48,0x48,0x7F,},   // 070      F
    { 0x2F,0x49,0x49,0x41,0x3E,},   // 071      G
    { 0x7F,0x08,0x08,0x08,0x7F,},   // 072      H
    { 0x0, 0x41,0x7f,0x41,0x0, },   // 073      I
    { 0x40,0x7E,0x41,0x01,0x02,},   // 074      J
    { 0x41,0x22,0x14,0x08,0x7F,},   // 075      K
    { 0x01,0x01,0x01,0x01,0x7F,},   // 076      L
    { 0x7F,0x20,0x18,0x20,0x7F,},   // 077      M
    { 0x7F,0x04,0x08,0x10,0x7F,},   // 078      N
    { 0x3E,0x41,0x41,0x41,0x3E,},   // 079      O
    { 0x30,0x48,0x48,0x48,0x7F,},   // 080      P
    { 0x3D,0x42,0x45,0x41,0x3E,},   // 081      Q
    { 0x31,0x4A,0x4C,0x48,0x7F,},   // 082      R
    { 0x26,0x49,0x49,0x49,0x32,},   // 083      S
    { 0x40,0x40,0x7F,0x40,0x40,},   // 084      T
    { 0x7F,0x01,0x01,0x01,0x7F,},   // 085      U
    { 0x7C,0x02,0x01,0x02,0x7C,},   // 086      V
    { 0x7E,0x01,0x0E,0x01,0x7E,},   // 087      W
    { 0x63,0x14,0x08,0x14,0x63,},   // 088      X
    { 0x70,0x08,0x07,0x08,0x70,},   // 089      Y
    { 0x61,0x51,0x49,0x45,0x43,},   // 090      Z
    { 0x0, 0x44,0x41,0x7f,0x0, },   // 091      [
    { 0x02,0x04,0x08,0x10,0x20,},   // 092      back slash
    { 0x0, 0x7f,0x41,0x41,0x0, },   // 093      ]
    { 0x10,0x20,0x40,0x20,0x10,},   // 094      ^
    { 0x01,0x01,0x01,0x01,0x01,},   // 095      _
    { 0x0, 0x50,0x60,0x0, 0x0, },   // 096      `
    { 0x0F,0x15,0x15,0x15,0x02,},   // 097      a
    { 0x0E,0x11,0x11,0x11,0x7F,},   // 098      b
    { 0x11,0x11,0x11,0x11,0x0E,},   // 099      c
    { 0x7F,0x11,0x11,0x11,0x0E,},   // 100      d
    { 0x0C,0x15,0x15,0x15,0x0E,},   // 101      e
    { 0x50,0x50,0x3F,0x10,0x10,},   // 102      f
    { 0x1E,0x15,0x15,0x15,0x08,},   // 103      g
    { 0x0F,0x10,0x10,0x08,0x7F,},   // 104      h
    { 0x0, 0x01,0x5f,0x11,0x0, },   // 105      i
    { 0x0, 0x5e,0x11,0x01,0x02,},   // 106      j
    { 0x0, 0x11,0x0a,0x04,0x7f,},   // 107      k
    { 0x0, 0x01,0x7f,0x41,0x0, },   // 108      l
    { 0x0F,0x10,0x1F,0x10,0x1F,},   // 109      m
    { 0x0F,0x10,0x10,0x08,0x1F,},   // 110      n
    { 0x0E,0x11,0x11,0x11,0x0E,},   // 111      o
    { 0x08,0x14,0x14,0x14,0x1F,},   // 112      p
    { 0x1F,0x14,0x14,0x14,0x08,},   // 113      q
    { 0x0, 0x10,0x10,0x08,0x1f,},   // 114      r
    { 0x12,0x15,0x15,0x15,0x09,},   // 115      s
    { 0x11,0x11,0x7E,0x10,0x10,},   // 116      t
    { 0x1F,0x02,0x01,0x01,0x1E,},   // 117      u
    { 0x1C,0x02,0x01,0x02,0x1C,},   // 118      v
    { 0x1E,0x01,0x06,0x01,0x1E,},   // 119      w
    { 0x11,0x0A,0x04,0x0A,0x11,},   // 120      x
    { 0x1E,0x05,0x05,0x05,0x18,},   // 121      y
    { 0x11,0x19,0x15,0x13,0x11,},   // 122      z
    { 0x0, 0x41,0x41,0x36,0x08,},   // 123      {
    { 0x0, 0x0, 0x77,0x0, 0x0, },   // 124      |
    { 0x08,0x36,0x41,0x41,0x0, },   // 125      }
    { 0x08,0x1C,0x2A,0x08,0x08,},   // 126      ~
    { 0x08,0x08,0x2A,0x1C,0x08,},   // 127      
};


BYTE code pixels[56] =                                 //This array is used in the LC24 algorithm
        {                                               //for the character set
            4,3,3,2,2,
            1,1,0,0,40,
            39,39,38,38,
            37,37,36,36,
            76,75,75,74,
            74,73,73,72,72,36,36,
            37,37,38,38,39,39,40,
            22,22,21,21,20,20,19,19,18,18,
            58,58,57,57,56,56,55,55,54,54,
        };

Conversion routine:

//***************************************************************
//***************************************************************
//
// FUNCTION NAME  : DisplayText
//
// INPUTS         : byKeyNum   -  Key 'display' number.
//                  StringSize -  Number of characters in the string.
//                  *Text      -  Pointer to the buffer where the bitmap is.
//                  Centered   -  Indicates if the string should
//                                be centered.
//                  LC16       -  LC16 data bytes must be bit reversed
//
//                  
// OUTPUTS        : None
//
//                    
// DESCRIPTION    : Display text on a specifed screen key.
//
//
//***************************************************************
//***************************************************************
void DisplayText(BYTE byKeyNum,     \
                 PSTR Text,         \
                 BYTE StringSize,   \
                 BIT  Centered,     \
                 BIT  LC16)
{
    xdata BYTE Bitmap[108];

    TextToBitMap(StringSize, Text, Bitmap, Centered, LC16);
                                                         
    SKPict(byKeyNum, Bitmap, LC16);
}

//***************************************************************
//***************************************************************
//
// FUNCTION NAME  : TextToBitMap
//
// INPUTS         : byStrSize - the lenght of the string passed.
//                  *pStr     - pointer to the string being processed.
//                  *pLcdData - pointer to the buffer that will 
//                              store the data.
//                  bCentered - A boolean value which indicates if
//                              the string should be centered.
//                  LC16      - Set if data intended for LC16
//                  
// OUTPUTS        : None
//
//                    
// DESCRIPTION    : Convert the string to a bitmap for displaying on a LCD key.
//                  We assume all parameters have been verified.
//
//                  We convert the string to a "true" bitmap. Before displaying
//                  this bitmap on a lcd key each byte will need to be bit
//                  reversed (if LC16).
//
//                  An LC16 bitmap consists of 64 bytes laid out as a 
//                  4*16 array. 
//         
//                        Byte0    Byte1    Byte2    Byte3
//                     R0   0        1        2        3
//                     R1   4        5        6        7
//                     :    :        :        :        :
//                     :    :        :        :        :
//                     R15  60       61       62       63
//
//                  A character cell consists of 40 bits laid out as a
//                  5*8 grid. We use a hard coded character set lookup table
//                  to generate each cell. Eg For character 'P' our lookup
//                  table has the following five byte entry:
//                           0x30,0x48,0x48,0x48,0x7F
//                  which translates into:
//
//                           ..XX....
//                           .X..X...
//                           .X..X...
//                           .X..X...
//                           .XXXXXXX
//
//                  Thus each character cell needs to be rotated by 90 degrees 
//                  before being placed in the lcd buffer.
//
//                  A one pixel vertical/vertical space is placed between
//                  characters.
//
//
//***************************************************************
//***************************************************************
void TextToBitMap(BYTE   byStrSize, \
                  PSTR   pStr,      \
                  NPBYTE pLcdData,  \
                  BIT    bCentered, \
                  BIT    LC16)
{                                  
     BYTE xdata  x         = 0;           //Increment
     BYTE xdata  y         = 0;           //Increment
     BYTE xdata  z         = 0;           //Increment
     BYTE xdata  SKMtmp    = 0;           //stores the byte taken from the character set
     BYTE xdata  byChr     = 0;           //byte from a string passed
     BYTE xdata  byOrg     = 0;           //where the text will start on an LC16 key
     BYTE xdata  byColCnt  = 0;           //tracks next column (pixel) position to write in image array
     BYTE xdata  byMask    = 0x80;        //the mask points to position within byte in image array
     BYTE xdata  byLineCnt = 0;           //points to current line in image array being populated
     BYTE xdata  byLineTmp;               //used to inc from current LineCnt to next line

     BYTE       *pBuf      = 0;           //used to access the actual pixel image

     BOOL        bOdd      = 1;           //used to indicate what pattern the byte run vertically in LC24
     
     memset(pLcdData,0,108);
     
     if(LC16)
     {
         //Determine starting position if less than full line (<5 chars)
         if((byStrSize <= 5) && (bCentered))    //if centered and 5 or less characters
         {
             byOrg = (64 / 4);                  //The origin of the characters is byte 16
         
             if(byStrSize < 5)                  
             { 
                 byColCnt = (5 - byStrSize) * 6;    //These are calculation used to say where the
                 byColCnt >>= 1;                    //text should start for a given number of characters
                 byMask = (0x80 >> (byColCnt % 8));
             }
         }
     
         //Create image from passed string
         for(x = 0; x < byStrSize; x++) 
         {
            //take the next byte from the string passed
            byChr = *pStr++;

            //move to the next line if the top line has been filled
            if(x == 5) 
            {
               byOrg = 32;      //Start at beginning of next line
               byMask = 0x80;
               byColCnt = 0;    //First column in this row
            }
                               
            //Every char pattern has bit 0x80 blank
            byMask = ( (byMask>>1) | (byMask<<7) );
            byColCnt++ ;                        

            //Loop through each byte of character set for this display char
            for(y = 0; y < BYTESPERCELL; y++) 
            {
               pBuf = pLcdData + byOrg + (byColCnt / 8);    //The byte about to be processed will be placed
                                                            //in pLcdData at a certain location
               SKMtmp = g_lpCharLU[byChr][(BYTESPERCELL - 1) - y];//Use the character set stored in prom
              
               for(z=0;z<8;z++) 
               {
                  if(SKMtmp & 0x80)         //if the bit is a one process it
                     *pBuf |= byMask;

                  SKMtmp <<= 1;             //otherwise move on
                  pBuf += 4;
               }

               byColCnt++;
               byMask = ( (byMask>>1) | (byMask<<7) );
            }  
         }
     }
     
     else
     {
         //Initialise counters
         byColCnt  = 35;    //Start in left most colum
         byLineCnt = 0;     //Start on first (top) line
         bOdd      = 1;     //First line is "Odd" => last byte only has lower nibble
         byMask    = 0x08;  //First pixels are in low nibble of byte 4

         //Determine starting position if less than full line (<6 chars)
         if((byStrSize <= 6) && (bCentered))
         {
             //Text centred on vertical - always line num 8 which is always odd
             byLineCnt = 8;
             //Centred on horiz - find num free spaces, alloc half (3 pixels) to each side
             byColCnt  = 35 - ((6 - byStrSize)*3);
             //Set mask start to match first position
             if(byColCnt < 32)
                 //In first 4 normal bytes (not shifted)
                 byMask = (0x80 >> ((31-byColCnt) % 8));
             else
                 //In last byte (=4) then mask is shifted across nibble only
                 byMask = (0x80 >> ((39-byColCnt) % 8));
         }

         //Create image from passed string
         for(x = 0; x < byStrSize; x++) 
         {
             //Take next byte from string passed
             byChr = *pStr++;
        
             //move to the next line if current line has been filled
             //need 5 pixels (columns) to fit next char
             //if all 6 chars written to end then byColCnt wraps around to 255 
             if(byColCnt < 4 || byColCnt > 250) {
                 byLineCnt += 8;
                 byColCnt   = 35;
                 bOdd       = 1;
                 byMask     = 0x08;
             }
             
             //Loop through each byte of character set for this display char
             for(y = 0; y <  BYTESPERCELL; y++)
             {
                 SKMtmp = g_lpCharLU[byChr][(BYTESPERCELL - 1) - y];//Get next char pattern byte

                 byLineTmp = byLineCnt;

                 //Loop through every pixel in char pattern byte
                 for(z=0;z<8;z++) 
                 {
                    if(SKMtmp & 0x80) {        //if the bit is a one process it
                        pBuf = pLcdData + ((byLineTmp/2)*9) + ((byColCnt+(bOdd?0:4))/8) + (bOdd?0:4);    
                                                                     //The byte about to be processed will be placed
                                                                     //in pLcdData at a certain location
                        if(bOdd)
                            //Use mask as normal
                            *pBuf |= byMask;
                        else
                            //Next line has bytes shifted left x 4 bits
                            //Need to move mask up/down depending on position (nibble)
                            if(byMask > 0x08)
                                *pBuf |= (byMask>>4);
                            else
                                *pBuf |= (byMask<<4);
                    }

                    //move to next bit in char pattern byte (next line in image)
                    SKMtmp <<= 1;
                    bOdd = !bOdd;
                    byLineTmp++;
                 }

                 //Go to next column to fill
                 byColCnt--;
                 byMask = ( (byMask>>1) | (byMask<<7) );
                 
             }
             
             //Skip one pixel column to create gap between chars
             byMask = ( (byMask>>1) | (byMask<<7) );
             byColCnt-- ;
         }
     }
}

Output to ScreenKey:

//***************************************************************
//***************************************************************
//
// FUNCTION NAME  : SKPict
//
// INPUTS         : KeyNr       - The key to display the picture on.
//                  *PictData   - A Pointer the an array containing
//                                the picture to be displayed.
//                  LCTpye      - 1 = LC16, 0 = LC24
//                  
// OUTPUTS        : None
//
//                    
// DESCRIPTION    : Called when a key display is to be updated.
//
//
//***************************************************************
//***************************************************************
void SKPict(BYTE KeyNr, NPBYTE PictData, BIT LCType)
{
    BYTE i = 0;
    BYTE lcd_size;

    SwSelect(KeyNr);            // select the key to display the image on.   
    
    SKDataOut(0x00, 0 );        // Start Byte
    SKDataOut(LC_PIXREG, 1);    // Cmd Byte               

    //Set max size to transmit to key
    lcd_size = LCType ? 64 : 108;

    for (i = 0; i < lcd_size; i++)
        if(LCType == 0)
            SKDataOut(PictData[i], 1);             // LC24    
        else
            SKDataOut(BitReverse(PictData[i]),1);  // LC16
   
    SKDataOut(0xAA, 0);         // End Byte
}

Obviously you would have to work on this to translate it for your specific compiler and MCU type. It is possible to reduce its footprint by only using part of the character set and removing the LC16 or LC24 sections that you do not want.

Good luck with it!

Thank you very much for that code, I will get onto porting that over, and will post my results here. And you are welcome to posting the other code, as long as its ok with sparkfun, since most of the code was theirs, I just fixed it up a bit to run on my MCU. :lol:

But if you must, You can credit my portion to Justin Richards

Again, Thank you for all the help! I will definitely be purchasing several of these for a few projects.

It’s been a while since the last post in this thread, but just in case anyone is still looking, below is simple ScreenKey library with dynamic text suport for Atmel AVR controllers based on Sparkfun’s example and “ScreenKeys” previous post.

utility.h

#ifndef UTILITY_H
#define UTILITY_H

#include <avr/io.h>


/*
 *  Convenience macro to combine I/O DDR/PORT/PIN and pin assignments together
 *
 *  Usage example:
 *
 *    #define LED2(reg) BIT(D,5,reg)
 *    #define SW2(reg) BIT(D,2,reg)
 * 
 *    int main()
 *    {
 *        SW2( DDR) = 0;
 *        SW2( PORT) = 1; //pullup on
 *        LED2( DDR) = 1;   
 *        while( 1)
 *      {
 *            if( SW2( PIN) == 0)
 *        {
 *                LED2( PORT) = 1;
 *            }
 *            else
 *        {
 *                LED2( PORT) = 0;
 *            }
 *        }
 *    } 
 */
struct bits {
  uint8_t b0:1;
  uint8_t b1:1;
  uint8_t b2:1;
  uint8_t b3:1;
  uint8_t b4:1;
  uint8_t b5:1;
  uint8_t b6:1;
  uint8_t b7:1;
} __attribute__((__packed__));
#define BIT(name,pin,reg) \
 ((*(volatile struct bits*)&reg##name).b##pin)


// Logic constants
#define FALSE 0
#define TRUE !FALSE
#define HIGH 1
#define LOW 0
#define OUT 1
#define IN 0
#define ON 1
#define OFF 0


#endif  //UTILITY_H

screenkeys.h

/*
 * This file contains hardware constants for ScreenKeys configurable
 * LCD display/switches.  See <http://www.screenkeys.com>.
 *
 * This code is not maintained and is free to use with no rights reserved, and
 * no guarantees made.
 *
 * Uses - Timer0, 2 outputs, 1 input.
 *
 * Tom Blough 7/20/2008
 */

#ifndef SCREENKEYS_H
#define SCREENKEYS_H

#include "utility.h"


/******** USER CONFIGURABLE OPTIONS ************************/

// ScreenKey type
#define SK_LCD24 1  // 0 = LC16/RGB16, 1 = LC24/RGB24

// Physical connection between ScreenKey and uP
#define SK_CLK(reg) BIT(D,0,reg)  // Screenkey clock output pin
#define SK_DATA(reg) BIT(D,1,reg)  // Screenkey data output pin
#define SK_SWITCH(reg) BIT(D,7,reg)  // Screenkey switch input pin

// Screenkey clock data - probably don't need to change this.  A SK clock
// of 50KHz should work fine for CPU clocks from 1MHz to 20MHz
#define SK_CLK_FREQ 50000UL  // ScreenKey clock frequency in Hz.  Range 50KHz - 4.0MHz
#define SK_FREQ_REG 0x00  // from Frequency Value Table in datasheet; must match SK_CLK_FREQ value

// uncomment the following line to include support for dynamic text.
// this adds approximately 1K program and 480 data bytes to the code footprint.
#define SK_TEXT_SUPPORT

/******** END OF CONFIGURATION OPTIONS ************************/


// RGB Background colors (3 colors [rgb] each with 3 intensities [off, on, bright])
#define SK_BLACK 0b00000000
#define SK_GRAY 0b00000111
#define SK_WHITE 0b01110111

#define SK_DARK_BLUE 0b00000001
#define SK_BRIGHT_BLUE 0b00010001
#define SK_PALE_BLUE 0b00010111

#define SK_DARK_RED 0b00000010
#define SK_BRIGHT_RED 0b00100010
#define SK_PALE_RED 0b00100111

#define SK_DARK_MAGENTA 0b00000011
#define SK_BRIGHT_MAGENTA 0b00110011
#define SK_PALE_MAGENTA 0b00110111

#define SK_DARK_GREEN 0b00000100
#define SK_BRIGHT_GREEN 0b01000100
#define SK_PALE_GREEN 0b01000111

#define SK_DARK_CYAN 0b00000101
#define SK_BRIGHT_CYAN 0b01010101
#define SK_PALE_CYAN 0b01010111

#define SK_DARK_YELLOW 0b00000110
#define SK_BRIGHT_YELLOW 0b01100110
#define SK_PALE_YELLOW 0b01100111

#define SK_PURPLE 0b00010011
#define SK_COBALT 0b00010101
#define SK_LAVENDER 0b00100011
#define SK_ORANGE 0b00100110
#define SK_TURQUOISE 0b01000101
#define SK_CHARTREUSE 0b01000110

// Commands
#define SK_SET_PIXEL 0x80
#define SK_SET_COLOR 0xED
#define SK_SET_FREQ_REG 0xEE
#define SK_SET_MUX 0xEF
#define SK_START_BYTE 0x00
#define SK_END_BYTE 0xAA

// LCD size specific image bytes and mux contrast data
#if SK_LCD24
#define SK_IMAGE_BYTES 108  // maximum bytes in pixel image
#define SK_MUX_DATA 0x0700  // mux divisor values for LCD contrast
#define SK_IMAGE_WIDTH 36  // display width in pixels  
#define SK_MAX_TEXT 6  // maximum of 6x8 characters across LCD
#define SK_LINE_1 0  // byte offset in display pixel map for start of 8-pixel line
#define SK_LINE_2 36
#define SK_LINE_3 72
#else
#define SK_IMAGE_BYTES 64
#define SK_MUX_DATA 0x0205
#define SK_IMAGE_WIDTH 32
#define SK_MAX_TEXT 5
#define SK_LINE_1 0
#define SK_LINE_2 32
#endif

// text alignment
#define SK_TEXT_CENTER 0
#define SK_TEXT_RIGHT 1
#define SK_TEXT_LEFT 2

// Parity values
#define SK_START_PARITY 0  // 0 = even parity
#define SK_CMD_PARITY 1  // 1 = odd parity
#define SK_DATA_PARITY 1
#define SK_END_PARITY 0

// public functions
extern void sk_init( void);
extern void sk_set_pixels( uint8_t data[], uint8_t offset, uint8_t count);
extern void sk_set_color( uint8_t color);

#ifdef SK_TEXT_SUPPORT
static const uint8_t charMap[96][5];  // 5x8 bitmap font
extern void sk_set_text( char* text, uint8_t justify, uint8_t line);
extern void sk_clear_text( uint8_t line);
#endif


// private helper functions
// uint8_t freq_reg_value( uint32_t frequency); // not needed see function implementation
void send_cmd( uint8_t cmd, uint16_t data);
void write_byte( uint8_t data, uint8_t parity);

#endif // SCREENKEYS_H

screenkeys.c

/*
 * This file establishes basic communication with a ScreenKeys configurable
 * LCD display/switch.  See <http://www.screenkeys.com>.  The routines here
 * should work with both the LC and RGB series as well as both size displays.
 *
 * An 8-bit timer on the AVR is used to contorl the clock generator.  If we
 * look at the extreme limits of the frequencies involved, an 8-bit counter
 * should be enough to handle the task.
 *   1Mhz AVR + 50KHz ScreenKey Clock = 1,000,000 / 50,000 / 2 = 10 counts
 *   20MHz AVR + 4MHz ScreenKey = 20/4/2 = 3 counts
 *   20MHz AVR + 50KHz Screenkey = 20,000,000 / 50,000 /2 = 200 counts
 * In reality, it looks like 80KHz ScreenKey clock is about the best that can
 * be done with 8MHz CPU, 190KHz with 20MHz, and the slowest CPU clock is about
 * 5MHz to get the minimum 50KHz ScreenKey clock.
 *
 * This code is not maintained and is free to use with no rights reserved, and
 * no guarantees made.
 *
 * Code is based on examples provided by Sparkfun:
 *  <http://www.sparkfun.com/datasheets/Components/screenkeys_example.zip>
 *  <http://forum.sparkfun.com/viewtopic.php?t=8312&start=30>
 *
 * Uses - Timer0, 2 outputs, 1 input.
 *
 * Tom Blough 7/20/2008
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include "screenkeys.h"
#include "utility.h"

volatile uint16_t bitData;  // bit buffer for 12-bit synchronous data packet


/////////////////////////////////////////////////
//                PUBLIC INTERFACE             //
/////////////////////////////////////////////////

/*
 * Setup the physical connections and establish communications with
 * the ScreenKey configurable pushbutton.
 */
void sk_init()
{
  SK_CLK( DDR) = OUT;  // set SK clock pin to output
  SK_DATA( DDR) = OUT;  // set SK data pin to output
  SK_DATA( PORT) = HIGH;  // set data pin high

  // setup 8-bit timer for ScreenKey clock generator
  TCCR0A |= (1<<WGM01);  // CTC mode
  TCCR0B |= (1<<CS00);  // internal clock on, no prescale
  OCR0A = (F_CPU / SK_CLK_FREQ / 2);  // set matchA count
  TIMSK0 |= (1<<OCIE0A);  // enable CTC interrupt on matchA
  sei();  // enable global interrupts

  // setup frequency divider and multiplex values
  send_cmd( SK_SET_FREQ_REG, SK_FREQ_REG);
  send_cmd( SK_SET_MUX, SK_MUX_DATA);
}

/*
 * Write the button image data (or a portion of it) to the ScreenKey
 */
void sk_set_pixels( uint8_t data[], uint8_t count, uint8_t offset)
{
  if( offset + count > SK_IMAGE_BYTES) return;

  write_byte( SK_START_BYTE, SK_START_PARITY);
  write_byte( SK_SET_PIXEL + offset, SK_CMD_PARITY);

  for( uint8_t i = 0; i < count; i++)
    write_byte( data[i], SK_DATA_PARITY);

  write_byte( SK_END_BYTE, SK_END_PARITY);
}

/*
 * Set the ScreenKey background color
 */
void sk_set_color( uint8_t color)
{
  send_cmd( SK_SET_COLOR, color);
}

#ifdef SK_TEXT_SUPPORT
/*
 * Clears a line of text
 */
void sk_clear_text( uint8_t line)
{
  uint8_t clear[SK_IMAGE_WIDTH] = {0x00};

  sk_set_pixels( clear, SK_IMAGE_WIDTH, line);
}

/*
 * Convert the text characters into a bitmap array and send to the ScreenKey
 * display area.  The bitmap array will need manipulation because the LC16
 * display starts at the upper left, while the LC24 display starts at the
 * upper right.
 */
void sk_set_text( char* text, uint8_t justify, uint8_t line)
{
  uint8_t textBuffer[SK_IMAGE_WIDTH] = {0x00}, dataBuffer[SK_IMAGE_WIDTH];
  uint8_t data, count, *ptr;

  // adjust string length if necessary. count is number of characters in
  // string or SK_MAX_TEXT characters - whichever is fewer.
  for( count = 0, ptr = (uint8_t*)text; *ptr && (count < SK_MAX_TEXT); count++, ptr++);

  // justify text by moving down appropiate number of columns in our text buffer
  if( justify == SK_TEXT_CENTER)
    ptr = textBuffer + ((SK_IMAGE_WIDTH - (count * 6)) / 2);
  else if( justify == SK_TEXT_LEFT)
    ptr = textBuffer + (SK_IMAGE_WIDTH - (count * 6));
  else
    ptr = textBuffer;

  // now fill buffer with characters starting from end of line
  // moving towards beginnning
  for( uint8_t chr = count; chr > 0; chr--)
  {
    // each character is 5 columns
    for( uint8_t col = 0; col < 5; col++)
    {
      // for each column, index into the character array (offsetting for the
      // 32 control characters we've excluded to save space), and grab
      // the corresponding 8-pixel row bitmap.
      *ptr++ = charMap[(uint8_t)(text[chr - 1]) - 0x20][col];
    }
    ptr++;  // add a blank column between characters
  }
  
  // convert the line of text into image bytes in another buffer. we'll then
  // send the buffer to the ScreenKey to improve the refresh speed over
  // creating and then sending one byte at a time. the tradeoff is that
  // this uses an additional 24/36 bytes for the buffer. some guru
  // might be able to figure out how to do this conversion in-place in the
  // text buffer without needing an additional buffer.
  count = 0;  // current bit/row position
  data = 0x00;
  ptr = dataBuffer;

  // for each of the 8 rows of pixels in the line of text
  for( uint8_t row = 8; row > 0; row--)
  {
  #ifdef SK_LCD24  // LCD24 display (36*24) with 0,0 in the upper right
    // for each of the 24 or 36 columns in the line of text
    for( uint8_t col = 0; col < SK_IMAGE_WIDTH; col++, count++)
    {
      if( count == 8)  // finished building a byte?
      {
        *ptr++ = data;  // queue it up
        data = 0x00;  // and reset to build another
        count = 0;
      }
      // set the next bit in our data byte
      data |= (((textBuffer[col] & (1<<(row - 1)))>>(row - 1))<<count);
    }
  #else  // LCD16 display (32*16) with 0,0 in the upper left 
    for( uint8_t col = SK_IMAGE_WIDTH; col > 0 ; col--, count++)
    {
      if( count == 8)
      {
        *ptr++ = data;
        data = 0x00;
        count = 0;
      }
      data |= (((textBuffer[col - 1] & (1<<(row - 1)))>>(row - 1))<<count);
    }
  #endif
  }
  *ptr = data;  // queue up the last byte

  // finally, send it to the ScreenKey for display
  sk_set_pixels( dataBuffer, SK_IMAGE_WIDTH, line);
}
#endif // SK_TEXT_SUPPORT


/////////////////////////////////////////////////
//      PRIVATE HELPER FUNCTIONS        //
/////////////////////////////////////////////////

/*
 * Timer ISR to generate clock signal and sychronize data transmission.  All
 * data transitions occur on the rising edge of the clock, and the data line
 * is to remain high when not transmitting.  Since last two bits (stop bits)
 * of every data packet transmitted are high, we do not have to actively do
 * anything to make the data line high after transmitting data.
 *
 * We want to keep this as short as possible.  At 8MHz uP clock and 50KHz
 * ScreenKey clock, we only have 80 clock ticks between interrupts.
 */
ISR( TIMER0_COMPA_vect)
{
  // start the next half of the clock waveform
  SK_CLK( PORT) = ~SK_CLK( PORT);
  
  if( SK_CLK( PORT)) // are we on rising edge?
  {
    if( bitData) // do we have data?
    {
      // set the data output pin to correspond
      // to the current bit0 of the data buffer
      SK_DATA( PORT) = (bitData & 0x01);
      // advance to the next bit - unsigned shift will fill with 0s
      // bitData will be == 0 when stop bits have finished transmitting
      bitData >>= 1;
    }
  }
}

/*
 * Prepares a byte to be sent to the ScreenKey
 * Parity is 1 for odd parity, 0 for even
 */
void write_byte( uint8_t data, uint8_t parity)
{
  uint16_t tmp;
  uint8_t pCalc;

  // calculate parity
  // lsb of pd keeps track of unmatched 1 bits which is then XORed with
  // desired parity type.  Needed parity bit is left in lsb
  /*pCalc = (data ^ (data >> 1) ^ (data >> 2) ^ (data >> 3)
        ^ (data >> 4) ^ (data >> 5) ^ (data >> 6) ^ (data >> 7)
        ^ parity) & 0x01;*/

  // this parity algorithm saves a few bytes over the one above.
  // from "compute parity in parallel" bit twiddling hacks by Sean Anderson
  // http://graphics.stanford.edu/~seander/bithacks.html
  // needed parity bit is left in lsb.
  pCalc = data;
  pCalc ^= pCalc >> 4;
  pCalc &= 0x0f;
  pCalc = ((0x6996 >> pCalc) ^ parity) & 0x01;


  // build our data packet
  //      not used
  //      |   stop bits
  //      |   | parity bit
  //      |   | |data (msb-lsb)
  //      |   | ||       start bit
  //      <--><>|<------>|
  //      xxxx11pdddddddd0      parity          data
  tmp = 0b0000110000000000 | (pCalc << 9) | (data << 1);

  // queue our data for transmission
  // since no transmit buffer, we have to wait until previous data is done
  // and since data is two bytes, we have to make sure no clock pulse happens
  // between the bytes loading.
  while( bitData);
  ATOMIC_BLOCK( ATOMIC_RESTORESTATE)
  {
    bitData = tmp;
  }
}

/*
 * Send a command to the ScreenKey
 */
void send_cmd( uint8_t cmd, uint16_t data)
{
  write_byte( SK_START_BYTE, SK_START_PARITY);
  write_byte( cmd, SK_CMD_PARITY);

  if( cmd == SK_SET_MUX)  // we only have one command requiring 2 bytes of data
    write_byte( (uint8_t)(data>>8), SK_DATA_PARITY);

  write_byte( (uint8_t)data, SK_DATA_PARITY);
  write_byte( SK_END_BYTE, SK_END_PARITY);
}

/********************************************
This turned out to be an acadaemic exercise.  Since we need to specify the frequency
to generate our clock, it's just as easy to also look up the value for the divider
and define it at the same time.  This ends up saving 354 bytes.  This function might
be useful if one wished to implement a contrast adjustment for the screenkey, but I
doubt such an adjustment would have a practical benefit.

uint8_t freq_reg_value( uint32_t frequency)
{
  // This approximates the lookup table on page 15 in the RGB24 datasheet.
  // The table is function of log10( frequency), but the lookup procedure
  // below actually has a smaller footprint and is faster than a curve fit.
  //
  // Datasheet table is split at 499.2KHz.  If table frequencies of 499.2KHz
  // and above are divided by 10 they almost correspond 1:1 with the first
  // half of the table.  We us this characteristic to reduce the lookup
  // table size by half.  Further, we can divide the frequencies by 1/2
  // and still retain sufficient precision, but we reduce the size of the
  // values so they fit within 8-bits. 
  uint8_t freqLookup[] = { 25, 27, 28, 30, 31, 33, 34, 36, 37, 39, 41, 42, 44, 45,
              47, 48, 50, 53, 56, 59, 62, 66, 69, 72, 75, 78, 81, 84,
              87, 91, 94, 97, 100, 106, 112, 119, 125, 131, 137, 144,
              150, 156, 162, 169, 175, 181, 187, 193, 200, 212, 225, 237 };

  uint16_t lookup = frequency / 1000 / 2;  // convert to KHz and divide by 2
  uint8_t value = 0;  // the resulting frequency value from the table

  for( uint8_t i = 0; ; i++, value += 2)
  {
    // if we reach the end of the lookup list, fold back table to beginning
    if( i >= sizeof( freqLookup) / sizeof( freqLookup[0]))
    {
      i = 0;
      lookup /= 10;
    }
    if( lookup <= freqLookup[i]) return value;
  }
}
*************************************************/

#ifdef SK_TEXT_SUPPORT
/* Each character is encoded into 5 bytes. These 40 bits create
 * a 5x8 character cell rotated 90 degrees counterclockwise with
 * one bit (pixel) of vertical space between rows of characters.
 * For example:
 *
 *    'B' {0x36,0x49,0x49,0x49,0x7F}
 *  
 *       MSB      LSB
 *    B[0] --**-**-
 *    B[1] -*--*--*
 *    B[2] -*--*--*
 *    B[3] -*--*--*
 *    B[4] -*******
 *
 *    'g' {0x1E,0x15,0x15,0x15,0x08}
 *  
 *       MSB      LSB
 *    B[0] ---****-
 *    B[1] ---*-*-*
 *    B[2] ---*-*-*
 *    B[3] ---*-*-*
 *    B[4] ----*---
 *
 * Therefore, an LC24 with a 36*24 display should be able to display
 * 3 rows of text each consisting of 6 characters with one column of 
 * pixels between characters.  A LC16 display (32x16) will display
 * 2 rows of 5 character text with one column of pixels between chars.
 *
 * The first 32 ASCII control characters are not included in this table
 * since they have no use on the ScreenKey, so be sure to offset by
 * 0x20 when indexing into the table.
 *
 * This table can be further reduced to save data space if, for example,
 * only numbers and upper case letters were needed.
 */
static const uint8_t charMap[96][5] = {
  { 0x00,0x00,0x00,0x00,0x00 },  // 032  Space
  { 0x00,0x00,0x79,0x00,0x00 },  // 033  !
  { 0x00,0x70,0x00,0x70,0x00 },  // 034  "
  { 0x14,0x7F,0x14,0x7F,0x14 },  // 035  Hash mark
  { 0x24,0x2A,0x7F,0x2A,0x12 },  // 036  $
  { 0x23,0x13,0x08,0x64,0x62 },  // 037  %
  { 0x05,0x22,0x55,0x49,0x36 },  // 038  &
  { 0x00,0x00,0x60,0x50,0x00 },  // 039  '
  { 0x00,0x41,0x22,0x1c,0x00 },  // 040  (
  { 0x00,0x1c,0x22,0x41,0x00 },  // 041  )
  { 0x14,0x08,0x3E,0x08,0x14 },  // 042  *
  { 0x08,0x08,0x3E,0x08,0x08 },  // 043  +
  { 0x00,0x00,0x06,0x05,0x00 },  // 044  ,
  { 0x08,0x08,0x08,0x08,0x08 },  // 045  -
  { 0x00,0x00,0x03,0x03,0x00 },  // 046  .
  { 0x20,0x10,0x08,0x04,0x02 },  // 047  forward slash
  { 0x3E,0x51,0x49,0x45,0x3E },  // 048  0
  { 0x00,0x01,0x7f,0x21,0x00 },  // 049  1
  { 0x31,0x49,0x45,0x43,0x21 },  // 050  2
  { 0x46,0x69,0x51,0x41,0x42 },  // 051  3
  { 0x04,0x7F,0x24,0x14,0x0C },  // 052  4
  { 0x4E,0x51,0x51,0x51,0x72 },  // 053  5
  { 0x06,0x49,0x49,0x29,0x1E },  // 054  6
  { 0x60,0x50,0x48,0x47,0x40 },  // 055  7
  { 0x36,0x49,0x49,0x49,0x36 },  // 056  8
  { 0x3C,0x4A,0x49,0x49,0x30 },  // 057  9
  { 0x00,0x00,0x36,0x36,0x00 },  // 058  :
  { 0x00,0x00,0x36,0x35,0x00 },  // 059  ;
  { 0x00,0x41,0x22,0x14,0x08 },  // 060  <
  { 0x14,0x14,0x14,0x14,0x14 },  // 061  =
  { 0x08,0x14,0x22,0x41,0x00 },  // 062  >
  { 0x30,0x48,0x45,0x40,0x20 },  // 063  ?
  { 0x3C,0x55,0x5D,0x41,0x3E },  // 064  @
  { 0x3F,0x48,0x48,0x48,0x3F },  // 065  A
  { 0x36,0x49,0x49,0x49,0x7F },  // 066  B
  { 0x22,0x41,0x41,0x41,0x3E },  // 067  C
  { 0x1C,0x22,0x41,0x41,0x7F },  // 068  D
  { 0x41,0x49,0x49,0x49,0x7F },  // 069  E
  { 0x40,0x48,0x48,0x48,0x7F },  // 070  F
  { 0x2F,0x49,0x49,0x41,0x3E },  // 071  G
  { 0x7F,0x08,0x08,0x08,0x7F },  // 072  H
  { 0x00,0x41,0x7f,0x41,0x00 },  // 073  I
  { 0x40,0x7E,0x41,0x01,0x02 },  // 074  J
  { 0x41,0x22,0x14,0x08,0x7F },  // 075  K
  { 0x01,0x01,0x01,0x01,0x7F },  // 076  L
  { 0x7F,0x20,0x18,0x20,0x7F },  // 077  M
  { 0x7F,0x04,0x08,0x10,0x7F },  // 078  N
  { 0x3E,0x41,0x41,0x41,0x3E },  // 079  O
  { 0x30,0x48,0x48,0x48,0x7F },  // 080  P
  { 0x3D,0x42,0x45,0x41,0x3E },  // 081  Q
  { 0x31,0x4A,0x4C,0x48,0x7F },  // 082  R
  { 0x26,0x49,0x49,0x49,0x32 },  // 083  S
  { 0x40,0x40,0x7F,0x40,0x40 },  // 084  T
  { 0x7F,0x01,0x01,0x01,0x7F },  // 085  U
  { 0x7C,0x02,0x01,0x02,0x7C },  // 086  V
  { 0x7E,0x01,0x0E,0x01,0x7E },  // 087  W
  { 0x63,0x14,0x08,0x14,0x63 },  // 088  X
  { 0x70,0x08,0x07,0x08,0x70 },  // 089  Y
  { 0x61,0x51,0x49,0x45,0x43 },  // 090  Z
  { 0x00,0x44,0x41,0x7f,0x00 },  // 091  [
  { 0x02,0x04,0x08,0x10,0x20 },  // 092  back slash
  { 0x00,0x7f,0x41,0x41,0x00 },  // 093  ]
  { 0x10,0x20,0x40,0x20,0x10 },  // 094  ^
  { 0x01,0x01,0x01,0x01,0x01 },  // 095  _
  { 0x00,0x50,0x60,0x00,0x00 },  // 096  `
  { 0x0F,0x15,0x15,0x15,0x02 },  // 097  a
  { 0x0E,0x11,0x11,0x11,0x7F },  // 098  b
  { 0x11,0x11,0x11,0x11,0x0E },  // 099  c
  { 0x7F,0x11,0x11,0x11,0x0E },  // 100  d
  { 0x0C,0x15,0x15,0x15,0x0E },  // 101  e
  { 0x50,0x50,0x3F,0x10,0x10 },  // 102  f
  { 0x1E,0x15,0x15,0x15,0x08 },  // 103  g
  { 0x0F,0x10,0x10,0x08,0x7F },  // 104  h
  { 0x00,0x01,0x5f,0x11,0x00 },  // 105  i
  { 0x00,0x5e,0x11,0x01,0x02 },  // 106  j
  { 0x00,0x11,0x0a,0x04,0x7f },  // 107  k
  { 0x00,0x01,0x7f,0x41,0x00 },  // 108  l
  { 0x0F,0x10,0x1F,0x10,0x1F },  // 109  m
  { 0x0F,0x10,0x10,0x08,0x1F },  // 110  n
  { 0x0E,0x11,0x11,0x11,0x0E },  // 111  o
  { 0x08,0x14,0x14,0x14,0x1F },  // 112  p
  { 0x1F,0x14,0x14,0x14,0x08 },  // 113  q
  { 0x00,0x10,0x10,0x08,0x1f },  // 114  r
  { 0x12,0x15,0x15,0x15,0x09 },  // 115  s
  { 0x11,0x11,0x7E,0x10,0x10 },  // 116  t
  { 0x1F,0x02,0x01,0x01,0x1E },  // 117  u
  { 0x1C,0x02,0x01,0x02,0x1C },  // 118  v
  { 0x1E,0x01,0x06,0x01,0x1E },  // 119  w
  { 0x11,0x0A,0x04,0x0A,0x11 },  // 120  x
  { 0x1E,0x05,0x05,0x05,0x18 },  // 121  y
  { 0x11,0x19,0x15,0x13,0x11 },  // 122  z
  { 0x00,0x41,0x41,0x36,0x08 },  // 123  {
  { 0x00,0x00,0x77,0x00,0x00 },  // 124  |
  { 0x08,0x36,0x41,0x41,0x00 },  // 125  }
  { 0x08,0x1C,0x2A,0x08,0x08 },  // 126  ~
  { 0x08,0x08,0x2A,0x1C,0x08 }   // 127  left arrow   
};
#endif // SK_TEXT_SUPPORT

screenkeysTest.c a simple test rig

#include <avr/io.h>
#include "screenkeys.h"
#include "start.h"
#include "utility.h"


int main()
{
  SK_SWITCH( DDR) = IN;  // set SK switch pin to input
  SK_SWITCH( PORT) = ON;  // set SK switch pullup on

  sk_init();
  sk_set_color( SK_DARK_GREEN);
  sk_set_text( "Left", SK_TEXT_LEFT, SK_LINE_1);
  sk_set_text( "Right", SK_TEXT_RIGHT, SK_LINE_2);
  sk_set_text( "Cnt", SK_TEXT_CENTER, SK_LINE_3);

  uint8_t flag = 0;
  uint8_t btn_db = 0;
  while( TRUE)
  {
    // Button Debounce
    if( SK_SWITCH( PIN) == LOW)
    {
      if (btn_db < 0x4)
        btn_db ++;

      if (btn_db == 0x4)
      {
        btn_db = 0xFF;
        if( !flag)
        {
          sk_set_pixels( start_img, sizeof( start_img), 0);
          sk_set_text( "99", SK_TEXT_LEFT, SK_LINE_3);
          sk_set_color( SK_BRIGHT_BLUE);
          flag = 1;
        }
        else
        {
          sk_set_text( "1234", SK_TEXT_RIGHT, SK_LINE_1);
          sk_clear_text( SK_LINE_2);
          sk_set_color( SK_BRIGHT_RED);
          flag = 0;
        }
      }  
    }
    else
      btn_db = 0;
  }
}

start.h and stop.h are from the Sparkfun example code.

Hi everybody,

I received a RGB24 screenkey and an Arduino Pro Min (AT328 one) last month, and I allready tried out some of the provided sample code. Everything worked well til yesterday. I just tried to add a serial communication between the Arduino board and a laptop to my well working code. First I thought to a bad handling of interrupts (I’m not very skilled at low level coding), but after loading back and running my previous piece of code, the ScreenKey still remains in the same state:

at power on it displays a solid line at screen’s top, which stays for about half a second, and nothing else happens after that. I can’t change back-light colors or bitmap pattern anymore.

Has anyone experienced such a problem?

The only problem I see while using serial com. is the absence of clock signal on the ScreenKey, but only for a short time (= time to transfer 2 or 3 bytes at 9600kbps). Could it has burned the ScreenKey ASICS?

Thank you in advance for any help or advice.

Thomas