diff --git a/display.c b/display.c index b98b271..f757b16 100644 --- a/display.c +++ b/display.c @@ -23,6 +23,7 @@ volatile uint8_t displaybuf[BUFSIZE]; #define TEACUP_C_INCLUDE #include "display_ssd1306.c" + #include "display_hd44780.c" #undef TEACUP_C_INCLUDE diff --git a/display.h b/display.h index a1aed94..3330d08 100644 --- a/display.h +++ b/display.h @@ -23,6 +23,13 @@ #define DISPLAY + #elif defined DISPLAY_TYPE_HD44780 + + #define DISPLAY_LINES 2 + #define DISPLAY_SYMBOLS_PER_LINE 16 + + #define DISPLAY + #elif defined DISPLAY_TYPE_LCD1302 #error Display type LCD1302 not yet supported. diff --git a/display_hd44780.c b/display_hd44780.c new file mode 100644 index 0000000..6f4ce9f --- /dev/null +++ b/display_hd44780.c @@ -0,0 +1,124 @@ + +/** \file + + \brief Code specific to the HD44780 display. +*/ + +/** + TODO list: + + - Procedures like display_clear() and display_set_cursor() should be queued + up, too. Just like characters. Fonts start at 0x20, so 0x00..0x1F are + available for command sequences. For example, setting the cursor could + queue up 0x04 0x01 0x20 (3 bytes) to set the cursor to line 1, column 32. + 0x04 is the "command", bytes are queued up with display_writechar(). + + This is necessary to enforce characters and cursor commands to happen in + the right order. Currently, writing a few characters, moving the cursor + elsewhere and writing even more characters results in all characters + being written to the second position, because characters wait in the + queue, while cursor movements are executed immediately. + + Code currently in display_set_cursor() would move to display_tick(), then. +*/ + +#include "display.h" + +#if defined TEACUP_C_INCLUDE && defined DISPLAY_TYPE_HD44780 + +#include "displaybus.h" +#include "delay.h" +#include "sendf.h" +#include "dda.h" + + +/** + * Initializes the display's controller configuring the way of + * displaying data. + */ +void display_init(void) { + + // Minimum initialisation time after power up. + delay_ms(15); + + displaybus_init(0); + + // Write left to right, no display shifting. + displaybus_write(0x06, parallel_4bit_instruction); + // Display ON, cursor not blinking. + displaybus_write(0x0C, parallel_4bit_instruction); +} + +/** + Clear the screen. This display has a dedicated command for doing it. +*/ +void display_clear(void) { + displaybus_write(0x01, parallel_4bit_instruction); +} + +/** + Sets the cursor to the given position. + + \param line The vertical cursor position to set, in lines. First line is + zero. Line height is character height, which is currently + fixed to 8 pixels. + + \param column The horizontal cursor position to set, in pixels. First + column is zero. + + Use this for debugging purposes, only. Regular display updates happen in + display_clock(). +*/ +void display_set_cursor(uint8_t line, uint8_t column) { + // Currently unimplemented. +} + +/** + Show a nice greeting. Pure eye candy. +*/ +void display_greeting(void) { + + display_clear(); + + // We have only 16 characters at our disposal ... + display_writestr_P(PSTR("Welcome @ Teacup")); + + // Forward this to the display immediately. + while (buf_canread(display)) { + display_tick(); + } + + // Allow the user to worship our work for a moment :-) + delay_ms(5000); +} + +/** + Regular update of the display. Typically called once a second from clock.c. +*/ +void display_clock(void) { + + display_clear(); + + update_current_position(); + sendf_P(display_writechar, PSTR("X:%lq Y:%lq"), + current_position.axis[X], current_position.axis[Y]); +} + +/** + Forwards a character from the display queue to display bus. As this is a + character based display it's easy. +*/ +void display_tick() { + uint8_t data; + + if (displaybus_busy()) { + return; + } + + if (buf_canread(display)) { + buf_pop(display, data); + displaybus_write(data, parallel_4bit_data); + } +} + +#endif /* TEACUP_C_INCLUDE && DISPLAY_TYPE_HD44780 */ diff --git a/displaybus.h b/displaybus.h index 574bd47..aa7db62 100644 --- a/displaybus.h +++ b/displaybus.h @@ -24,7 +24,35 @@ #if defined DISPLAY_BUS_4BIT - #error Display connected directly via 4 pins is not yet supported. + #include "parallel-4bit.h" + + static void displaybus_init(uint8_t address) __attribute__ ((always_inline)); + inline void displaybus_init(uint8_t address) { + return parallel_4bit_init(); + } + + static uint8_t displaybus_busy(void) __attribute__ ((always_inline)); + inline uint8_t displaybus_busy(void) { + return parallel_4bit_busy(); + } + + /** + Note: we use 'rs' here to decide wether to send data or a command. Other + buses, like I2C, make no such distinction, but have last_byte + instead. This works, because there is currently no display supported + which can use both, I2C and the 4-bit bus. + + In case such support is wanted, displaybus_write() likely needs to + take both parameters. The actually best solution will be seen better + if such a display actually appears. + */ + static void displaybus_write(uint8_t data, enum rs_e rs) \ + __attribute__ ((always_inline)); + inline void displaybus_write(uint8_t data, enum rs_e rs) { + return parallel_4bit_write(data, rs); + } + + #define DISPLAY_BUS #elif defined DISPLAY_BUS_8BIT diff --git a/parallel-4bit.c b/parallel-4bit.c new file mode 100644 index 0000000..7a4ef03 --- /dev/null +++ b/parallel-4bit.c @@ -0,0 +1,206 @@ + +/** \file + \brief Parallel 4-bit subsystem + + This implements this custom interface often seen on LCD displays which + uses 4 data lines (D0..D3) and 3 control lines: + + RS = Register Select: High for data input, Low for instruction input. + + RW = Read/Write: High for read, Low for write. + + E = Enable: A line for triggering a read or a write. Its detailed + usage is a bit complicated. + + This implementation was written in the hope to be exchangeable with using + I2C or SPI as display bus for the same display. + + Other than the I2C implementation, this one uses no send buffer. Bytes can + be sent quickly enough to allow sending them immediately. +*/ + +#include "parallel-4bit.h" + +#ifdef DISPLAY_BUS_4BIT + +#include "delay.h" +#include "pinio.h" + +// Check for the necessary pins. +#ifndef DISPLAY_RS_PIN + #error DISPLAY_BUS_4BIT defined, but not DISPLAY_RS_PIN. +#endif +#ifndef DISPLAY_RW_PIN + #error DISPLAY_BUS_4BIT defined, but not DISPLAY_RW_PIN. +#endif +#ifndef DISPLAY_E_PIN + #error DISPLAY_BUS_4BIT defined, but not DISPLAY_E_PIN. +#endif +#ifndef DISPLAY_D4_PIN + #error DISPLAY_BUS_4BIT defined, but not DISPLAY_D4_PIN. +#endif +#ifndef DISPLAY_D5_PIN + #error DISPLAY_BUS_4BIT defined, but not DISPLAY_D5_PIN. +#endif +#ifndef DISPLAY_D6_PIN + #error DISPLAY_BUS_4BIT defined, but not DISPLAY_D6_PIN. +#endif +#ifndef DISPLAY_D7_PIN + #error DISPLAY_BUS_4BIT defined, but not DISPLAY_D7_PIN. +#endif + + +static void e_pulse(void) { + WRITE(DISPLAY_E_PIN, 1); + delay_us(1); + WRITE(DISPLAY_E_PIN, 0); +} + +/** + Inititalise the subsystem. +*/ +void parallel_4bit_init(void) { + + SET_OUTPUT(DISPLAY_RS_PIN); + WRITE(DISPLAY_RS_PIN, 0); + + SET_OUTPUT(DISPLAY_RW_PIN); + WRITE(DISPLAY_RW_PIN, 0); + + SET_OUTPUT(DISPLAY_E_PIN); + WRITE(DISPLAY_E_PIN, 0); + + /** + Perform a reset. + */ + SET_OUTPUT(DISPLAY_D4_PIN); + SET_OUTPUT(DISPLAY_D5_PIN); + SET_OUTPUT(DISPLAY_D6_PIN); + SET_OUTPUT(DISPLAY_D7_PIN); + + // Initial write is 8 bit. + WRITE(DISPLAY_D4_PIN, 1); + WRITE(DISPLAY_D5_PIN, 1); + WRITE(DISPLAY_D6_PIN, 0); + WRITE(DISPLAY_D7_PIN, 0); + e_pulse(); + delay_ms(5); // Delay, busy flag can't be checked here. + + // Repeat last command. + e_pulse(); + delay_us(100); // Delay, busy flag can't be checked here. + + // Repeat last command a third time. + e_pulse(); + delay_us(100); // Delay, busy flag can't be checked here. + + // Now configure for 4 bit mode. + WRITE(DISPLAY_D4_PIN, 0); + e_pulse(); + delay_us(100); // Some displays need this additional delay. +} + +/** + Read a byte from the bus. Doing so is e.g. required to detect wether the + display is busy. + + \param rs 1: Read data. + 0: Read busy flag / address counter. + + \return Byte read from LCD controller. +*/ +static uint8_t parallel_4bit_read(uint8_t rs) { + uint8_t data; + + if (rs) + WRITE(DISPLAY_RS_PIN, 1); // Read data. + else + WRITE(DISPLAY_RS_PIN, 0); // Read busy flag. + WRITE(DISPLAY_RW_PIN, 1); // Read mode. + + SET_INPUT(DISPLAY_D4_PIN); + SET_INPUT(DISPLAY_D5_PIN); + SET_INPUT(DISPLAY_D6_PIN); + SET_INPUT(DISPLAY_D7_PIN); + + data = 0; + + // Read high nibble. + WRITE(DISPLAY_E_PIN, 1); + delay_us(1); + if (READ(DISPLAY_D4_PIN)) data |= 0x10; + if (READ(DISPLAY_D5_PIN)) data |= 0x20; + if (READ(DISPLAY_D6_PIN)) data |= 0x40; + if (READ(DISPLAY_D7_PIN)) data |= 0x80; + WRITE(DISPLAY_E_PIN, 0); + + delay_us(1); + + // Read low nibble. + WRITE(DISPLAY_E_PIN, 1); + delay_us(1); + if (READ(DISPLAY_D4_PIN)) data |= 0x01; + if (READ(DISPLAY_D5_PIN)) data |= 0x02; + if (READ(DISPLAY_D6_PIN)) data |= 0x04; + if (READ(DISPLAY_D7_PIN)) data |= 0x08; + WRITE(DISPLAY_E_PIN, 0); + + return data; +} + +/** + Report wether the bus or the connected display is busy. + + \return Wether the bus is busy, which means that eventual new transactions + would have to wait. +*/ +uint8_t parallel_4bit_busy(void) { + uint8_t status; + + status = parallel_4bit_read(0); + + return status & 0x80; +} + +/** + Send a byte to the bus partner. + + \param data The byte to be sent. + + \param rs 1 = parallel_4bit_data: Write data. + 0 = parallel_4bit_instruction: Write instruction. + + Other than other bus implementations we do not buffer here. Writing a byte + takes just some 3 microseconds and there is nothing supporting such writes in + hardware, so the overhead of buffering is most likely not worth the effort. +*/ +void parallel_4bit_write(uint8_t data, enum rs_e rs) { + + // Wait for the display to become ready. + while (parallel_4bit_busy()); + + // Setup for writing. + WRITE(DISPLAY_RS_PIN, rs); // Write data / instruction. + WRITE(DISPLAY_RW_PIN, 0); // Write mode. + + SET_OUTPUT(DISPLAY_D4_PIN); + SET_OUTPUT(DISPLAY_D5_PIN); + SET_OUTPUT(DISPLAY_D6_PIN); + SET_OUTPUT(DISPLAY_D7_PIN); + + // Output high nibble. + WRITE(DISPLAY_D4_PIN, (data & 0x10) ? 1 : 0); + WRITE(DISPLAY_D5_PIN, (data & 0x20) ? 1 : 0); + WRITE(DISPLAY_D6_PIN, (data & 0x40) ? 1 : 0); + WRITE(DISPLAY_D7_PIN, (data & 0x80) ? 1 : 0); + e_pulse(); + + // Output low nibble. + WRITE(DISPLAY_D4_PIN, (data & 0x01) ? 1 : 0); + WRITE(DISPLAY_D5_PIN, (data & 0x02) ? 1 : 0); + WRITE(DISPLAY_D6_PIN, (data & 0x04) ? 1 : 0); + WRITE(DISPLAY_D7_PIN, (data & 0x08) ? 1 : 0); + e_pulse(); +} + +#endif /* DISPLAY_BUS_4BIT */ diff --git a/parallel-4bit.h b/parallel-4bit.h new file mode 100644 index 0000000..affe987 --- /dev/null +++ b/parallel-4bit.h @@ -0,0 +1,18 @@ + +#ifndef _PARALLEL_4BIT_H +#define _PARALLEL_4BIT_H + +#include "config_wrapper.h" + + +enum rs_e { + parallel_4bit_instruction = 0, + parallel_4bit_data +}; + +void parallel_4bit_init(void); +uint8_t parallel_4bit_busy(void); +void parallel_4bit_write(uint8_t data, enum rs_e rs); + + +#endif /* _PARALLEL_4BIT_H */