Initial commit.

This commit is contained in:
H. Utku Maden 2022-08-22 14:34:04 +03:00
commit c884d37214
4 changed files with 799 additions and 0 deletions

4
CMakeLists.txt Normal file

@ -0,0 +1,4 @@
idf_component_register("lcd",
SRCS "src/lcd.c"
INCLUDE_DIRS "include"
)

52
README.md Normal file

@ -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.

478
include/lcd.h Normal file

@ -0,0 +1,478 @@
#ifndef _LCD_H_
#define _LCD_H_
/**
* @file lcd.h LCD driver header file.
*
* @mainpage Generic C99 LCD Driver.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <assert.h>
#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 <stdio.h>
// 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

265
src/lcd.c Normal file

@ -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);
}