Driving an LCD display directly with ATtiny

My local electronics shop Partco (arguably the best in Finland) had a great offer on 6-digit LCD displays. For 1€ a piece, I immediately bought one:

Once I had my hands on it, the reason for such a low price became apparent: There was no controller chip, only 50 pins and the knowledge that pin 1 was “common cathode” and the rest were for the segments. So I decided to see if I could get it work directly without a controller. And succeeded, read on to learn how!

After some googling, it became apparent that unlike LEDs, LCDs don’t like to have constant voltage applied over them, but instead need an AC source. They don’t draw current like LEDs, but instead work like capacitive planes, the segments with voltage difference between the segment and common cathode becoming visible. LCD segments get damaged if there is DC voltage for long periods of time, so the solution is to apply a square wave of about 100 Hz (some models like 30 Hz, others 200 Hz) to the common cathode and one in opposite phase to a segment to “light” it.

So if you use a MCU with 5V operating voltage to control the LCD, you first set the common cathode to 0V (ground) and a segment to 5V (VCC). After a while, you switch. This way, a segment experiences alternating voltages of +5V and -5V and does not get damaged. Finally, I found a great application note from Maxim that explained this really clearly and recommended 1k resistors for segments and none for the common cathode. I was good to go! Here’s a rough schematic:

Now all we need is a bit of code! I had a ATtiny2313 wired with a 12 MHz crystal, so I decided to use the 16-bit timer 1 which would be called 200 times a second to create a 100 Hz square wave. With a prescaler of 8, timer 1 gets incremented 1.5 million times a second, so with OCR1A set to 7 500 (625 * 12) we get what we want:

TCCR1B |= (1 << WGM12); // configure timer 1 for CTC mode
TIMSK |= (1 << OCIE1A); // enable CTC interrupt for timer 1

OCR1A = 625 * (F_CPU/1000000L); // 200 Hz with prescaler 8

sei(); //  enable global interrupts

TCCR1B |= (1 << CS11); // start timer 1 at clk/8

The timer routine itself is simple:

  1. First toggle the common anode at PD6
  2. If PD6 is high, a low value in pins PB0-PB2 will translate to a visible segment
  3. If PD6 is low, a high value in pins PB0-PB2 will translate to a visible segment
  4. Every 200 calls (once per second) increment “segments” variable

This basically gives me a binary counter from 0 to 7 with three segments:

ISR(TIMER1_COMPA_vect) { // 200 Hz timer to generate 100 Hz square wave
    static uint8_t cathode = 0, segments = 0, counter = 0;

    PORTD ^= (1<<PD6); // toggle cathode voltage
    cathode = !cathode; // toggle cathode indicator

    if(cathode == 0) { // cathode at 0V - high bits visible
        // segment bit LO/HI -> pin LO/HI
        PORTB = (segments & SEG_PINS) | (PORTB & ~SEG_PINS);
    } else {
        // segment bit LO/HI -> pin HI/LO
        PORTB = (~segments & SEG_PINS) | (PORTB & ~SEG_PINS);


    if(counter >= 200) {
        counter = 0;

Quick check with an oscilloscope shows that PD6 and PBx really toggle between same and opposing phase (here’s a moment when they are in opposing phase, “LCD on” – note that I’m using 1:10 probes here):

And what do you know: It actually works! I had to use some pin headers to avoid sinking the LCD too firmly to my breadboard.

It was a nice hack, but using 50 pins of a microcontroller or alternatively something like 7 shift registers seemed an overkill. But it’s good to know how an LCD works in any case. Here’s the complete source file if you want to try it out (at your own risk of course :).

4 Comments or trackbacks to Driving an LCD display directly with ATtiny

February 10, 2012 at 23:06

I’d change that “(at your own responsibility of course :)” to “(at your own risk of course :)”. ;)

Nice hack. :)


February 11, 2012 at 1:59

@Tuhnu Thanks. Good idea, that sounds better. :)


Leave a Reply

Your e-mail address will not be published.

− 2 = zero

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>