LCD3Wire: a 3-wire driver for HD44780-based LCDs

Here is probably an extreme implementation of a 2-wire interface. I wrote this to really show the limitations of such a concept in a multi-pin environment.

Here is the code.

/*
Demo code to run lcd1602 with two io pins
Code runs on 1MIPS avr / gcc-avr

Parts required:
 - 1 mcu (avr for now but portable to ther chips) running at 1MIPS. Need adjustments to other frequencies
 - two pins (digital outs)
 - 4 rc filters: 2 x 1k resistors, 1 x 10k resistor, 1 x 100k resistor and 4x 110n capacitors
 - supports hd44780-compatible lcds, up to 4 lines. lcd in 4-bit mode, no reading of busy flag

Connection:
 - RC filters: from a mcu pin to a resistor and then capacitor to ground. a lcd pin is connected to the joint of the capacitor + resistor.
 - RS: connected to the ENABLE pin via a 1k/110n filter.
 - RW: tied to ground
 - ENABLE: connected to the mcu pin designated as LCD_EN. No rc filter
 - D0..3: not connected
 - D4: connected to the mcu pin designated as LCD_Ds
 - D5: connected to D4 via a 1k/110n filter
 - D6: connected to D4 via a 10k/110n filter
 - D7: connected to D4 via a 100k/110n filter

Disclaimer: no warranty of any kind made. Use at your own risk.
 */

#include <avr/io.h>						//gcc-avr

//pin configuration
#define LCD_EN			D8				//defined to D8
#define LCD_Ds			D9				//defined to D9
#define DLY_min			1				//delays to send D4/en. determined by your rc time constant
#define DLY_x			10ul			//delay multipliers. "ul" for possible overflow. 10x to match the 10x ratio for rc constants

#define LCD_RS			LCD_EN			//en / rs the same pin
#define DLY_D4			(DLY_min)		//delays to send D7. In cycles
#define DLY_D5			(DLY_D4 * DLY_x)	//delays to send D7. In cycles
#define DLY_D6			(DLY_D5 * DLY_x)	//delays to send D7. In cycles
#define DLY_D7			(DLY_D6 * DLY_x)	//delays to send D7. In cycles

typedef struct {
	unsigned char B0: 1;
	unsigned char B1: 1;
	unsigned char B2: 1;
	unsigned char B3: 1;
	unsigned char B4: 1;
	unsigned char B5: 1;
	unsigned char B6: 1;
	unsigned char B7: 1;
} BIT8_T;

//extend bit fields
#if defined (PORTA)
#define PORTAbits	(*(volatile BIT8_T *)&PORTA)
#define DDRAbits	(*(volatile BIT8_T *)&DDRA)
#define PINAbits	(*(volatile BIT8_T *)&PINA)
#endif

#if defined (PORTB)
#define PORTBbits	(*(volatile BIT8_T *)&PORTB)
#define DDRBbits	(*(volatile BIT8_T *)&DDRB)
#define PINBbits	(*(volatile BIT8_T *)&PINB)
#endif

#if defined (PORTC)
#define PORTCbits	(*(volatile BIT8_T *)&PORTC)
#define DDRCbits	(*(volatile BIT8_T *)&DDRC)
#define PINCbits	(*(volatile BIT8_T *)&PINC)
#endif

//helper functions. don't call directly
#define _DDR(id, pin)			DDR ## id ## bits.B ## pin
#define _PORT(id, pin)			PORT ## id ## bits.B ## pin

//port functions
#define DDR(pin)				_DDR(pin)
#define PORT(pin)				_PORT(pin)

//arduino compatibility
#define OUTPUT		1			//set a pin to output
#define INPUT		0			//set a pin to input
#define HIGH		1			//set a pin
#define LOW			0			//clear a pin
//pin macros
#define pinMode(pin, mode)		_DDR(pin) = (mode)
#define digitalWrite(pin, val)	_PORT(pin) = (val)
//pin definitions
#define D8	B, 0						//pin D8 on B.0
#define D9	B, 1						//pin D9 on B.1

#define NOP()		asm("NOP")

//set cursor to row/col
#define lcd_goto(row, col)	lcd_write(0, 0x80 | (row) | (col));
#define LCD_LINE0			0x00				//lcd display line0
#define LCD_LINE1			0x40				//lcd display line1
#define LCD_LINE3			0x14				//lcd display line2
#define LCD_LINE4			0x54				//lcd display line3

//delay routines
void lcd_delay(unsigned long cycles) {
	while (cycles--) NOP();
}

//write 8-bits to the lcd in 4-bit mode
void lcd_write(unsigned char rs, unsigned char dat) {
	//send the highest 4 bits. RS has the same rc filter as D6
	if (dat & 0x80) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D7);
	if (dat & 0x40) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D6);
	if (rs)         digitalWrite(LCD_RS, HIGH); else digitalWrite(LCD_EN, LOW);
	if (dat & 0x20) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D5);
	//like D4, EN has no rc filter
	if (dat & 0x10) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW);
	digitalWrite(LCD_EN, HIGH);
	//lcd_delay(DLY_D4);	in case you need it
	digitalWrite(LCD_EN, LOW);	//strobe out data

	//send the lowest 4 bits
	//send the highest 4 bits. RS has the same rc filter as D6
	if (dat & 0x08) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D7);
	if (dat & 0x04) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D6);
	if (rs)         digitalWrite(LCD_RS, HIGH); else digitalWrite(LCD_EN, LOW);
	if (dat & 0x02) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW); lcd_delay(DLY_D5);
	//like D4, EN has no rc filter
	if (dat & 0x01) digitalWrite(LCD_Ds, HIGH); else digitalWrite(LCD_Ds, LOW);
	digitalWrite(LCD_EN, HIGH);
	//lcd_delay(DLY_D4);	in case you need it
	digitalWrite(LCD_EN, LOW);	//strobe out data
}

//initialize the lcd
void lcd_init(void) {
	//set the pin modes
	pinMode(LCD_EN, OUTPUT);
	pinMode(LCD_Ds, OUTPUT);

	//initialize the pins
	digitalWrite(LCD_EN, LOW);
	digitalWrite(LCD_Ds, LOW);

	//send the initialize sequence
	lcd_write(0, 0x33);			//send 1 of 3 0b0011
	lcd_write(0, 0x32);			//send 2+3 of 3 0b0011, 4-bit mode
	lcd_write(0, 0x28);			//4bit 2 lines
	lcd_write(0, 0x01);			//clear display
	lcd_delay(1000);			//wait for 2ms - need additional work
	lcd_write(0, 0x0c);			//display on, cursor off, no blinking
}

//send string
void lcd_str(unsigned char *str) {
	while (*str) lcd_write(1, *str++);	//send display data
}

//ends lcd2wire driver

//demo code starts here
void setup(void) {
	lcd_init();
}

void loop(void) {
	//display 1st line
	lcd_goto(LCD_LINE0, 0);			//start in col 0
	lcd_str("abcdefg");

	//display 2nd line
	lcd_goto(LCD_LINE1, 2);			//start in col 2
	lcd_str("0123456");
}

//stylized main
int main(void)
{

    // Insert code

	setup();						//reset the mcu
	while(1) {
		loop();						//looping around
	}

	return 0;
}