/* * ---------------------------------------------------------------------------- * "THE BEER-WARE LICENSE" (Revision 42): * wrote this file. As long as you retain this notice you * can do whatever you want with this stuff. If we meet some day, and you think * this stuff is worth it, you can buy me a beer in return. Joerg Wunsch * ---------------------------------------------------------------------------- * * HD44780 LCD display driver * * The LCD controller is used in 4-bit mode with a full bi-directional * interface (i.e. R/~W is connected) so the busy flag can be read. * * $Id$ */ #include "defines.h" #include #include #include #include #include "hd44780.h" #define GLUE(a, b) a##b /* single-bit macros, used for control bits */ #define SET_(what, p, m) GLUE(what, p) |= (1 << (m)) #define CLR_(what, p, m) GLUE(what, p) &= ~(1 << (m)) #define GET_(/* PIN, */ p, m) GLUE(PIN, p) & (1 << (m)) #define SET(what, x) SET_(what, x) #define CLR(what, x) CLR_(what, x) #define GET(/* PIN, */ x) GET_(x) /* nibble macros, used for data path */ #define ASSIGN_(what, p, m, v) GLUE(what, p) = (GLUE(what, p) & \ ~((1 << (m)) | (1 << ((m) + 1)) | \ (1 << ((m) + 2)) | (1 << ((m) + 3)))) | \ ((v) << (m)) #define READ_(what, p, m) (GLUE(what, p) & ((1 << (m)) | (1 << ((m) + 1)) | \ (1 << ((m) + 2)) | (1 << ((m) + 3)))) >> (m) #define ASSIGN(what, x, v) ASSIGN_(what, x, v) #define READ(what, x) READ_(what, x) #define HD44780_BUSYFLAG 0x80 /* * Send one pulse to the E signal (enable). Mind the timing * constraints. If readback is set to true, read the HD44780 data * pins right before the falling edge of E, and return that value. */ static inline uint8_t hd44780_pulse_e(bool readback) __attribute__((always_inline)); static inline uint8_t hd44780_pulse_e(bool readback) { uint8_t x; SET(PORT, HD44780_E); /* * Guarantee at least 500 ns of pulse width. For high CPU * frequencies, a delay loop is used. For lower frequencies, NOPs * are used, and at or below 1 MHz, the native pulse width will * already be 1 us or more so no additional delays are needed. */ #if F_CPU > 4000000UL _delay_us(0.5); #else /* * When reading back, we need one additional NOP, as the value read * back from the input pin is sampled close to the beginning of a * CPU clock cycle, while the previous edge on the output pin is * generated towards the end of a CPU clock cycle. */ if (readback) __asm__ volatile("nop"); # if F_CPU > 1000000UL __asm__ volatile("nop"); # if F_CPU > 2000000UL __asm__ volatile("nop"); __asm__ volatile("nop"); # endif /* F_CPU > 2000000UL */ # endif /* F_CPU > 1000000UL */ #endif if (readback) x = READ(PIN, HD44780_D4); else x = 0; CLR(PORT, HD44780_E); return x; } /* * Send one nibble out to the LCD controller. */ static void hd44780_outnibble(uint8_t n, uint8_t rs) { CLR(PORT, HD44780_RW); if (rs) SET(PORT, HD44780_RS); else CLR(PORT, HD44780_RS); ASSIGN(PORT, HD44780_D4, n); (void)hd44780_pulse_e(false); } /* * Send one byte to the LCD controller. As we are in 4-bit mode, we * have to send two nibbles. */ void hd44780_outbyte(uint8_t b, uint8_t rs) { hd44780_outnibble(b >> 4, rs); hd44780_outnibble(b & 0xf, rs); } /* * Read one nibble from the LCD controller. */ static uint8_t hd44780_innibble(uint8_t rs) { uint8_t x; SET(PORT, HD44780_RW); ASSIGN(DDR, HD44780_D4, 0x00); if (rs) SET(PORT, HD44780_RS); else CLR(PORT, HD44780_RS); x = hd44780_pulse_e(true); ASSIGN(DDR, HD44780_D4, 0x0F); CLR(PORT, HD44780_RW); return x; } /* * Read one byte (i.e. two nibbles) from the LCD controller. */ uint8_t hd44780_inbyte(uint8_t rs) { uint8_t x; x = hd44780_innibble(rs) << 4; x |= hd44780_innibble(rs); return x; } /* * Wait until the busy flag is cleared. */ void hd44780_wait_ready(bool longwait) { #if USE_BUSY_BIT while (hd44780_incmd() & HD44780_BUSYFLAG) ; #else if (longwait) _delay_ms(1.52); else _delay_us(37); #endif } /* * Initialize the LCD controller. * * The initialization sequence has a mandatory timing so the * controller can safely recognize the type of interface desired. * This is the only area where timed waits are really needed as * the busy flag cannot be probed initially. */ void hd44780_init(void) { SET(DDR, HD44780_RS); SET(DDR, HD44780_RW); SET(DDR, HD44780_E); ASSIGN(DDR, HD44780_D4, 0x0F); _delay_ms(15); /* 40 ms needed for Vcc = 2.7 V */ hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0); _delay_ms(4.1); hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0); _delay_ms(0.1); hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0); _delay_us(37); hd44780_outnibble(HD44780_FNSET(0, 1, 0) >> 4, 0); hd44780_wait_ready(false); hd44780_outcmd(HD44780_FNSET(0, 1, 0)); hd44780_wait_ready(false); hd44780_outcmd(HD44780_DISPCTL(0, 0, 0)); hd44780_wait_ready(false); } /* * Prepare the LCD controller pins for powerdown. */ void hd44780_powerdown(void) { ASSIGN(PORT, HD44780_D4, 0); CLR(PORT, HD44780_RS); CLR(PORT, HD44780_RW); CLR(PORT, HD44780_E); }