/** library to communication with Hitacho HD44780 LCD controller * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2019-2020 * @note peripherals used: GPIO @ref lcd_hd44780_gpio */ /* standard libraries */ #include // standard integer types #include // general utilities #include // boolean utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // real-time control clock library #include // general purpose input output library /* own libraries */ #include "global.h" // common methods #include "lcd_hd44780.h" // own definitions #if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C #include "i2c_master.h" // I²C utilities #endif /** include busy time waiting in writes * @note this removes the need to call lcd_hd44780_wait_busy but prevents you to do something else meanwhile, particularly when reading is enabled * @note because I²C is already slow enough, there is no need to wait further */ #if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C #define LCD_HD44780_BUSY_WAIT_INCLUDE 1 // lcd_hd44780_wait_busy works, but the I²C bus is often slower #else // LCD_HD44780_I2C #define LCD_HD44780_BUSY_WAIT_INCLUDE 1 // you can change this value #endif // LCD_HD44780_I2C /** busy wait time for most short writes (37 us with margin) */ #define LCD_HD44780_BUSY_WAIT_SHORT (37 + 5) /** busy wait time for some long writes (1520 us, but experience shows it's more) * @note I have no idea why, but longer times increase the contrast darkness */ #define LCD_HD44780_BUSY_WAIT_LONG (1520 + 500) /* usual HD44780 pinout: * - 1 GND: ground * - 2 VCC: 5V (3.3V versions also exist, but a less common) * - 3 V0 : LCD bias voltage, connect to 10-20k potentiometer (VCC to GND) * - 4 RS : Register Select (high = data, low = instruction) * - 5 R/W: Read/Write (high = read, low = write) * - 6 E : enable (falling edge to latch data, high to output register) * - 7 DB0: Data Bit 0 (for 8-bit transfer) * - 8 DB1: Data Bit 1 (for 8-bit transfer) * - 9 DB2: Data Bit 2 (for 8-bit transfer) * - 10 DB3: Data Bit 3 (for 8-bit transfer) * - 11 DB4: Data Bit 4 (for 4-bit transfer) * - 12 DB5: Data Bit 5 (for 4-bit transfer) * - 13 DB6: Data Bit 6 (for 4-bit transfer) * - 14 DB7: Data Bit 7 (for 4-bit transfer) * - 15 BLA: Backlight Anode * - 16 BLK: Backlight Cathode */ /** @defgroup lcd_hd44780_signals HD44780 signals * @note can be combined using OR * @{ */ #define LCD_HD44780_RS (1 << 0) /**< RS : Register Select (high = data, low = instruction) */ #define LCD_HD44780_RnW (1 << 1) /**< R/W: Read/Write (high = read, low = write) */ #define LCD_HD44780_E (1 << 2) /**< enable (falling edge to latch data, high to output register) */ #define LCD_HD44780_DB0 (1 << 3) /**< Data Bit 0 */ #define LCD_HD44780_DB1 (1 << 4) /**< Data Bit 1 */ #define LCD_HD44780_DB2 (1 << 5) /**< Data Bit 2 */ #define LCD_HD44780_DB3 (1 << 6) /**< Data Bit 3 */ #define LCD_HD44780_DB4 (1 << 7) /**< Data Bit 4 */ #define LCD_HD44780_DB5 (1 << 8) /**< Data Bit 5 */ #define LCD_HD44780_DB6 (1 << 9) /**< Data Bit 6 */ #define LCD_HD44780_DB7 (1 << 10) /**< Data Bit 7 */ #define LCD_HD44780_LED (1 << 11) /**< Backlight Cathode */ /** @} */ #if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C /* I²C backpack PCF8574 GPIO expander pinout: * - P0: RS * - P1: RnW * - P2: E * - P3: LED * - P4: D4 * - P5: D5 * - P6: D6 * - P7: D7 */ /** register select */ #define LCD_HD44780_I2C_RS (1 << 0) /** select read or write */ #define LCD_HD44780_I2C_RnW (1 << 1) /** enable pin */ #define LCD_HD44780_I2C_E (1 << 2) /** enable LED backlight */ #define LCD_HD44780_I2C_LED (1 << 3) /** data bit 4 */ #define LCD_HD44780_I2C_DB4 (1 << 4) /** data bit 5 */ #define LCD_HD44780_I2C_DB5 (1 << 5) /** data bit 6 */ #define LCD_HD44780_I2C_DB6 (1 << 6) /** data bit 7 */ #define LCD_HD44780_I2C_DB7 (1 << 7) /** I²C peripheral base address */ #define LCD_HD44780_I2C_PERIPH I2C1 /** I2C address of I²C backpack adapter (7 bits are LSB) */ uint8_t lcd_hd44780_i2c_addr = 0x3f; // default PCF8574A I²C backpack slave address /** I2C GPIO output state */ static uint8_t lcd_hd44780_i2c_output = 0xff; #else // LCD_HD44780_I2C /** @defgroup lcd_hd44780_gpio GPIO used to control the HD44780 * @{ */ /** register select * @note pulled up by HD44780 */ #define LCD_HD44780_GPIO_RS PB9 /** pin allowing writing data bits (on low) or reading them (on high) * @note remove definition if tied to ground * @note this enables read back data, but more importantly read the busy flag to know when the controller finished processing the command. Tying R/nW to ground instead of a GPIO saves a pin, but no data can be read back. Also every command needs to wait a minimum time before being able to send the next one, even if the controlled might actually already have processed it and is not busy anymore. * @note is pulled up by HD44780 */ #define LCD_HD44780_GPIO_RnW PB10 /** enable pin * @warning needs an external 10k pull-up resistor */ #define LCD_HD44780_GPIO_E PB11 /** data bit 7 * @note pulled up by HD44780 */ #define LCD_HD44780_GPIO_DB7 PB12 /** data bit 6 * @note pulled up by HD44780 */ #define LCD_HD44780_GPIO_DB6 PB13 /** data bit 5 * @note pulled up by HD44780 */ #define LCD_HD44780_GPIO_DB5 PB14 /** data bit 4 * @note pulled up by HD44780 */ #define LCD_HD44780_GPIO_DB4 PB15 /** data bit 3 * @note pulled up by HD44780 * @note leave undefined when only 4-bit mode is used */ //#define LCD_HD44780_GPIO_DB3 P /** data bit 2 * @note pulled up by HD44780 * @note leave undefined when only 4-bit mode is used */ //#define LCD_HD44780_GPIO_DB2 P /** data bit 1 * @note pulled up by HD44780 * @note leave undefined when only 4-bit mode is used */ //#define LCD_HD44780_GPIO_DB1 P /** data bit 0 * @note pulled up by HD44780 * @note leave undefined when only 4-bit mode is used */ //#define LCD_HD44780_GPIO_DB0 P /** @} */ #endif // LCD_HD44780_I2C /** set for 8-bit interface data * @note I²C implies 4-bit */ #if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C #define LCD_HD44780_INTERFACE_DL 0 #elif defined(LCD_HD44780_GPIO_DB3) && defined(LCD_HD44780_GPIO_DB2) && defined(LCD_HD44780_GPIO_DB1) && defined(LCD_HD44780_GPIO_DB0) #define LCD_HD44780_INTERFACE_DL 1 #else #define LCD_HD44780_INTERFACE_DL 0 #endif /** if the display is configured having 2 lines */ static bool lcd_hd44780_n_2lines = true; /** set signals * @param[in] signals */ static void lcd_hd44780_set_signal(uint16_t signals) { #if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C if (signals & LCD_HD44780_RS) { lcd_hd44780_i2c_output |= LCD_HD44780_I2C_RS; } if (signals & LCD_HD44780_RnW) { lcd_hd44780_i2c_output |= LCD_HD44780_I2C_RnW; } if (signals & LCD_HD44780_E) { lcd_hd44780_i2c_output |= LCD_HD44780_I2C_E; } if (signals & LCD_HD44780_DB4) { lcd_hd44780_i2c_output |= LCD_HD44780_I2C_DB4; } if (signals & LCD_HD44780_DB5) { lcd_hd44780_i2c_output |= LCD_HD44780_I2C_DB5; } if (signals & LCD_HD44780_DB6) { lcd_hd44780_i2c_output |= LCD_HD44780_I2C_DB6; } if (signals & LCD_HD44780_DB7) { lcd_hd44780_i2c_output |= LCD_HD44780_I2C_DB7; } if (signals & LCD_HD44780_LED) { lcd_hd44780_i2c_output |= LCD_HD44780_I2C_LED; } #else // LCD_HD44780_I2C if (signals & LCD_HD44780_RS) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_PIN(LCD_HD44780_GPIO_RS)); } #ifdef LCD_HD44780_GPIO_RnW if (signals & LCD_HD44780_RnW) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_PIN(LCD_HD44780_GPIO_RnW)); } #endif // LCD_HD44780_GPIO_RnW if (signals & LCD_HD44780_E) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_PIN(LCD_HD44780_GPIO_E)); } #if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL if (signals & LCD_HD44780_DB0) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_PIN(LCD_HD44780_GPIO_DB0)); } if (signals & LCD_HD44780_DB1) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_PIN(LCD_HD44780_GPIO_DB1)); } if (signals & LCD_HD44780_DB2) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_PIN(LCD_HD44780_GPIO_DB2)); } if (signals & LCD_HD44780_DB3) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_PIN(LCD_HD44780_GPIO_DB3)); } #endif // LCD_HD44780_INTERFACE_DL if (signals & LCD_HD44780_DB4) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4)); } if (signals & LCD_HD44780_DB5) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5)); } if (signals & LCD_HD44780_DB6) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6)); } if (signals & LCD_HD44780_DB7) { gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7)); } if (signals & LCD_HD44780_LED) { // no LED GPIO defined } #endif // LCD_HD44780_I2C } /** clear signals * @param[in] signals */ static void lcd_hd44780_clear_signal(uint16_t signals) { #if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C if (signals & LCD_HD44780_RS) { lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_RS; } if (signals & LCD_HD44780_RnW) { lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_RnW; } if (signals & LCD_HD44780_E) { lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_E; } if (signals & LCD_HD44780_DB4) { lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_DB4; } if (signals & LCD_HD44780_DB5) { lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_DB5; } if (signals & LCD_HD44780_DB6) { lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_DB6; } if (signals & LCD_HD44780_DB7) { lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_DB7; } if (signals & LCD_HD44780_LED) { lcd_hd44780_i2c_output &= ~LCD_HD44780_I2C_LED; } #else // LCD_HD44780_I2C if (signals & LCD_HD44780_RS) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_PIN(LCD_HD44780_GPIO_RS)); } #ifdef LCD_HD44780_GPIO_RnW if (signals & LCD_HD44780_RnW) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_PIN(LCD_HD44780_GPIO_RnW)); } #endif // LCD_HD44780_GPIO_RnW if (signals & LCD_HD44780_E) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_PIN(LCD_HD44780_GPIO_E)); } #if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL if (signals & LCD_HD44780_DB0) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_PIN(LCD_HD44780_GPIO_DB0)); } if (signals & LCD_HD44780_DB1) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_PIN(LCD_HD44780_GPIO_DB1)); } if (signals & LCD_HD44780_DB2) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_PIN(LCD_HD44780_GPIO_DB2)); } if (signals & LCD_HD44780_DB3) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_PIN(LCD_HD44780_GPIO_DB3)); } #endif // LCD_HD44780_INTERFACE_DL if (signals & LCD_HD44780_DB4) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4)); } if (signals & LCD_HD44780_DB5) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5)); } if (signals & LCD_HD44780_DB6) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6)); } if (signals & LCD_HD44780_DB7) { gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7)); } if (signals & LCD_HD44780_LED) { // no LED GPIO defined } #endif // LCD_HD44780_I2C } /** flush the set and cleared signals * @note this only applies to I²C and helps gain time */ static void lcd_hd44780_flush_signal(void) { #if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C i2c_master_slave_write(LCD_HD44780_I2C_PERIPH, lcd_hd44780_i2c_addr, false, &lcd_hd44780_i2c_output, 1); // write the set signals #endif } /** read from controller * @param[in] data read data (true) or busy flag and address counter (false) * @return data/AC read */ static uint8_t lcd_hd44780_read(bool data) { #if defined(LCD_HD44780_GPIO_RnW) || defined(LCD_HD44780_I2C) lcd_hd44780_set_signal(LCD_HD44780_RnW | LCD_HD44780_DB7 | LCD_HD44780_DB6 | LCD_HD44780_DB5 | LCD_HD44780_DB4); // switch DB direction to input to read data if (data) { lcd_hd44780_set_signal(LCD_HD44780_RS); // set high to read data } else { lcd_hd44780_clear_signal(LCD_HD44780_RS); // set low to read busy flag and AC } lcd_hd44780_flush_signal(); // no need to wait tAS = 40 ns before next step since the instructions are slower lcd_hd44780_set_signal(LCD_HD44780_E); // set high to have data output lcd_hd44780_flush_signal(); sleep_us(0); // wait t_DDR = 160 ns before reading // read data bits uint8_t input = 0; // to store read data #if defined(LCD_HD44780_I2C) uint8_t i2c_data; if (I2C_MASTER_RC_NONE == i2c_master_slave_read(LCD_HD44780_I2C_PERIPH, lcd_hd44780_i2c_addr, false, &i2c_data, 1)) { if (i2c_data & LCD_HD44780_I2C_DB7) { input |= 0x80; } if (i2c_data & LCD_HD44780_I2C_DB6) { input |= 0x40; } if (i2c_data & LCD_HD44780_I2C_DB5) { input |= 0x20; } if (i2c_data & LCD_HD44780_I2C_DB4) { input |= 0x10; } } #else // LCD_HD44780_I2C if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7))) { input |= 0x80; } if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6))) { input |= 0x40; } if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5))) { input |= 0x20; } if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4))) { input |= 0x10; } #endif // LCD_HD44780_I2C #if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_PIN(LCD_HD44780_GPIO_DB3))) { input |= 0x08; } if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_PIN(LCD_HD44780_GPIO_DB2))) { input |= 0x04; } if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_PIN(LCD_HD44780_GPIO_DB1))) { input |= 0x02; } if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_PIN(LCD_HD44780_GPIO_DB0))) { input |= 0x01; } #else // LCD_HD44780_INTERFACE_DL // get second nibble // don't wait PW_EH = 230 ns since reading took longer lcd_hd44780_clear_signal(LCD_HD44780_E); // end current data read lcd_hd44780_flush_signal(); sleep_us(1); // wait t_cycE = 500 ns lcd_hd44780_set_signal(LCD_HD44780_E); // have next data output lcd_hd44780_flush_signal(); sleep_us(0); // wait t_DDR = 160 ns before reading // read second nibble #if defined(LCD_HD44780_I2C) if (I2C_MASTER_RC_NONE == i2c_master_slave_read(LCD_HD44780_I2C_PERIPH, lcd_hd44780_i2c_addr, false, &i2c_data, 1)) { if (i2c_data & LCD_HD44780_I2C_DB7) { input |= 0x08; } if (i2c_data & LCD_HD44780_I2C_DB6) { input |= 0x04; } if (i2c_data & LCD_HD44780_I2C_DB5) { input |= 0x02; } if (i2c_data & LCD_HD44780_I2C_DB4) { input |= 0x01; } } #else // LCD_HD44780_I2C if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7))) { input |= 0x80; } if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6))) { input |= 0x40; } if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5))) { input |= 0x20; } if (gpio_get(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4))) { input |= 0x10; } #endif // LCD_HD44780_I2C #endif // LCD_HD44780_INTERFACE_DL // don't wait PW_EH = 230 ns since reading took longer lcd_hd44780_clear_signal(LCD_HD44780_E); // end data read lcd_hd44780_flush_signal(); return input; #else // LCD_HD44780_GPIO_RnW || LCD_HD44780_I2C return 0; // read is not supported #endif } uint8_t lcd_hd44780_read_data(void) { return lcd_hd44780_read(true); } /** write to controller * @param[in] command true if it is an command, false if it is data * @param[in] data data bit to write * @param[in] first_nibble_only if only the first nibble of the data should be written (only applies to 4-bit mode) */ static void lcd_hd44780_write(bool command, uint8_t data, bool first_nibble_only) { lcd_hd44780_clear_signal(LCD_HD44780_RnW); // switch DB direction to output to write data if (command) { lcd_hd44780_clear_signal(LCD_HD44780_RS); } else { lcd_hd44780_set_signal(LCD_HD44780_RS); } lcd_hd44780_flush_signal(); // no need to wait tAS = 40 ns before next step since the instructions are slower // write data if (data & 0x80) { lcd_hd44780_set_signal(LCD_HD44780_DB7); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB7); } if (data & 0x40) { lcd_hd44780_set_signal(LCD_HD44780_DB6); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB6); } if (data & 0x20) { lcd_hd44780_set_signal(LCD_HD44780_DB5); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB5); } if (data & 0x10) { lcd_hd44780_set_signal(LCD_HD44780_DB4); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB4); } #if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL if (data & 0x08) { lcd_hd44780_set_signal(LCD_HD44780_DB3); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB3); } if (data & 0x04) { lcd_hd44780_set_signal(LCD_HD44780_DB2); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB2); } if (data & 0x02) { lcd_hd44780_set_signal(LCD_HD44780_DB1); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB1); } if (data & 0x01) { lcd_hd44780_set_signal(LCD_HD44780_DB0); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB0); } #else // LCD_HD44780_INTERFACE_DL if (!first_nibble_only) { // write second nibble // pulse E to send data sleep_us(1); // wait t_cycE = 500 ns lcd_hd44780_set_signal(LCD_HD44780_E); // set high change output data lcd_hd44780_flush_signal(); sleep_us(1); // wait PW_EH = 230 ns lcd_hd44780_clear_signal(LCD_HD44780_E); // set low to latch data lcd_hd44780_flush_signal(); // no need to wait t_H = 10 ns before next step since next instructions are slower // send second nibble if (data & 0x08) { lcd_hd44780_set_signal(LCD_HD44780_DB7); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB7); } if (data & 0x04) { lcd_hd44780_set_signal(LCD_HD44780_DB6); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB6); } if (data & 0x02) { lcd_hd44780_set_signal(LCD_HD44780_DB5); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB5); } if (data & 0x01) { lcd_hd44780_set_signal(LCD_HD44780_DB4); } else { lcd_hd44780_clear_signal(LCD_HD44780_DB4); } } #endif // LCD_HD44780_INTERFACE_DL // pulse E to send data sleep_us(1); // wait t_cycE = 500 ns lcd_hd44780_set_signal(LCD_HD44780_E); // set high change output data lcd_hd44780_flush_signal(); sleep_us(1); // wait PW_EH = 230 ns lcd_hd44780_clear_signal(LCD_HD44780_E); // set low to latch data lcd_hd44780_flush_signal(); // no need to wait t_H = 10 ns before next step since next instructions are slower // no need to wait t_AH = 10 ns before next step since next instructions are slower } /** wait until controller is not busy anymore * @param[in] timeout maximum time to wait in us * @return address count, or >= 0x80 if timeout expired */ static uint8_t lcd_hd44780_wait_busy(uint16_t timeout) { #if defined(LCD_HD44780_GPIO_RnW) uint8_t ac = 0x80; // address counter to return while (timeout && ((ac = lcd_hd44780_read(false)) >= 0x80)) { // wait until busy flag is low or timeout // wait a bit sleep_us(100); if (timeout > 100) { timeout -= 100; } else { timeout = 0; } } return ac; #else // I²C is also to slow to read (at least 6400 us per read) sleep_us(timeout); // just wait return 0; #endif } /** write function set command * @param[in] dl_8bit 8-bit (true) or 4-bit (false) data length * @param[in] n_2lines 2 (true) or 1 (false) lines * @param[in] f_5x10 5x10 (true) or 5x8 (false) dots font */ static void lcd_hd44780_function_set(bool dl_8bit, bool n_2lines, bool f_5x10) { uint8_t data = 0x20; if (dl_8bit) { data |= 0x10; } if (n_2lines) { data |= 0x08; } if (f_5x10) { data |= 0x04; } lcd_hd44780_write(true, data, false); lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT); } bool lcd_hd44780_setup(bool n_2lines, bool f_5x10) { sleep_ms(40 + 2); // wait for display to initialise after power on: 15 ms for VCC > 4.5 V, 40 ms for VCC > 2.7 V #if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C // configure I²C peripheral if (!i2c_master_check_signals(LCD_HD44780_I2C_PERIPH)) { // check if there are pull-ups to operator I²C return false; } i2c_master_setup(LCD_HD44780_I2C_PERIPH, 100); // setup I²C bus (PCF8574 supports an I²C clock up to 100 kHz) lcd_hd44780_i2c_output = 0xff; // put GPIO to input (sort of open drain output) if (I2C_MASTER_RC_NONE != i2c_master_slave_write(LCD_HD44780_I2C_PERIPH, lcd_hd44780_i2c_addr, false, &lcd_hd44780_i2c_output, 1)) { // check if the device is present and set inputs return false; } #else // LCD_HD44780_I2C // enable all GPIO rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_E)); // enable clock for GPIO port gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_PIN(LCD_HD44780_GPIO_E)); // start idle low gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_E)); // set GPIO as output rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB7)); // enable clock for GPIO port gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_PIN(LCD_HD44780_GPIO_DB7)); // idle high gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB7)); // open drain allows to read and write rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB6)); // enable clock for GPIO port gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_PIN(LCD_HD44780_GPIO_DB6)); // idle high gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB6)); // open drain allows to read and write rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB5)); // enable clock for GPIO port gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_PIN(LCD_HD44780_GPIO_DB5)); // idle high gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB5)); // open drain allows to read and write rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB4)); // enable clock for GPIO port gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_PIN(LCD_HD44780_GPIO_DB4)); // idle high gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB4)); // open drain allows to read and write #if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB3)); // enable clock for GPIO port gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_PIN(LCD_HD44780_GPIO_DB3)); // idle high gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB3)); // open drain allows to read and write rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB2)); // enable clock for GPIO port gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_PIN(LCD_HD44780_GPIO_DB2)); // idle high gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB2)); // open drain allows to read and write rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB1)); // enable clock for GPIO port gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_PIN(LCD_HD44780_GPIO_DB1)); // idle high gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB1)); // open drain allows to read and write rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_DB0)); // enable clock for GPIO port gpio_set(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_PIN(LCD_HD44780_GPIO_DB0)); // idle high gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_MODE_OUTPUT_10_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_DB0)); // open drain allows to read and write #endif // LCD_HD44780_INTERFACE_DL rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_RS)); // enable clock for GPIO port gpio_clear(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_PIN(LCD_HD44780_GPIO_RS)); // set low to read busy flag gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_RS)); // set GPIO as output #ifdef LCD_HD44780_GPIO_RnW rcc_periph_clock_enable(GPIO_RCC(LCD_HD44780_GPIO_RnW)); // enable clock for GPIO port gpio_set(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_PIN(LCD_HD44780_GPIO_RnW)); // set high to read gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_OPENDRAIN, GPIO_PIN(LCD_HD44780_GPIO_RnW)); // set GPIO as output #endif // LCD_HD44780_GPIO_RnW #endif // LCD_HD44780_I2C // initialize device lcd_hd44780_write(true, 0x30, true); // 1st function write set to go to state 1 (8-bit) or 2 (4-bit first nibble) (BF cannot be checked) sleep_ms(4 + 1); // wait 4.1 ms lcd_hd44780_write(true, 0x30, true); // 2st function write set to go to state 1 (8-bit) or 3 (4-bit second nibble) (BF cannot be checked) sleep_us(100 + 10); // wait 100 us lcd_hd44780_write(true, 0x30, true); // 3rd function write set to go to state 1 (8-bit) (BF cannot be checked) sleep_us(LCD_HD44780_BUSY_WAIT_SHORT); // wait 37 us #if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL lcd_hd44780_function_set(1, n_2lines, f_5x10); // function set #else lcd_hd44780_write(true, 0x20, true); // switch to 4-bit mode sleep_us(LCD_HD44780_BUSY_WAIT_SHORT); // wait 37 us lcd_hd44780_function_set(0, n_2lines, f_5x10); // function set #endif lcd_hd44780_n_2lines = n_2lines; // remember number of lines lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT); lcd_hd44780_display_control(false, false, false); // display off lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT); lcd_hd44780_clear_display(); // display clear lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_LONG); lcd_hd44780_entry_mode_set(true, false); // entry mode set lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT); return true; // I²C configuration succeeded } void lcd_hd44780_release(void) { #if defined(LCD_HD44780_I2C) && LCD_HD44780_I2C i2c_master_release(LCD_HD44780_I2C_PERIPH); // release I²C peripheral #else // LCD_HD44780_I2C // switch back GPIO back to input gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB7), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB7)); gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB6), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB6)); gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB5), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB5)); gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB4), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB4)); #if defined(LCD_HD44780_INTERFACE_DL) && LCD_HD44780_INTERFACE_DL gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB3), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB3)); gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB2), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB2)); gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB1), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB1)); gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_DB0), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_DB0)); #endif // LCD_HD44780_INTERFACE_DL gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_RS), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_RS)); #ifdef LCD_HD44780_GPIO_RnW gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_RnW), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_RnW)); #endif // LCD_HD44780_GPIO_RnW gpio_set_mode(GPIO_PORT(LCD_HD44780_GPIO_E), GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_PIN(LCD_HD44780_GPIO_E)); #endif // LCD_HD44780_I2C: } void lcd_hd44780_write_data(uint8_t data) { lcd_hd44780_write(false, data, false); lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT); } void lcd_hd44780_write_command(uint8_t data) { lcd_hd44780_write(true, data, false); lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT); } void lcd_hd44780_clear_display(void) { lcd_hd44780_write_command(0x01); lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_LONG); } void lcd_hd44780_return_home(void) { lcd_hd44780_write_command(0x02); lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_LONG); } void lcd_hd44780_entry_mode_set(bool increment, bool shift) { uint8_t command = 0x04; if (increment) { command |= 0x02; } if (shift) { command |= 0x01; } lcd_hd44780_write_command(command); } void lcd_hd44780_display_control(bool d, bool c, bool b) { uint8_t command = 0x08; if (d) { command |= 0x04; } if (c) { command |= 0x02; } if (b) { command |= 0x01; } lcd_hd44780_write_command(command); } void lcd_hd44780_set_cgram_address(uint8_t acg) { lcd_hd44780_write_command(0x40 | (acg & 0x3f)); } void lcd_hd44780_set_ddram_address(uint8_t add) { lcd_hd44780_write_command(0x80 | (add & 0x7f)); } void lcd_hd44780_write_line(bool line2, const char* data, uint8_t length) { if (line2 && !lcd_hd44780_n_2lines) { // writing line 2 when it does not exists is not possible return; } lcd_hd44780_set_ddram_address(line2 ? 0x40 : 0x00); // set start of line lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT); for (uint8_t i = 0; i < length && i < (lcd_hd44780_n_2lines ? 0x27 : 0x4f); i++) { // write line lcd_hd44780_write_data(data[i]); // write character lcd_hd44780_wait_busy(LCD_HD44780_BUSY_WAIT_SHORT); } }