--- /dev/null
+/*
+ATMega pinout (with LCD_SHIFTREG):
+B0 - HD44780 RS
+B1 - HD44780 clock
+B2 - shift reg data
+B3 - shift reg clock
+
+ATMega pinout (without LCD_SHIFTREG):
+D4 - HD44780 RS
+D5 - HD44780 clock
+D6 - HD44780 data 0
+D7 - HD44780 data 1
+B0 - HD44780 data 2
+B1 - HD44780 data 3
+B2 - HD44780 data 4
+B3 - HD44780 data 5
+B4 - HD44780 data 6
+B5 - HD44780 data 7
+
+In both cases, HD44780 R/W needs to be connected to ground.
+*/
+#include <avr/io.h>
+#include "delay.h"
+#include "lcd.h"
+#include "timer.h"
+
+#ifdef LCD_SHIFTREG
+#define CLOCK_PORT PORTB
+#define CLOCK_BIT 0x02
+#define REGSEL_PORT PORTB
+#define REGSEL_BIT 0x01
+#else
+#define CLOCK_PORT PORTD
+#define CLOCK_BIT 0x20
+#define REGSEL_PORT PORTD
+#define REGSEL_BIT 0x10
+#endif
+
+#ifndef LCD_BUFSIZE
+#define LCD_BUFSIZE 32
+#endif
+
+#define NOP() __asm__("nop")
+
+#ifdef LCD_ASYNC
+static volatile uint8_t lcd_busy = 0;
+static uint8_t lcd_buffer[LCD_BUFSIZE];
+static volatile uint8_t lcd_buf_head = 0;
+static volatile uint8_t lcd_buf_tail = 0;
+#endif
+
+static void lcd_clock(void)
+{
+ delay_us(1);
+ CLOCK_PORT |= CLOCK_BIT;
+ delay_us(1);
+ CLOCK_PORT &= ~CLOCK_BIT;
+}
+
+static void lcd_set_data(uint8_t d)
+{
+#ifdef LCD_SHIFTREG
+ uint8_t i;
+ for(i=0; i<8; ++i)
+ {
+ PORTB = (PORTB&~0x04)|((d&1)<<2);
+ PORTB |= 0x08;
+ NOP();
+ PORTB &= ~0x08;
+ d >>= 1;
+ }
+#else
+ PORTD = (PORTD&0x3F) | (d<<6);
+ PORTB = (PORTB&0xC0) | (d>>2);
+#endif
+}
+
+static void lcd_command(uint8_t c)
+{
+ REGSEL_PORT &= ~REGSEL_BIT;
+ lcd_set_data(c);
+ lcd_clock();
+}
+
+static void lcd_delay(uint16_t us)
+{
+#ifdef LCD_ASYNC
+ lcd_busy = 1;
+ timer_start_us(0, us);
+#else
+ delay_us(us);
+#endif
+}
+
+#ifdef LCD_ASYNC
+static void lcd_wait(void)
+{
+ while(lcd_busy) ;
+}
+
+static void lcd_buffer_push(uint8_t byte)
+{
+ while((lcd_buf_head+1)%sizeof(lcd_buffer)==lcd_buf_tail) ;
+ lcd_buffer[lcd_buf_head] = byte;
+ lcd_buf_head = (lcd_buf_head+1)%sizeof(lcd_buffer);
+}
+
+static uint8_t lcd_buffer_pop(void)
+{
+ uint8_t byte = lcd_buffer[lcd_buf_tail];
+ lcd_buf_tail = (lcd_buf_tail+1)%sizeof(lcd_buffer);
+ return byte;
+}
+
+static void lcd_timer(void)
+{
+ if(lcd_busy)
+ {
+ if(lcd_buf_head!=lcd_buf_tail)
+ {
+ uint8_t byte = lcd_buffer_pop();
+ if(byte==0xFF)
+ {
+ REGSEL_PORT |= REGSEL_BIT;
+ byte = lcd_buffer_pop();
+ }
+ else if(byte>=0x80)
+ REGSEL_PORT &= ~REGSEL_BIT;
+ else
+ REGSEL_PORT |= REGSEL_BIT;
+ lcd_set_data(byte);
+ lcd_clock();
+ lcd_delay(50);
+ }
+ else
+ {
+ lcd_busy = 0;
+ timer_stop(0);
+ }
+ }
+}
+
+TIMER_SET_CALLBACK(0, lcd_timer)
+#endif
+
+void lcd_init(void)
+{
+#ifdef LCD_SHIFTREG
+ DDRB |= 0x0F;
+ PORTB &= ~0x0F;
+#else
+ DDRD |= 0xF0;
+ PORTD &= ~0xF0;
+ DDRB |= 0x3F;
+ PORTB &= ~0x3F;
+#endif
+
+ delay_ms(20);
+ lcd_command(0x38);
+ delay_us(50);
+ lcd_command(0x0C);
+ delay_us(50);
+}
+
+void lcd_clear(void)
+{
+#ifdef LCD_ASYNC
+ lcd_wait();
+#endif
+ lcd_command(0x01);
+ lcd_delay(1700);
+}
+
+void lcd_gotoxy(uint8_t x, uint8_t y)
+{
+ uint8_t a = x+10*y;
+ if(y&1)
+ a += 54;
+
+#ifdef LCD_ASYNC
+ if(lcd_busy)
+ {
+ lcd_buffer_push(a|0x80);
+ return;
+ }
+#endif
+
+ lcd_command(a|0x80);
+ lcd_delay(50);
+}
+
+void lcd_write(uint8_t c)
+{
+#ifdef LCD_ASYNC
+ if(lcd_busy)
+ {
+ if(c>=0x80)
+ lcd_buffer_push(0xFF);
+ lcd_buffer_push(c);
+ return;
+ }
+#endif
+
+ REGSEL_PORT |= REGSEL_BIT;
+ lcd_set_data(c);
+ lcd_clock();
+ lcd_delay(50);
+}