commit c884d37214f670d5b3f4dfcc1b30f81fdd42986c Author: H. Utku Maden Date: Mon Aug 22 14:34:04 2022 +0300 Initial commit. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b84f398 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register("lcd", + SRCS "src/lcd.c" + INCLUDE_DIRS "include" +) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..761d9eb --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +Generic LCD driver as a ESP32 Component +======================================= + +This is a generic LCD driver wrapped as a ESP32 component. You are free to use +the driver component as you wish as long as you follow the license agreement in +[LICENSE.md](LICENSE.md). + +Usage Example +------------- + +```c + +int lcdBusIO(lcdDriver_t *driver, bool rw, bool rs, bool en, uint8_t data) +{ + // Implement for your own setup. +} + +int lcdDelay(lcdDriver_t *driver, uint32_t delay) +{ + // Implement for your own setup. + return usleep((useconds_t)delay); +} + +void app_main(void) +{ + lcdDriver_t lcd = { + .userData = NULL, + .dimensions = {16, 2}, + .writeOnly = true, + .fourBits = true + }; + + // Do GPIO initialization. + + // Reset GPIO pins to their default state. + lcdBusIO(&lcd, false, false, false, 0xFF); + + lcdLoadDefaultTiming(&lcd); // Load default LCD timings. + lcdInit(&lcd); // Initialize the LCD and driver. + + lcdDirection(&lcd, true); // Set the direction to forward. + lcdSetDisplay(&lcd, true, false, false); // Enable display. + lcdHome(&lcd); // Home the LCD cursor. + + lcdPutZString(&driver, "Hello World!"); +} +``` + +TO-DO +----- +* Implement read-write mode. +* Implement 8-bit mode. \ No newline at end of file diff --git a/include/lcd.h b/include/lcd.h new file mode 100644 index 0000000..70583a4 --- /dev/null +++ b/include/lcd.h @@ -0,0 +1,478 @@ +#ifndef _LCD_H_ +#define _LCD_H_ + +/** + * @file lcd.h LCD driver header file. + * + * @mainpage Generic C99 LCD Driver. + */ + +#include +#include +#include +#include + +#if defined(__GNUC__) || defined(__clang__) +#define WEAK __attribute__((weak)) +#elif defined(_MSC_VER) +// Not really what I want but it is what I have. +#define WEAK __declspec(selectany) +#elif defined(__CC_ARM) +#define WEAK __weak +#elif +#warning Unable to infer weak symbol macro. +#define WEAK +#endif + +#ifndef EIO +#define EIO 5 +#endif + +// Raw command definitions. +/** Clear screen */ +#define LCD_CMD_CLEAR() (0x01) +/** Move cursor back to start, cancel display shift. */ +#define LCD_CMD_HOME() (0x02) +/** + * Set entry mode. + * @param dir Entry direction, true for right, false for left. + * @param s Shift enable. Display RAM contents are shifted in the direction on read or write. + */ +#define LCD_CMD_ENTRY(dir,s) (0x04 | ((!!(dir)) << 1) | (!!(s))) +/** + * Set display mode. + * @param f Disable or enable entire display. + * @param c Disable or enable underline cursor. + * @param b Disable or enable blinking block. + */ +#define LCD_CMD_DISPLAY(f,c,b) (0x08 | ((!!(f)) << 2) | ((!!(c)) << 1) | (!!(b))) +/** + * Move cursor or shift display. + * @param s Shift for true and move cursor for false. + * @param r True for right and false for left. + */ +#define LCD_CMD_CURSOR(s,r) (0x10 | ((!!(s)) << 3) | ((!!(r)) << 2)) +/** + * Function mode set. Initializes the display. + * @param b Bus width. false for 4bits and true for 8bits. + * @param n Display lines. false for one line and true for two lines. + * @param f Display font. false for small font (5x8) and true for large font (5x10) + */ +#define LCD_CMD_FUNCTION(b,n,f) (0x20 | ((!!(b)) << 4) | ((!!(n)) << 3) | ((!!(f)) << 2)) +/** + * Set character RAM pointer. + * @param addr Pointer into character RAM. + */ +#define LCD_CMD_CADDR(addr) (0x40 | (addr & 0x3F)) +/** + * Set display RAM pointer. + * @param addr Pointer into display RAM. + */ +#define LCD_CMD_DADDR(addr) (0x80 | (addr & 0x7F)) + +#define LCD_TIMING_ADDRESS_SETUP 10 +#define LCD_TIMING_ENABLE_HOLD 10 +#define LCD_TIMING_DATA_HOLD 10 +#define LCD_TIMING_BUSY_INTERVAL 50 +#define LCD_TIMING_BUSY_HOLD_SHORT 500 +#define LCD_TIMING_BUSY_HOLD_LONG 50000 + +/** + * Driver structure. + */ +typedef struct lcdDriver_t lcdDriver_t; + +/** + * Write read from the LCD bus. + * @param driver The driver structure calling, for convenience. + * @param rw Read/Write pin state. + * @param rs Register select pin state. + * @param en Enable pin state. + * @param data Data bus output, valid if rw is false. + * @return Non-negative if successful, value of data bus if rw is true, negative on error. + * @remarks + * The driver need not be initialized using the function pointers. Implement the weakly + * linked int lcdBusIO(lcdDriver_t*,bool,bool,bool,uint8_t) to handle IO without function + * pointers. + */ +typedef int (*lcdBusIOHandler_t)(lcdDriver_t* driver, bool rw, bool rs, bool en, uint8_t data); + +/** + * Suspend until at least delay microseconds. + * @param driver The driver structure calling, for convenience. + * @param delay Delay amount in microseconds. + * @return Zero if successful, non-zero for error. + * @remarks + * Precision is not critical, as long as at least delay microseconds pass. + * + * The driver need not be initialized using the function pointers. Implement the weakly + * linked int lcdDelay(lcdDriver_t*,uint32_t) to handle delay without function + * pointers. + */ +typedef int (*lcdDelayHandler_t)(lcdDriver_t* driver, uint32_t delay); + +/** + * The LCD driver structure.' + */ +struct lcdDriver_t +{ + int error; /** Last error number for LCD driver, using POSIX error numbers. */ + struct { + uint8_t width; /** Width of display. */ + uint8_t height; /** Height of display. */ + } dimensions; /** Display dimensions. */ + bool fourBits:1; /** Operate display in 4-bit mode. */ + bool writeOnly:1; /** Write only mode of operation. */ + bool largeFont:1; /** Use large font (5x10). */ + uint8_t padding0:5; /** Padding for flags */ + void *userData; /** Storage for your usage */ + lcdBusIOHandler_t busIO; /** LCD IO function handler, if not strongly linked. */ + lcdDelayHandler_t delay; /** Delay function used to ensure bus timing, if not strongly linked. */ + + + struct { + uint32_t addressSetup; /** Time to wait after setting up address lines. */ + uint32_t enableHold; /** Time to wait after setting enable high. */ + uint32_t dataHold; /** Time to wait after setting enable low. */ + uint32_t busyInterval; /** (read-write mode) Busy flag check interval. */ + uint32_t busyHoldShort; /** (write-only mode) Hold time after write, short version. */ + uint32_t busyHoldLong; /** (write-only mode) Hold time after write, long version. */ + } busTiming; /** Bus timing variables. Great for tuning for specific displays. See void lcdLoadDefaultTiming(lcdDriver_t*). */ + + /* private to implementation, modify at your own risk. */ + struct { + int8_t x; + int8_t y; + } cursor; + bool direction:1; + uint8_t padding1:7; +}; + +/** + * Function to control the LCD bus. + * @param driver The driver structure. + * @param rw Read-Write state. + * @param rs Register Select. + * @param en Enable state. + * @param data Data pins. + * @return Non-zero when an error occurs. Updates errno. + * @remarks This function is declared weak. Redefine the function to implement + * a custom bus controller. By default, it will try to call the related function + * pointer in the driver structure. + */ +int WEAK lcdBusIO(lcdDriver_t *driver, bool rw, bool rs, bool en, uint8_t data); + +/** + * Function to delay execution in the driver. + * @param driver The driver structure. + * @param delay the amount to delay in microseconds. + * @return Non-negative on success, bus value if reading, negative on error. + * @remarks This function is declared weak. Redefine the function to implement + * a custom delay function. By default, it will try to call the related function + * pointer in the driver structure. The delay need not be exact, as long as the + * function returns after _at least_ the given delay value. + */ +int WEAK lcdDelay(lcdDriver_t *driver, uint32_t delay); + +/** + * Load default bus timings into the driver structure. + * @param driver The driver structure. + * @remarks See LCD_TIMING_* for default values. + */ +inline static void lcdLoadDefaultTiming(lcdDriver_t *driver) +{ + driver->busTiming.addressSetup = LCD_TIMING_ADDRESS_SETUP; + driver->busTiming.enableHold = LCD_TIMING_ENABLE_HOLD; + driver->busTiming.dataHold = LCD_TIMING_DATA_HOLD; + driver->busTiming.busyInterval = LCD_TIMING_BUSY_INTERVAL; + driver->busTiming.busyHoldShort = LCD_TIMING_BUSY_HOLD_SHORT; + driver->busTiming.busyHoldLong = LCD_TIMING_BUSY_HOLD_LONG; +} + +/** + * Initialize the LCD display. + * @param driver The driver structure. + * @return Non-zero if unsuccessful. + */ +int lcdInit(lcdDriver_t *driver); + +/** + * Write a command to the LCD bus. + * @param driver The driver structure. + * @param command The command byte. + * @return Non-zero if unsuccessful. + */ +int lcdCommand(lcdDriver_t *driver, uint8_t command); + +/** + * Write data to display or character RAM. + * @param driver The driver sturcure. + * @param data Data to write. + * @return Non-zero if unsuccessful. + */ +int lcdWrite(lcdDriver_t *driver, uint8_t data); + +/** + * Decode the cursor position in the driver. + * @param driver The driver structure. + * @return The address for the current position pointer. + * @remarks This macro is private to the driver. You should not need + * to use this. + */ +#define LCD_DECODE_CURSOR(driver)\ + ((driver)->cursor.x + /* Base position */\ + ((driver)->dimensions.height > 2) * /* If the display has more than 2 lines. */\ + ( \ + 64 * ((driver)->cursor.y % 2) + /* Add 64 if the row is even */\ + (driver)->dimensions.width * ((driver)->cursor.y >= 2) /* Add width if the row is the last two. */\ + ) \ + ) +// #include +// inline static uint8_t _LCD_DECODE_CURSOR(lcdDriver_t* driver) { +// uint8_t value = LCD_DECODE_CURSOR(driver); +// printf("%hhd, %hhd = %hhu\n", driver->cursor.x, driver->cursor.y, value); +// return value; +// } +// #undef LCD_DECODE_CURSOR +// #define LCD_DECODE_CURSOR(driver) _LCD_DECODE_CURSOR(driver) + +/** + * Update the LCD cursor in the driver. + * @param driver The driver structure. + * @remarks This is private to the driver implementation. You should not need to call + * this function on your own. + */ +inline static void lcdUpdateCursor(lcdDriver_t *driver) +{ + if (driver->direction) + { + driver->cursor.x++; + if (driver->cursor.x >= driver->dimensions.width) + { + driver->cursor.x = 0; + driver->cursor.y++; + if (driver->cursor.y >= driver->dimensions.height) + driver->cursor.y = 0; + } + } + else + { + driver->cursor.x--; + if (driver->cursor.x < 0) + { + driver->cursor.x = driver->dimensions.width - 1; + driver->cursor.y--; + if (driver->cursor.y < 0) + driver->cursor.y = driver->dimensions.height - 1; + } + } +} + +/** + * Clear the LCD. + * @param driver The driver structure. + * @return Non-zero value on error. Updates errno. + */ +inline static int lcdClear(lcdDriver_t *driver) +{ + assert(driver); + if (lcdCommand(driver, LCD_CMD_CLEAR()) || lcdDelay(driver, driver->busTiming.busyHoldShort)) + { + return -1; + } + else + { + driver->cursor.x = 0; + driver->cursor.y = 0; + return 0; + } +} + +/** + * Put the cursor in the home position. + * @param driver The driver structure. + * @return Non-zero value on error. Updates errno. + */ +inline static int lcdHome(lcdDriver_t *driver) +{ + assert(driver); + if (lcdCommand(driver, LCD_CMD_HOME()) || lcdDelay(driver, driver->busTiming.busyHoldLong)) + { + return -1; + } + else + { + driver->cursor.x = 0; + driver->cursor.y = 0; + return 0; + } +} + +/** + * Change the LCD write direction. + * @param driver The driver structure. + * @param forward True for forward direction. + * @return Non-zero value on error. Updates errno. + */ +inline static int lcdDirection(lcdDriver_t *driver, bool forward) +{ + assert(driver); + if (lcdCommand(driver, LCD_CMD_ENTRY(forward, 0)) || lcdDelay(driver, driver->busTiming.busyHoldShort)) + { + return -1; + } + else + { + driver->direction = forward; + return 0; + } +} + +/** + * Move the cursor to the next character. + * @param driver The driver structure. + * @return Non-zero value on error. Updates errno. + */ +inline static int lcdNext(lcdDriver_t *driver) +{ + assert(driver); + lcdUpdateCursor(driver); + if (lcdCommand(driver, LCD_CMD_DADDR(LCD_DECODE_CURSOR(driver)))) + { + return -1; + } + else + { + return 0; + } +} + +/** + * Set the display mode of the LCD. + * @param driver The driver structure. + * @param display True to enable character display. + * @param cursor True to enable the cursor. + * @param blink True to enable cursor blinking. + * @return Non-zero value on error. Updates errno. + */ +inline static int lcdSetDisplay(lcdDriver_t *driver, bool display, bool cursor, bool blink) +{ + assert(driver); + return lcdCommand(driver, LCD_CMD_DISPLAY(display, cursor, blink)); +} + +/** + * Set the cursor position on the LCD. + * @param driver The driver structure. + * @param column The LCD column. + * @param row The LCD row. + * @return Non-zero value on error. Updates errno. + */ +inline static int lcdSetCursor(lcdDriver_t *driver, uint8_t column, uint8_t row) +{ + assert(driver); + assert(driver->dimensions.width > column); + assert(driver->dimensions.height > row); + + driver->cursor.x = column; + driver->cursor.y = row; + + uint8_t address = LCD_DECODE_CURSOR(driver); + return lcdCommand(driver, LCD_CMD_DADDR(address)); +} + +/** + * Store a custom glyph to the LCD character RAM. + * @param driver The driver structure. + * @param which The character to substitute. + * @param bits The bit pattern for the character. + * @return Non-zero value on error. Updates errno. + * @remarks When using a large font, there can only be 4 custom characters, + * otherwise there can be 8. The characters must be in the range of 0-8, and + * must be even in case of a large font. + */ +inline static int lcdStoreGlyph(lcdDriver_t *driver, char which, const uint8_t *bits) +{ + assert(driver); + assert((driver->largeFont && (which >> 1) < 4) || (which < 8)); + assert(bits); + + uint8_t address = (driver->largeFont ? which & 0x06 : which) << 3; + if (lcdCommand(driver, LCD_CMD_CADDR(address))) + return -1; + + for (int i = 0; i < (driver->largeFont ? 10 : 8); i++) + { + if (lcdWrite(driver, bits[i])) return -1; + } + + return 0; +} + +/** + * Put a single character on the LCD display. + * @param driver The driver structure. + * @param chr The character to put. + * @return Non-zero value on error. Updates errno. + * @see lcdPutString + * @see lcdPutZString + */ +inline static int lcdPutChar(lcdDriver_t *driver, char chr) +{ + assert(driver); + if (lcdCommand(driver, LCD_CMD_DADDR(LCD_DECODE_CURSOR(driver))) || lcdWrite(driver, chr)) + return -1; + + lcdUpdateCursor(driver); + + return lcdCommand(driver, LCD_CMD_DADDR(LCD_DECODE_CURSOR(driver))); +} + +/** + * Put a string of known length on the LCD display. + * @param driver The driver structure. + * @param str The string. + * @param length Length of the string. + * @return Non-zero value on error. Updates errno. + * @see lcdPutZString @see lcdPutChar + */ +inline static int lcdPutString(lcdDriver_t *driver, const char *str, size_t length) +{ + assert(driver); + assert(str); + + uint8_t address = LCD_DECODE_CURSOR(driver); + if (lcdCommand(driver, LCD_CMD_DADDR(address))) return -1; + + for (int i = 0; i < length; i++) + { + if (lcdWrite(driver, str[i])) return -1; + + lcdUpdateCursor(driver); + + { + uint8_t newaddr = LCD_DECODE_CURSOR(driver); + if (newaddr != address + 1) + { + if (lcdCommand(driver, LCD_CMD_DADDR(address))) return -1; + } + address = newaddr; + } + } + + return lcdCommand(driver, LCD_CMD_DADDR(address)); +} + +/** + * Put a null terminated string on the LCD display. + * @param driver The driver structure. + * @param str The string. + * @return Non-zero value on error. + * @remarks Uses strlen internally. + * @see lcdPutString @see lcdPutChar + */ +#define lcdPutZString(driver, str) lcdPutString(driver, str, strlen(str)) + +// int lcdRead(lcdDriver_t *driver, uint8_t *data); // Not implemented. + +#endif diff --git a/src/lcd.c b/src/lcd.c new file mode 100644 index 0000000..4733940 --- /dev/null +++ b/src/lcd.c @@ -0,0 +1,265 @@ +#include "lcd.h" + +/** + * Control the LCD bus, write value and read. + * @param driver The driver controlling the bus. + * @param rw Read/Write pin. + * @param rs Register Select pin. + * @param en Enable pin. + * @param data Data bus. + * @return Non-negative on success, bus value if reading, negative on error. + */ +int WEAK lcdBusIO(lcdDriver_t *driver, bool rw, bool rs, bool en, uint8_t data) +{ + assert(driver->busIO); + return driver->busIO(driver, rw, rs, en, data); +} + +/** + * Delay with microsecond precision. + * @param driver The driver delaying. + * @param delay Delay in microseconds. + * @return Zero for success, non-zero on failure. + * @remarks + * Although the implementation should have microsecond precision, over delay + * is not critical as long as the condition that the driver suspended for + * delay microseconds is satisfied. + */ +int WEAK lcdDelay(lcdDriver_t *driver, uint32_t delay) +{ + assert(driver->delay); + return driver->delay(driver, delay); +} + +int lcdCommand(lcdDriver_t *driver, uint8_t command) +{ + // Write command into bus. + if ( + lcdBusIO(driver, 0, 0, 0, command) < 0 || + lcdDelay(driver, driver->busTiming.addressSetup) != 0 || + lcdBusIO(driver, 0, 0, 1, command) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + lcdBusIO(driver, 0, 0, 0, command) < 0 || + lcdDelay(driver, driver->busTiming.dataHold) + ) + { + // IO failed. + driver->error = EIO; + return -1; + } + + if (driver->fourBits) + { + // Write bottom nibble of command into bus if in 4bit mode. + command <<= 4; + if ( + lcdBusIO(driver, 0, 0, 0, command) < 0 || + lcdDelay(driver, driver->busTiming.addressSetup) != 0 || + lcdBusIO(driver, 0, 0, 1, command) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + lcdBusIO(driver, 0, 0, 0, command) < 0 || + lcdDelay(driver, driver->busTiming.dataHold) + ) + { + // IO failed. + driver->error = EIO; + return -1; + } + } + + if (driver->writeOnly) + { + // Just do a dumb delay if in write only mode. + return lcdDelay(driver, driver->busTiming.busyHoldShort); + } + else + { + // Setup read from busy flag. + if ( + lcdBusIO(driver, 1, 0, 0, 0) < 0 || + lcdDelay(driver, driver->busTiming.addressSetup) != 0 + ) + { + // IO failed. + driver->error = EIO; + return -1; + } + + int value = 0; + do { + if ( + lcdDelay(driver, driver->busTiming.busyInterval) != 0 || // Delay before reading busy pin. + lcdBusIO(driver, 1, 0, 1, 0) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + (value = lcdBusIO(driver, 1, 0, 1, 0)) < 0 || // Read busy value. + lcdBusIO(driver, 1, 0, 0, 0) < 0 || + ((driver->fourBits) && ( // 4-bit mode extra ticks. + lcdDelay(driver, driver->busTiming.dataHold) != 0 || + lcdBusIO(driver, 1, 0, 1, 0) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) || + lcdBusIO(driver, 1, 0, 0, 0) < 0 + )) + ) + { + // IO failed. + driver->error = EIO; + return -1; + } + } while (value & (1 << 7)); // Busy flag is the 7th bit. + + return lcdBusIO(driver, 0, 0, 0, 0); + } +} + +int lcdWrite(lcdDriver_t *driver, uint8_t data) +{ + // Write data into bus. + if ( + lcdBusIO(driver, 0, 1, 0, data) < 0 || + lcdDelay(driver, driver->busTiming.addressSetup) != 0 || + lcdBusIO(driver, 0, 1, 1, data) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + lcdBusIO(driver, 0, 1, 0, data) < 0 || + lcdDelay(driver, driver->busTiming.dataHold) + ) + { + // IO failed. + driver->error = EIO; + return -1; + } + + if (driver->fourBits) + { + // Write bottom nibble of data into bus if in 4bit mode. + data <<= 4; + if ( + lcdBusIO(driver, 0, 1, 0, data) < 0 || + lcdDelay(driver, driver->busTiming.addressSetup) != 0 || + lcdBusIO(driver, 0, 1, 1, data) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + lcdBusIO(driver, 0, 1, 0, data) < 0 || + lcdDelay(driver, driver->busTiming.dataHold) + ) + { + // IO failed. + driver->error = EIO; + return -1; + } + } + + if (driver->writeOnly) + { + // Just do a dumb delay if in write only mode. + return lcdDelay(driver, driver->busTiming.busyHoldShort); + } + else + { + // Setup read from busy flag. + if ( + lcdBusIO(driver, 1, 0, 0, 0) < 0 || + lcdDelay(driver, driver->busTiming.addressSetup) != 0 + ) + { + // IO failed. + driver->error = EIO; + return -1; + } + + int value = 0; + do { + if ( + lcdDelay(driver, driver->busTiming.busyInterval) != 0 || // Delay before reading busy pin. + lcdBusIO(driver, 1, 0, 1, 0) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + (value = lcdBusIO(driver, 1, 0, 1, 0)) < 0 || // Read busy value. + lcdBusIO(driver, 1, 0, 0, 0) < 0 || + ((driver->fourBits) && ( // 4-bit mode extra ticks. + lcdDelay(driver, driver->busTiming.dataHold) != 0 || + lcdBusIO(driver, 1, 0, 1, 0) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) || + lcdBusIO(driver, 1, 0, 0, 0) < 0 + )) + ) + { + // IO failed. + driver->error = EIO; + return -1; + } + } while (value & (1 << 7)); // Busy flag is the 7th bit. + + return lcdBusIO(driver, 0, 0, 0, 0); + } +} + +int lcdInit4Bit(lcdDriver_t *driver) +{ + uint8_t cmd = LCD_CMD_FUNCTION(1, 0, 0); + + // Do operations on the bus as if the display is in 8 bit mode. + if ( + lcdBusIO(driver, 0, 0, 0, cmd) < 0 || + lcdDelay(driver, driver->busTiming.addressSetup) != 0 || + lcdBusIO(driver, 0, 0, 1, cmd) < 0 || // Set 8 bit mode. + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + lcdBusIO(driver, 0, 0, 0, cmd) < 0 || + lcdDelay(driver, 5000) != 0 || // Sleep for 5ms. (from hitachi manual) + + lcdBusIO(driver, 0, 0, 0, cmd) < 0 || + lcdDelay(driver, driver->busTiming.addressSetup) != 0 || + lcdBusIO(driver, 0, 0, 1, cmd) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + lcdBusIO(driver, 0, 0, 0, cmd) < 0 || // Set 8 bit mode again. + lcdDelay(driver, 100) != 0 || // Sleep for 100 uS (from hitachi manual) + + lcdBusIO(driver, 0, 0, 1, cmd) < 0 || // Set 8 bit mode once again. + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + lcdBusIO(driver, 0, 0, 0, cmd) < 0 || + lcdDelay(driver, driver->busTiming.dataHold) != 0 + ) + { + driver->error = EIO; + return -1; + } + + cmd = LCD_CMD_FUNCTION(0, 0, 0); + + if ( + lcdBusIO(driver, 0, 0, 0, cmd) < 0 || + lcdDelay(driver, driver->busTiming.addressSetup) != 0 || + lcdBusIO(driver, 0, 0, 1, cmd) < 0 || + lcdDelay(driver, driver->busTiming.enableHold) != 0 || + lcdBusIO(driver, 0, 0, 0, cmd) < 0 || + lcdDelay(driver, driver->busTiming.dataHold + 100) != 0 // Request four bit mode, still in 8 bit mode. + ) + { + driver->error = EIO; + return -1; + } + + return lcdCommand(driver, cmd); // Final request for four bit mode, LCD is initialized. +} + +int lcdInit(lcdDriver_t *driver) +{ + assert(driver); + driver->cursor.x = 0; + driver->cursor.y = 0; + + if ( + driver->fourBits && + ( + lcdInit4Bit(driver) || + lcdCommand(driver, LCD_CMD_FUNCTION(0, driver->dimensions.height > 1, driver->largeFont)) || + lcdDelay(driver, driver->busTiming.busyHoldShort) + ) + ) + { + return -1; + } + else if (!driver->fourBits) + { + assert(false); + } + + return lcdClear(driver); +}