Siemens A60 LCD and Linux

Siemens A60 LCD

Here's a bit of info and an application for the display from a Siemens A60 (also shared with A65, M55, C60, MC60 and S55 models). This is a nice self-contained module with built in backlight, and connection is made via a row of nice, large easy-to-solder-to gold pads, like the Nokia 3510i. The image resolution is 101 columns by 80 lines.

Having the backlight built in eliminates any problems with uneven illumination which will inevitably happen when trying to backlight transflective LCDs like the 3510's, unless you're very patient to get the backlight diffusion just right. This display uses two white LEDs which are connected via the pads.

As with the 3510i display, there is a driver chip mounted on the back of the LCD. This includes the LCD bias supply, the scanning logic and enough SRAM for a frame buffer. All that's required to make it work is a power supply and a source of data. Image data is sent to the display via a special 4-wire synchronous serial interface.

This display works on a 2.9V supply, which by happy coincidence is almost exactly the same as the forward voltage of each of the backlight LEDs. This means that one of the LEDs can be used as a simple shunt regulator if you don't happen to have a 2.9V rail.

The pinout is (from the pad nearest the edge of the plastic frame): -

  1. Chip Select (pull low to enable)
  2. Reset (pull low to reset)
  3. Register Select (low = command, hi = data)
  4. Serial Clock (serial data is latched on the rising edge)
  5. Serial Data
  6. Logic Supply (+2.9V as measured in the donor A60 cell phone)
  7. Logic Ground
  8. Backlight Anode 1
  9. Backlight Cathodes
  10. Backlight Anode 2

Driving from a PC under Linux

The display is low powered enough to mean that it can be bus powered from a PC parallel port. The code listed below will enable raw image data to be streamed to the display via the parallel port. This might be useful to make a handy self-contained status indicator for a Linux box - e.g. a headless MythTV backend.

Connect the LCD data lines to the parallel port via resistors to limit the current in the IC's clamp diodes. I used 12K resistors, but this might be a bit too high for reliable operation. 4K7 might be a better choice, depending on the specific parallel port.

Connect the anodes of the LCD backlight LEDs to the parallel port via lower value resistors. I used 620ohms for a reasonable brightness. You may need to adjust this value.

The +2.9V logic supply is connected to one of the LED anodes. Logic ground and the LED common cathode are connected together and to the PC ground in the parallel port.

To use it, compile then execute, streaming in your raw image data. The program expects exactly 24240 bytes of data: triplets of unsigned bytes representing red, green and blue samples (in that order), 101 triplets per line, 80 lines in the image. This stream can be trivially effected under Linux with the ImageMagick tools, e.g. something like this: -

convert MyImage.JPG -resize 101x80\> -size 101x80 xc:black +swap \
  -gravity center -composite rgb:'-' | stream  -size 101x80 rgb:'-' \
  -storage-type char -map rgb '-' | ./a60-lcd
	

Two arguments are recognized: ./a60-lcd 0 (number zero) switches off the LCD, rendering it safe to disconnect and ./a60-lcd s skips the reset/initialisation sequence, possibly useful for slideshows or live status updates

Just copy the following into a text editor and save as a60-lcd.c then build by typing make a60-lcd from a terminal:

#include <stdio.h>
#include <stdlib.h>
#include <linux/ioctl.h>
#include <linux/parport.h>
#include <linux/ppdev.h>
#include <fcntl.h>
#include <time.h>

int fd;
int data = 0x7f;

/*
 * Linux tester for Siemens A60 LCD (A65, M55, C60, MC60, S55, etc)
 *
 * Connect signals to parallel port via ~10K resistors thus: -
 *
 * Parallel Port            A60-LCD
 *   2   D0     <--10K->     1  CS
 *   3   D1     <--10K->     2  Reset
 *   4   D2     <--10K->     3  RS
 *   5   D3     <--10K->     4  SCL
 *   6   D4     <--10K->     5  SDA
 *   7   D5     <-620R->     6  +2.9V *and* 8 A1
 *   8   D6     <-620R->    10  A2
 *  25   GND    <------>     7  GND   *and* 9 K
 *
 * LCD pad 1 is nearest the edge of the frame, pad 10 is nearest the
 * middle of the assembly.
 *
 * By coincidence, Vf of the backlight LEDs is approx 2.9V, thus
 * one can be used as a simple shunt regulator for the LCD supply.
 *
 * Remember, the series resistor limits the current in the shunt.
 * The series resistors in the signal lines limit the current in
 * the LCD's clamp diodes. Adjust values as necessary to suit the
 * specific parallel port.
 *
 * Make sure you have your distro's ppdev package installed.
 */

void Send (X)
{
    int mask;
    
    /* clock a byte out serially to the LCD */
    
    for (mask = 0x80; mask; mask >>= 1)
    //for (mask = 0x01; mask & 0x100; mask <<= 1)
    {
        if (mask & X)
            data |= 0x10;
        else
            data &= ~0x10;
        data &= ~0x08;
        ioctl (fd, PPWDATA, &data);
        //ioctl (fd, PPWDATA, &data);
        
        data |= 0x08;
        ioctl (fd, PPWDATA, &data);
        //ioctl (fd, PPWDATA, &data);
    }
}

void Initialise (void)
{
    int i, flag;
    int Sequence [] =
    {
        0xf4, 0x90, 0xb3, 0xa0, 0xd0, -1,
        0xf0, 0xe2, 0xd4, 0x70, 0x66, 0xb2, 0xba, 0xa1, 0xa3, 0xab, 
            0x94, 0x95, 0x95, 0x95, -1,
        0xf5, 0x90, -1,
        0xf1, 0x00, 0x10, 0x22, 0x30, 0x45, 0x50, 0x68, 0x70, 0x8a,
            0x90, 0xac, 0xb0, 0xce, 0xd0, -1,
        0xf2, 0x0f, 0x10, 0x20, 0x30, 0x43, 0x50, 0x66, 0x70, 0x89, 
            0x90, 0xab, 0xb0, 0xcd, 0xd0, -1,
        0xf3, 0x0e, 0x10, 0x2f, 0x30, 0x40, 0x50, 0x64, 0x70, 0x87,
            0x90, 0xaa, 0xb0, 0xcb, 0xd0, -1,
        0xf4, 0x0d, 0x10, 0x2e, 0x30, 0x4f, 0x50, -1,
        0xf5, 0x91, -1,
        0xf1, 0x01, 0x11, 0x22, 0x31, 0x43, 0x51, 0x64, 0x71, 0x86,
            0x91, 0xa8, 0xb1, 0xcb, 0xd1, -1,
        0xf2, 0x0f, 0x11, 0x21, 0x31, 0x42, 0x51, 0x63, 0x71, 0x85,
            0x91, 0xa6, 0xb1, 0xc8, 0xd1, -1,
        0xf3, 0x0b, 0x11, 0x2f, 0x31, 0x41, 0x51, 0x62, 0x71, 0x83,
            0x91, 0xa4, 0xb1, 0xc6, 0xd1, -1,
        0xf4, 0x08, 0x11, 0x2b, 0x31, 0x4f, 0x51, -1,
        0x80, 0x94, -1,
        0xf5, 0xa2, -1,
        0xf4, 0x60, -1,
        0xf0, 0x40, 0x50, 0xc0, -1,
        0xf4, 0x70, -1, -1,
        0xf0, 0x81, -1,
        0xf4, 0xb3, 0xa0, -1,
        0xf0, 0x00 | 0x6, 0x10, 0x20, 0x30, -1,
        0xf5, 0x00 | 0xf, 0x10 | 0xc, 0x20 | 0xf, 0x30 | 0x4, -1,
        -1, -1
    };

    /* power off */
    data = 0;
    ioctl (fd, PPWDATA, &data);
    usleep (500000);

    /* power on, reset */
    data = 0x7d;
    ioctl (fd, PPWDATA, &data);
    usleep (10000);
    
    /* reset off */
    data |= 0x02;
    ioctl (fd, PPWDATA, &data);
    usleep (10000);
    
    /* send commands */
    i = 0;
    data &= ~0x01;
    data |=  0x04;
    ioctl (fd, PPWDATA, &data);
    
    flag = 1;   
    do
    {
        Send (Sequence [i++]);
        
        if (Sequence [i] == -1)
        {
            data |= 0x01;
            ioctl (fd, PPWDATA, &data);
            usleep (1);
            
            data &= ~0x01;
            ioctl (fd, PPWDATA, &data);
            
            i++;
        }
        
        if (Sequence [i] == -1)
        {
            usleep (20000);
            i++;
        }
    } while (Sequence [i] != -1);
    
    /* select data register */
    data &= ~0x04;
    ioctl (fd, PPWDATA, &data); 
    /* good to go! */
}

void PowerOff (void)
{
    data = 0;
    ioctl (fd, PPWDATA, &data);
}

void SetWindow (int x, int y, int sx, int sy)
{
    /* bounds checking */
    sx += x;
    if (x < 0) x = 0;
    if (x > 100) x = 100;
    if (sx <= x) sx = x + 1;
    if (sx > 101) sx = 101;
    
    sy += y;
    if (y < 0) y = 0;
    if (y > 79) y = 79;
    if (sy <= y) sy = y + 1;
    if (sy > 80) sy = 80;
    
    /* send command */
    x = 2 * (3 + x);
    sx = 2 * (3 + sx) + 1;
    data |=  0x04;
    data &= ~0x01;
    ioctl (fd, PPWDATA, &data);
    
    Send (0xf0);
    Send (0x00 | (x & 0x0f));
    Send (0x10 | (x >> 4));
    Send (0x20 | (y & 0x0f));
    Send (0x30 | (y >> 4));
    
    Send (0xf5);
    Send (0x00 | (sx & 0x0f));
    Send (0x10 | (sx >> 4));
    Send (0x20 | (sy & 0x0f));
    Send (0x30 | (sy >> 4));
    
    data &= ~0x04;
    data |=  0x01;
    ioctl (fd, PPWDATA, &data);
}

int main (int argc, char *argv[])
{
    int i, j, k, demo;
    
    /* open the parallel port driver */
    fd = open ("/dev/parport0", O_RDWR);
    if (fd == -1)
    {
        perror ("open");
        exit (1);
    }

    if (ioctl (fd, PPCLAIM))
    {
        perror ("PPCLAIM");
        close (fd);
        exit (1);
    }

/*  mode = IEEE1284_MODE_COMPAT;
    if (ioctl (fd, PPNEGOT, &mode))
    {
        perror ("PPNEGOT");
        close (fd);
        exit (1);
    } */
    
    if (argc > 1 && argv[1][0] == '0')
    {
        PowerOff ();
        close (fd);
        exit (0);
    }

    if (argc < 2 || argv[1][0] != 's')
    {
        Initialise ();
    }
    
    demo = (argc > 1 && argv[1][0] == 'd');
    k = 0;
    do
    {
        for (i = 0; i < 80; i++)
        {
            SetWindow (0, i, 101, 1);
        
            data &= ~0x01;
            ioctl (fd, PPWDATA, &data);
            for (j = 0; j < 101; j++)
            {
                if (demo)
                {
                    int x, c;
                    x = ((i + k) ^ (j - k)) & 0xFF;
                    c = ((x >> 4) % 7) + 1;
                    x &= 0x0F;
            
                    Send  ((c & 0x01)? x: 0);
                    Send (((c & 0x02)? x << 4: 0) |
                          ((c & 0x04)? x: 0));
                }
                else
                {
                    int r, g, b;
                    r = getchar ();
                    g = getchar ();
                    b = getchar ();
                    Send  ((r >> 4) & 0x0F);
                    Send (((b >> 4) & 0x0F) |
                           (g & 0xF0));
                }
            }
            data |=  0x01;
            ioctl (fd, PPWDATA, &data);
        }
        k++;
        usleep (100000);
    } while (demo);
    
    close (fd);
    return 0;
}
	
Here's a little bash shell script which overlays some text onto a resized image:

#!/bin/sh

convert $1 -resize 101x80\> -size 101x80 xc:black +swap \
  -gravity center -composite \
  -fill red -stroke white  -font Comic-Sans-MS-Regular \
  -pointsize 16 -gravity south -annotate 0 $2 rgb:'-' | \
  stream  -size 101x80 rgb:'-' -storage-type char -map rgb '-' | \
  ./a60-lcd
	

Just run this with two arguments: the image and the message.

./a60-display.sh /usr/share/pixmaps/faces/penguin.jpg Meh
	

... will display ...

1