/*
 *  AVRLCD1A.c
 *  AVR-LCD Version 1a, built 27.07.2001
 *
 *  by Christoph Sommer (DeltaDev@GMX.net; http://www.deltadevelopment.de)
 *
 *  controls a 2x16 LCD (HD 44780 compatible)
 *    as sold by Electronic Assembly
 *
 *  tested with a LCD labelled PHILIPS LTN211R-10 QJK317H
 */

// --- Interface ------------------------------------------------------------

/*
 * Usage: initPort(); initLCD(); sendText(string);
 */

// --- Includes -------------------------------------------------------------

#include <io.h>

// --- LCD constants --------------------------------------------------------

// Lines start at these DDRAM addresses
#define DDRAM_LINE1		0x00
#define DDRAM_LINE2		0x40

// --- LCD control codes ----------------------------------------------------

// for reading in instruction mode
#define DATA_IN_ACMASK		(0<<7) | (1<<6) | (1<<5) | (1<<4) | (1<<3) | (1<<2) | (1<<1) | (1<<0) // Address counter = (byte & DATA_IN_MASK)
#define DATA_IN_BUSY		(1<<7) // Busy flag set by LCD

// for writing in instruction mode
#define DATA_OUT_CLEAR			(1<<0)
#define DATA_OUT_HOME			(1<<1)
#define DATA_OUT_ENTRYMODE		(1<<2)
#define DATA_OUT_ENTRYMODE_RIGHT	  (1<<1) // 0 = direction: left
#define DATA_OUT_ENTRYMODE_SHIFTDISPLAY	  (1<<0) // 0 = cursor moves
#define DATA_OUT_DISPLAY		(1<<3)
#define DATA_OUT_DISPLAY_ON		  (1<<2) // 0 = display off
#define DATA_OUT_DISPLAY_LINECURSOR	  (1<<1) // 0 = no _ cursor
#define DATA_OUT_DISPLAY_BLOCKCURSOR	  (1<<0) // 0 = no blinking block cursor
#define DATA_OUT_SHIFT			(1<<4)
#define DATA_OUT_SHIFT_DISPLAY		  (1<<3) // 0 = cursor
#define DATA_OUT_SHIFT_RIGHT		  (1<<2) // 0 = left
#define DATA_OUT_FSET			(1<<5)
#define DATA_OUT_FSET_8BIT		  (1<<4) // 0 = 4 bit datalength (pins 0-3 not connected, transmit high nibble first)
#define DATA_OUT_FSET_MULTILINE	  	  (1<<3) // 0 = one line
#define DATA_OUT_FSET_HIRES	  	  (1<<2) // 0 = 5x7 Dots (1 = 5x10 dots)
#define DATA_OUT_CGRAMGOTO		(1<<6) // bits 5-3: ASCII Code, bits 2-0 Line no
#define DATA_OUT_DDRAMGOTO		(1<<7) // bits 0-6: address

// --- hardware-dependent defines -------------------------------------------

// pin layout (PortD)
//
// on PORTD:
// 7654 3210
// ctrl data
// on LCD:
// _SWE 7654

#define CONTROL_DATAXCHANGE     (1<<4) // (E: Enable), rising: LCD may send data, falling: LCD may read data
#define CONTROL_READ            (1<<5) // (R/W: Read/Write toggle), lo: MCU is sending
#define CONTROL_RAMACCESS       (1<<6) // (RS: Register select), lo: Instrucions are being sent

#define dataGet() (inp(PIND) & 0x0F)
#define dataSet(x) outp((inp(PORTD) & 0xF0) | ((x) & 0x0F), PORTD)

#define controlSet(x) outp(inp(PORTD) | ((x) & 0xF0), PORTD)
#define controlClr(x) outp(inp(PORTD) & ( ~((x) & 0xF0) ), PORTD)
#define controlGet() (inp(PORTD) & 0xF0);
#define controlRestore(x) outp(((x) & 0xF0) | (inp(PORTD) & 0x0F), PORTD);

#define softwareDataread() outp(0xF0, DDRD); outp(inp(PORTD) & 0xF0, PORTD)
#define softwareDatawrite() outp(0xFF, DDRD); outp(inp(PORTD) & 0xF0, PORTD)

// --- Helper functions -----------------------------------------------------

void delayX(void) {
        int i,j,k;

        for (i=0; i<250; i++) {
                k=0;
                for (j=0; j<250; j++) {
                        k++;
                        }
                }

        }

// --- LCD base functions ---------------------------------------------------

// sends a byte to the LCD's DDRAM or CGRAM
void sendData (unsigned char dataByte) {
	unsigned int oldControl;

        oldControl = controlGet();

        // push high nibble to port and signal a data ready
	dataSet(dataByte >> 4);
        controlClr(CONTROL_DATAXCHANGE);
        controlSet(CONTROL_DATAXCHANGE);

        // push low nibble to port and signal a data ready
        dataSet(dataByte >> 0);
        controlClr(CONTROL_DATAXCHANGE);
        controlSet(CONTROL_DATAXCHANGE);

        // prepare for reading in instruction mode so LCD transmits busy flag
	softwareDataread();
        controlSet(CONTROL_READ);
        controlClr(CONTROL_RAMACCESS);

	// wait until busy flag is clear
        do {
                // receive high nibble and signal a "got it"
		dataByte = (dataGet() << 4);
                controlClr(CONTROL_DATAXCHANGE);
                controlSet(CONTROL_DATAXCHANGE);

                // receive low nibble and signal a "got it"
		dataByte |= (dataGet() << 0);
                controlClr(CONTROL_DATAXCHANGE);
                controlSet(CONTROL_DATAXCHANGE);

                delayX();
                } while (dataByte & DATA_IN_BUSY);

        // restore old state of pins
	controlRestore(oldControl);

        // switch back to writing
        controlClr(CONTROL_READ);
	softwareDatawrite();
	}

void sendText (char* msg) {
	for (;*msg;msg++) sendData(*msg);
	}

void initLCDPort (void) {
        outp(0xFF, DDRD); // all Ports are output
        outp(0x00, PORTD); // take all bits low
	}

void initLCD (void) {
        // prepare for writing in INSTRUCTION mode
	softwareDatawrite();
        dataSet(0x0);
        controlSet(CONTROL_DATAXCHANGE);
        controlClr(CONTROL_READ);
        controlClr(CONTROL_RAMACCESS);

	// this is highly experimental:

        // transmit high nibble of "8bit mode" three times
        dataSet((DATA_OUT_FSET | DATA_OUT_FSET_8BIT) >> 4); controlClr(CONTROL_DATAXCHANGE); controlSet(CONTROL_DATAXCHANGE); 
        delayX();
        dataSet((DATA_OUT_FSET | DATA_OUT_FSET_8BIT) >> 4); controlClr(CONTROL_DATAXCHANGE); controlSet(CONTROL_DATAXCHANGE); 
        delayX();
        dataSet((DATA_OUT_FSET | DATA_OUT_FSET_8BIT) >> 4); controlClr(CONTROL_DATAXCHANGE); controlSet(CONTROL_DATAXCHANGE); 
        delayX();
        // transmit high nibble of "4bit mode"
        dataSet((DATA_OUT_FSET) >> 4); controlClr(CONTROL_DATAXCHANGE); controlSet(CONTROL_DATAXCHANGE); 
        delayX();

	// set display parameters
	sendData(DATA_OUT_FSET |
          0*DATA_OUT_FSET_8BIT |
	  1*DATA_OUT_FSET_MULTILINE |
	  0*DATA_OUT_FSET_HIRES);
	sendData(DATA_OUT_DISPLAY |
	  1*DATA_OUT_DISPLAY_ON |
	  0*DATA_OUT_DISPLAY_LINECURSOR |
	  0*DATA_OUT_DISPLAY_BLOCKCURSOR);
	sendData(DATA_OUT_CLEAR);
	sendData(DATA_OUT_ENTRYMODE |
	  1*DATA_OUT_ENTRYMODE_RIGHT |
	  0*DATA_OUT_ENTRYMODE_SHIFTDISPLAY);
	sendData(DATA_OUT_DDRAMGOTO | DDRAM_LINE1);

	// leave instruction mode
        controlSet(CONTROL_RAMACCESS);
	}

