LCD, 4 bit data mode
LCD driving using 4 bit data mode to reduce the GPIO count
/* note: lcd model MC21605A6W */
/* note: DB0:3 and RW must be grounded */
/* note: see https://github.com/texane/lcmeter for usage */
#include <stdint.h>
#include <avr/io.h>
#define LCD_POS_DB 0x02
#define LCD_PORT_DB PORTD
#define LCD_DIR_DB DDRD
#define LCD_MASK_DB (0x0f << LCD_POS_DB)
#define LCD_POS_EN 0x06
#define LCD_PORT_EN PORTD
#define LCD_DIR_EN DDRD
#define LCD_MASK_EN (0x01 << LCD_POS_EN)
#define LCD_POS_RS 0x07
#define LCD_PORT_RS PORTD
#define LCD_DIR_RS DDRD
#define LCD_MASK_RS (0x01 << LCD_POS_RS)
static inline void wait_50_ns(void)
{
__asm__ __volatile__ ("nop\n\t");
}
static inline void wait_500_ns(void)
{
/* 8 cycles at 16mhz */
__asm__ __volatile__ ("nop\n\t");
__asm__ __volatile__ ("nop\n\t");
__asm__ __volatile__ ("nop\n\t");
__asm__ __volatile__ ("nop\n\t");
__asm__ __volatile__ ("nop\n\t");
__asm__ __volatile__ ("nop\n\t");
__asm__ __volatile__ ("nop\n\t");
__asm__ __volatile__ ("nop\n\t");
}
static inline void wait_50_us(void)
{
/* 800 cycles at 16mhz */
uint8_t x;
for (x = 0; x < 100; ++x) wait_500_ns();
}
static inline void wait_2_ms(void)
{
wait_50_us();
wait_50_us();
wait_50_us();
wait_50_us();
}
static inline void wait_50_ms(void)
{
/* FIXME: was _delay_ms(50), but not working */
uint8_t x;
for (x = 0; x < 25; ++x) wait_2_ms();
}
static inline void lcd_pulse_en(void)
{
/* assume EN low */
LCD_PORT_EN |= LCD_MASK_EN;
wait_50_us();
LCD_PORT_EN &= ~LCD_MASK_EN;
wait_2_ms();
}
static void lcd_write_db4(uint8_t x)
{
/* configured in 4 bits mode */
LCD_PORT_DB &= ~LCD_MASK_DB;
LCD_PORT_DB |= (x >> 4) << LCD_POS_DB;
lcd_pulse_en();
LCD_PORT_DB &= ~LCD_MASK_DB;
LCD_PORT_DB |= (x & 0xf) << LCD_POS_DB;
lcd_pulse_en();
}
static void lcd_write_db8(uint8_t x)
{
/* configured in 8 bits mode */
/* only hi nibble transmitted, (0:3) grounded */
LCD_PORT_DB &= ~LCD_MASK_DB;
LCD_PORT_DB |= (x >> 4) << LCD_POS_DB;
lcd_pulse_en();
}
/* exported interface */
void lcd_setup(void)
{
LCD_DIR_DB |= LCD_MASK_DB;
LCD_DIR_RS |= LCD_MASK_RS;
LCD_DIR_EN |= LCD_MASK_EN;
LCD_PORT_DB &= ~LCD_MASK_DB;
LCD_PORT_RS &= ~LCD_MASK_RS;
LCD_PORT_EN &= ~LCD_MASK_EN;
/* small delay for the lcd to boot */
wait_50_ms();
/* datasheet init sequence */
#define LCD_MODE_BLINK (1 << 0)
#define LCD_MODE_CURSOR (1 << 1)
#define LCD_MODE_DISPLAY (1 << 2)
lcd_write_db8(0x30);
wait_2_ms();
wait_2_ms();
wait_500_ns();
lcd_write_db8(0x30);
wait_2_ms();
lcd_write_db4(0x32);
wait_2_ms();
lcd_write_db4(0x28);
wait_2_ms();
lcd_write_db4((1 << 3) | LCD_MODE_DISPLAY);
wait_2_ms();
lcd_write_db4(0x01);
wait_2_ms();
lcd_write_db4(0x0f);
wait_2_ms();
}
void lcd_clear(void)
{
/* clear lcd */
lcd_write_db4(0x01);
wait_2_ms();
}
void lcd_home(void)
{
/* set cursor to home */
lcd_write_db4(0x02);
wait_2_ms();
}
void lcd_set_ddram(uint8_t addr)
{
lcd_write_db4((1 << 7) | addr);
wait_2_ms();
}
void lcd_goto_xy(uint8_t x, uint8_t y)
{
/* assume 0 <= x < 8 */
/* assume 0 <= y < 2 */
/* from datasheet: */
/* first line is 0x00 to 0x27 */
/* second line is 0x40 to 0x67 */
static const uint8_t row[] = { 0x00, 0x40 };
lcd_set_ddram(row[y] | x);
}
void lcd_write(const uint8_t* s, unsigned int n)
{
wait_50_ns();
LCD_PORT_RS |= LCD_MASK_RS;
for (; n; --n, ++s)
{
lcd_write_db4(*s);
wait_2_ms();
}
LCD_PORT_RS &= ~LCD_MASK_RS;
}