stm32f1/lib/lcd_hd44780.c

784 lines
29 KiB
C

/** library to communication with Hitacho HD44780 LCD controller
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
* @date 2019-2020
* @note peripherals used: GPIO @ref lcd_hd44780_gpio
*/
/* standard libraries */
#include <stdint.h> // standard integer types
#include <stdlib.h> // general utilities
#include <stdbool.h> // boolean utilities
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/rcc.h> // real-time control clock library
#include <libopencm3/stm32/gpio.h> // 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);
}
}