Add LCD Character Displays support.
authorNathael Pajani <nathael.pajani@ed3l.fr>
Wed, 9 Sep 2015 19:15:01 +0000 (21:15 +0200)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:04 +0000 (17:03 +0100)
extdrv/lcd_char.c [new file with mode: 0644]
include/extdrv/lcd_char.h [new file with mode: 0644]

diff --git a/extdrv/lcd_char.c b/extdrv/lcd_char.c
new file mode 100644 (file)
index 0000000..6b1bc84
--- /dev/null
@@ -0,0 +1,440 @@
+/****************************************************************************
+ *   extdrv/lcd.c
+ *
+ *  LCD character display support
+ *
+ * Copyright 2013 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+
+/* This file holds all the code used to handle LCD character displays that are compatible
+ *   with the Hitachi HD44780 driver.
+ * There are many of them out there, and you can usually tell them by the 16-pin interface.
+ *
+ * Most code and comments are inspired by Arduino LiquidCrystal library code submited by
+ *   David A. Mellis, Limor Fried (http://www.ladyada.net) and Tom Igoe.
+ *
+ * Refer to HD44780U documentation from Hitachi for more information.
+ */
+
+#include <stdint.h>
+#include "core/lpc_regs_12xx.h"
+#include "core/lpc_core_cm0.h"
+#include "core/system.h"
+#include "core/systick.h"
+#include "core/pio.h"
+#include "lib/stdio.h"
+#include "drivers/gpio.h"
+#include "drivers/timers.h"
+#include "drivers/ssp.h"
+#include "extdrv/lcd_char.h"
+
+#include "drivers/serial.h" /* for debug */
+#include "extdrv/status_led.h"
+
+
+/*
+ * Configure all gpio used for e-paper display handling
+ * Also calls timer_setup()
+ * Keeps a pointer to the lcdc_definition structure, which MUST stay available.
+ */
+void lcdc_config(struct lcdc_definition* lcd)
+{
+       int i = 0, nb_data_pins = 4;
+
+       if (lcd->eight_bits_mode) {
+               nb_data_pins = 8;
+       }
+       /* Register select pin : output and low (instructions) */
+       config_gpio(&(lcd->pin_reg_sel), LPC_IO_MODE_PULL_UP, GPIO_DIR_OUT, 0);
+       /* Enable pin : output and low */
+       config_gpio(&(lcd->pin_enable), LPC_IO_MODE_PULL_UP, GPIO_DIR_OUT, 0);
+       /* Raed/Write select pin : output and low (write) */
+       config_gpio(&(lcd->pin_rw_sel), LPC_IO_MODE_PULL_UP, GPIO_DIR_OUT, 0);
+       /* Data : Input/output, set as output for initial state. */
+       lcd->mask = ~(0);
+       lcd->first_pin_num = lcd->data_pins[0].pin;
+       lcd->port_regs = LPC_GPIO_REGS(lcd->data_pins[0].port);
+       for (i = 0; i < nb_data_pins; i++) {
+               config_gpio(&(lcd->data_pins[i]), LPC_IO_MODE_PULL_UP, GPIO_DIR_OUT, 0);
+               lcd->mask &= ~(0x01 << lcd->data_pins[i].pin);
+       }
+       
+}
+
+
+/*******************************************************************************/
+/* Internal function for write data transfer
+ * Having data on consecutive pins allows for the same function to be used for both 4 and 8 bits data width.
+ * Masking will take care of the rest.
+ */
+static void lcdc_write_byte(struct lcdc_definition* lcd, uint8_t data)
+{
+       uint32_t oldmask = lcd->port_regs->mask;
+       /* Disable interrupts : we are about to set a port mask, thus any interrupt routine trying to
+        *   access a pin on this port will be unable to perform it's job */
+       lpc_disable_irq();
+       /* Set data first */
+       lcd->port_regs->mask = lcd->mask;
+       lcd->port_regs->out = (data << lcd->first_pin_num);
+       lcd->port_regs->mask = oldmask;
+       /* Old mask restored, restore IRQ */
+       lpc_enable_irq();
+       /* And generate enable pulse: 
+        *   enable is already low
+        *   address (type + r/w) has been stable for more than 50ns (about 2.5 instructions at 48MHz, we
+        *      have a minimum of three above)
+        *   data is set, so it will be stable for more than 100ns before end of pulse
+        *   so we generate a 1us pulse which is far more than the 290ns minimum pulse width.
+        */
+       usleep(1);
+       gpio_set(lcd->pin_enable);
+       usleep(1);
+       gpio_clear(lcd->pin_enable);
+       /* The cycle time must be above 650ns, already the case, but arduino code waits for 100us ... */
+       usleep(100);
+}
+
+static uint8_t lcdc_busy(struct lcdc_definition* lcd);
+
+/* This is the LCD write function.
+ * It takes care of r/d and r/w selection signals and performs the call(s) to the byte write function
+ *    according to the data width configuration.
+ * It also waits for the busy indicator to get low before returning when issuing instructions.
+ */
+static void lcdc_perform_write(struct lcdc_definition* lcd, uint8_t data, uint8_t type)
+{
+       /* Type is Instruction (type == 0) or Data (type == 1) */
+       if (type) {
+               gpio_set(lcd->pin_reg_sel);
+       } else {
+               gpio_clear(lcd->pin_reg_sel);
+       }
+       /* We are writting */
+       gpio_clear(lcd->pin_rw_sel);
+       /* Use one or two cycles depending on bus width */
+       if (lcd->eight_bits_mode) {
+               lcdc_write_byte(lcd, data);
+       } else {
+               lcdc_write_byte(lcd, (data >> 4) & 0x0F);
+               lcdc_write_byte(lcd, data & 0x0F);
+       }
+       /* When sending an instruction, wait for the busy bit to clear */
+       if (type == LCDC_IS_RS_INSTRUCTION) {
+               while (lcdc_busy(lcd)) {
+                       usleep(200);
+               };
+       }
+
+}
+
+/* Internal function for read data transfer
+ * As for write, having data on consecutive pins allows for the same function to be used for
+ *   both 4 and 8 bits data width.
+ * Masking will take care of the rest.
+ */
+static uint8_t lcdc_read_byte(struct lcdc_definition* lcd)
+{
+       uint32_t oldmask = lcd->port_regs->mask;
+       uint8_t data = 0;
+
+       /* Erase data while pins are in output mode (mask has 0's for unmasked pins) */
+       lcd->port_regs->clear = ~(lcd->mask);
+       /* Set pins as input (mask has 0's for unmasked pins) */
+       lcd->port_regs->data_dir &= lcd->mask;
+       /* Generate enable pulse:
+        *   enable is already low
+        *   address (type + r/w) has been stable for more than 50ns (about 2.5 instructions at 48MHz, we
+        *      have a minimum of two above, and setting enable pin high makes for three)
+        *   generate a 1us pulse which is far more than the 290ns minimum pulse width.
+        *   data is available 150ns after enable goes high, so the 1us delay is also much more than required.
+        */
+       usleep(1);
+       gpio_set(lcd->pin_enable);
+       usleep(1);
+       /* End of Enable pulse */
+       gpio_clear(lcd->pin_enable);
+       /* Read data */
+       /* Disable interrupts : we are about to set a port mask, thus any interrupt routine trying to
+        *   access a pin on this port will be unable to perform it's job */
+       lpc_disable_irq();
+       /* Set mask */
+       lcd->port_regs->mask = lcd->mask;
+       data = lcd->port_regs->in;
+       /* Restore mask */
+       lcd->port_regs->mask = oldmask;
+       /* Old mask restored, restore IRQ */
+       lpc_enable_irq();
+       /* Restore output config for pins (mask has 0's for unmasked pins) */
+       lcd->port_regs->data_dir |= ~(lcd->mask);
+       /* The cycle time must be above 650ns, already the case, but arduino code waits for 100us ... */
+       usleep(100);
+       /* And shift the data before returning it */
+       return (data >> lcd->first_pin_num);
+}
+
+/* This is the LCD read function.
+ * It takes care of r/d and r/w selection signals and performs the call(s) to the byte read function
+ *    according to the data width configuration.
+ */
+static uint8_t lcdc_perform_read(struct lcdc_definition* lcd, uint8_t type)
+{
+       uint8_t data = 0;
+       /* Type is Instruction or Data */
+       if (type) {
+               gpio_set(lcd->pin_reg_sel);
+       } else {
+               gpio_clear(lcd->pin_reg_sel);
+       }
+       /* We are reading */
+       gpio_set(lcd->pin_rw_sel);
+       /* Use one or two cycles depending on bus width */
+       if (lcd->eight_bits_mode) {
+               data = lcdc_read_byte(lcd);
+       } else {
+               data = (lcdc_read_byte(lcd) << 4);
+               data |= lcdc_read_byte(lcd);
+       }
+       return data;
+}
+
+
+/* Check the busy bit.
+ * This is used to wait for the end of the instruction execution.
+ */
+static uint8_t lcdc_busy(struct lcdc_definition* lcd)
+{
+       uint8_t data = lcdc_perform_read(lcd, LCDC_IS_RS_INSTRUCTION);
+       if (data & LCDC_BUSY_FLAG) {
+               return 1;
+       }
+       return 0;
+}
+
+
+/*******************************************************************************/
+/* Instructions */
+
+/* Turn ON/Off Display
+ * This function is used to control the power state of the display and the two position cursors (blink
+ *   and underline)
+ * Arguments set to 1 activate the function, agrguments set to 0 turn the corresponding function OFF.
+ */
+void lcdc_on_off(struct lcdc_definition* lcd, uint8_t on, uint8_t cursor_underline, uint8_t cursor_blink)
+{
+       lcdc_perform_write(lcd, LCDC_DISPLAY_ON(on, cursor_underline, cursor_blink), LCDC_IS_RS_INSTRUCTION);
+}
+
+/* Clear display */
+void lcdc_clear(struct lcdc_definition* lcd)
+{
+       lcdc_perform_write(lcd, LCDC_DISPLAY_CLEAR, LCDC_IS_RS_INSTRUCTION);
+       msleep(2);
+}
+
+/* Set increment and shift mode
+ * Address increment type is either LCDC_MODE_INCREMENT or LCDC_MODE_DECREMENT. This controls the direction
+ *   in which the cursor or blinking is moved (right or left) when data is sent to the display (either DDRAM
+ *   or CGRAM).
+ * If auto_display_shift is one (LCDC_MODE_ACC_DISP_SHIFT) then it will appear that the cursor does not move
+ *   but the text does.
+ */
+void lcdc_set_inc_and_shift_mode(struct lcdc_definition* lcd, uint8_t increment_type, uint8_t auto_display_shift)
+{
+       lcdc_perform_write(lcd, LCDC_MODE(increment_type, auto_display_shift), LCDC_IS_RS_INSTRUCTION);
+}
+
+/* Return the cursor to the home position (top left character */
+void lcdc_move_cursor_home(struct lcdc_definition* lcd)
+{
+       lcdc_perform_write(lcd, LCDC_CURSOR_HOME, LCDC_IS_RS_INSTRUCTION);
+       msleep(2);
+}
+
+/* Move the cursor right (dir = 1) or left (dir = 0) */
+void lcdc_move_cursor(struct lcdc_definition* lcd, uint8_t dir)
+{
+       lcdc_perform_write(lcd, LCDC_CHIFT_CTRL(LCDC_SHIFT_CURSOR, dir), LCDC_IS_RS_INSTRUCTION);
+}
+
+/* Shift the display right (dir = 1) or left (dir = 0) */
+void lcdc_shift_display(struct lcdc_definition* lcd, uint8_t dir)
+{
+       lcdc_perform_write(lcd, LCDC_CHIFT_CTRL(LCDC_SHIFT_DISPLAY, dir), LCDC_IS_RS_INSTRUCTION);
+}
+
+/* Set the cursor position to the selected line and column
+ * This is the position where the next character sent will be displayed.
+ * This function (or lcdc_move_cursor_home()) must be called after a write to the Character Generator RAM
+ *    (CGRAM) to switch back to writting to the display.
+ */
+void lcdc_set_cursor_position(struct lcdc_definition* lcd, uint8_t line, uint8_t col)
+{
+       int line_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
+       if (line >= lcd->nb_lines) {
+               line = (lcd->nb_lines - 1);
+       }
+       lcdc_perform_write(lcd, LCDC_SET_DDRAM_ADDR(col + line_offsets[line]), LCDC_IS_RS_INSTRUCTION);
+}
+
+
+/* Return the current line and collumn position of the cursor.
+ * This is useful to save the current cursor position before writing to CGRAM so it can be restored later.
+ */
+void lcdc_get_cursor_position(struct lcdc_definition* lcd, uint8_t* col, uint8_t* line)
+{
+       uint8_t data = 0;
+       /* Wait for busy bit to clear (should always be the case as we wait for it after all instructions) */
+       while (lcdc_busy(lcd));
+       /* Sleep a (very) little bit because the address is updated after the busy bit is cleared */
+       usleep(1);
+       data = lcdc_perform_read(lcd, LCDC_IS_RS_INSTRUCTION);
+       /* Compute line and collumn according to value of data */
+       *col = (data & 0x3F);
+       *line = 0;
+       if (data >= 0x40) {
+               (*line)++;
+       }
+       if (*col >= 0x14) {
+               *col -= 0x14;
+               *line += 2;
+       }
+}
+
+/*******************************************************************************/
+/*
+ * LCD Character display initialize.
+ * After power-on, display is in this state :
+ *   Display clear
+ *   DL=1, 8-bit interface
+ *   N=0, 1-line display
+ *   F=0, 5x8 dot character font
+ *   D=0, Display off
+ *   C=0, Cursor off
+ *   B=0, Blinking off
+ *   I/D=1, Increment by 1
+ *   S=0, No shift
+ * After this call, it is in the configuration given by the lcdc_definition structure
+ *   for the fields defined in the structure, and the following state for the others :
+ *   Display clear
+ *   D=0, Display off
+ *   C=0, Cursor off
+ *   B=0, Blinking off
+ */
+void lcdc_init(struct lcdc_definition* lcd)
+{
+       uint32_t cur_tick = systick_get_tick_count();
+       uint8_t val = 0;
+
+       if (!is_systick_running()) {
+               systick_timer_on(1);
+               systick_start();
+       }
+       /* We must provide an initial 50ms delay after power-up for initial reset routine of the LCD
+        * This could be as little as 15ms if we were using 5V power, but our micro-controller is 3.3V supplied
+        * Refer to HD44780 documentation pages 45 and 56 for more information on initialization instructions
+        */
+       if (cur_tick < 50) {
+               msleep(50 - cur_tick);
+       }
+       /* We will use directly the byte / half_byte instructions, so we must clear the instruction
+        *   indicator ourself (instruction mode is active when corresponding signal is low) */
+       gpio_clear(lcd->pin_reg_sel);
+       gpio_clear(lcd->pin_rw_sel);
+       if (lcd->eight_bits_mode) {
+               lcdc_write_byte(lcd, 0x30);
+               msleep(5); /* Min 4.1ms */
+               lcdc_write_byte(lcd, 0x30);
+       } else {
+               lcdc_write_byte(lcd, 0x03);
+               msleep(5); /* Min 4.1ms */
+               lcdc_write_byte(lcd, 0x03);
+       }
+       usleep(150); /* Min 100us */
+       lcdc_perform_write(lcd, 0x32, LCDC_IS_RS_INSTRUCTION);
+       /* Set function instruction */
+       val = LCDC_SET_FUNC(lcd->eight_bits_mode, (lcd->nb_lines > 1), lcd->font_size);
+       lcdc_perform_write(lcd, val, LCDC_IS_RS_INSTRUCTION);
+       /* Turn off display */
+       lcdc_on_off(lcd, 0, 0, 0);
+       /* Clear display */
+       lcdc_clear(lcd);
+       /* Set mode */
+       lcdc_set_inc_and_shift_mode(lcd, lcd->increment_type, lcd->auto_display_shift);
+}
+
+
+/*******************************************************************************/
+/* Send a character to the display, at the current cursor location */
+void lcdc_write(struct lcdc_definition* lcd, const uint8_t data)
+{
+       lcdc_perform_write(lcd, data, LCDC_IS_RS_DATA);
+}
+
+/* Read a byte from the display memory, at the current cursor location */
+uint8_t lcdc_read(struct lcdc_definition* lcd)
+{
+       return lcdc_perform_read(lcd, LCDC_IS_RS_DATA);
+}
+
+/* Send a line to the display.
+ * Line numbering starts at 0
+ */
+void lcdc_send_data_line(struct lcdc_definition* lcd, const uint8_t line, uint8_t* line_data, uint8_t len)
+{
+       int i = 0;
+       /* Do not go beyond line length */
+       if (len > lcd->bytes_per_line) {
+               len = lcd->bytes_per_line;
+       }
+       /* Move the cursor at the beginning or end of line depending on the writing direction */
+       if (lcd->increment_type == LCDC_MODE_INCREMENT) {
+               lcdc_set_cursor_position(lcd, line, 0);
+       } else {
+               lcdc_set_cursor_position(lcd, line, lcd->bytes_per_line);
+       }
+       /* And display the data */
+       for (i = 0; i < len; i++) {
+               lcdc_write(lcd, line_data[i]);
+       }
+}
+
+
+
+/*******************************************************************************/
+/* Send custom characters to display
+ * Send all the data for one character.
+ * char_num is the character number, it will be shifted to generate the most significant bits
+ *    of the character address.
+ * charmap MUST be at least 8 bytes long. Only 8 bytes will be sent.
+ */
+void create_char(struct lcdc_definition* lcd, uint8_t char_num, uint8_t* charmap)
+{
+       int i = 0;
+       if (char_num >= 8) {
+               return; /* Only eight user defined characters available. */
+       }
+       /* A 5x8 patern uses 8 bytes for one character, so character address is shifted by 3 */
+       lcdc_perform_write(lcd, LCDC_SET_CGRAM_ADDR(char_num << 3), LCDC_IS_RS_INSTRUCTION);
+       /* And send the data */
+       for (i = 0; i < 8; i++) {
+               lcdc_perform_write(lcd, charmap[i], LCDC_IS_RS_DATA);
+       }
+}
+
+
diff --git a/include/extdrv/lcd_char.h b/include/extdrv/lcd_char.h
new file mode 100644 (file)
index 0000000..a6fba1a
--- /dev/null
@@ -0,0 +1,204 @@
+/****************************************************************************
+ *   extdrv/lcd_char.h
+ *
+ *
+ * Copyright 2013 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+
+#ifndef EXTDRV_LCD_CHAR_H
+#define EXTDRV_LCD_CHAR_H
+
+#include <stdint.h>
+#include "core/pio.h"
+
+/* IMPORTANT NOTE :
+ * ALL data pins MUST be consecutive, on the same port, and the first pin in the
+ *  table MUST be the lowest numbered data pin used for this driver
+ */
+
+/***************************************************************************** */
+/* LCD Character display */
+struct lcdc_definition
+{
+       uint8_t nb_lines;
+       uint8_t bytes_per_line;
+       uint8_t eight_bits_mode; /* Use LCDC_DATA_IS_EIGTH_BITS or LCDC_DATA_IS_FOUR_BITS */
+       uint8_t font_size; /* Use LCDC_FONT_TEN_DOTS or LCDC_FONT_EIGHT_DOTS */
+       uint8_t increment_type;  /* Use LCDC_MODE_INCREMENT or LCDC_MODE_DECREMENT */
+       uint8_t auto_display_shift;
+       /* Output pins */
+       struct pio pin_reg_sel;
+       struct pio pin_enable;
+       struct pio pin_rw_sel;
+       /* Important note : ALL data pins MUST be consecutive, on the same port, and the first pin
+        *  in the table MUST be the lowest numbered data pin used for this driver */
+       struct pio data_pins[8];
+       /* These are for internal use only and will be overwritten by config function.
+        * They MUST NOT be changed by the user application */
+       uint32_t mask;
+       uint8_t first_pin_num;
+       struct lpc_gpio* port_regs;
+};
+
+
+
+/*******************************************************************************/
+
+/*
+ * Configure all gpio used for e-paper display handling
+ * Also calls timer_setup()
+ * Keeps a pointer to the lcdc_definition structure, which MUST stay available.
+ */
+void lcdc_config(struct lcdc_definition* lcd_def);
+
+/*
+ * LCD Character display initialize.
+ * After power-on, display is in this state :
+ *   Display clear
+ *   DL=1, 8-bit interface
+ *   N=0, 1-line display
+ *   F=0, 5x8 dot character font
+ *   D=0, Display off
+ *   C=0, Cursor off
+ *   B=0, Blinking off
+ *   I/D=1, Increment by 1
+ *   S=0, No shift
+ * After this call, it is in the configuration given by the lcdc_definition structure
+ *   for the fields defined in the structure, and the following state for the others :
+ *   Display clear
+ *   D=0, Display off
+ *   C=0, Cursor off
+ *   B=0, Blinking off
+ */
+void lcdc_init(struct lcdc_definition* lcd);
+
+
+/*******************************************************************************/
+/* Instructions */
+
+/* Turn ON/Off Display
+ * This function is used to control the power state of the display and the two position cursors (blink
+ *   and underline);
+ * Arguments set to 1 activate the function, agrguments set to 0 turn the corresponding function OFF.
+ */
+void lcdc_on_off(struct lcdc_definition* lcd, uint8_t on, uint8_t cursor_underline, uint8_t cursor_blink);
+
+/* Clear display */
+void lcdc_clear(struct lcdc_definition* lcd);
+
+/* Set increment and shift mode
+ * Address increment type is either LCDC_MODE_INCREMENT or LCDC_MODE_DECREMENT. This controls the direction
+ *   in which the cursor or blinking is moved (right or left) when data is sent to the display (either DDRAM
+ *   or CGRAM).
+ * If auto_display_shift is one (LCDC_MODE_ACC_DISP_SHIFT) then it will appear that the cursor does not move
+ *   but the text does.
+ */
+void lcdc_set_inc_and_shift_mode(struct lcdc_definition* lcd, uint8_t increment_type, uint8_t auto_display_shift);
+
+/* Return the cursor to the home position (top left character */
+void lcdc_move_cursor_home(struct lcdc_definition* lcd);
+
+/* Move the cursor right (dir = 1) or left (dir = 0) */
+void lcdc_move_cursor(struct lcdc_definition* lcd, uint8_t dir);
+
+/* Shift the display right (dir = 1) or left (dir = 0) */
+void lcdc_shift_display(struct lcdc_definition* lcd, uint8_t dir);
+
+/* Set the cursor position to the selected line and column
+ * This is the position where the next character sent will be displayed.
+ * This function (or lcdc_move_cursor_home()) must be called after a write to the Character Generator RAM
+ *    (CGRAM) to switch back to writting to the display.
+ */
+void lcdc_set_cursor_position(struct lcdc_definition* lcd, uint8_t line, uint8_t col);
+
+/* Return the current line and collumn position of the cursor.
+ * This is useful to save the current cursor position before writing to CGRAM so it can be restored later.
+ */
+void lcdc_get_cursor_position(struct lcdc_definition* lcd, uint8_t* col, uint8_t* line);
+
+
+/*******************************************************************************/
+/* Send a character to the display, at the current cursor location */
+void lcdc_write(struct lcdc_definition* lcd, const uint8_t data);
+
+/* Read a byte from the display memory, at the current cursor location */
+uint8_t lcdc_read(struct lcdc_definition* lcd);
+
+/* Send a line to the display.
+ * Line numbering starts at 0
+ */
+void lcdc_send_data_line(struct lcdc_definition* lcd, const uint8_t line, uint8_t* line_data, uint8_t len);
+
+
+/*******************************************************************************/
+/* Send custom characters to display
+ * Send all the data for one character.
+ * char_num is the character number, it will be shifted to generate the most significant bits
+ *    of the character address.
+ * charmap MUST be at least 8 bytes long. Only 8 bytes will be sent.
+ */
+void create_char(struct lcdc_definition* lcd, uint8_t char_num, uint8_t* charmap);
+
+
+/*******************************************************************************/
+#define LCDC_IS_RS_INSTRUCTION  0
+#define LCDC_IS_RS_DATA         1
+
+/* Flags and mask used when reading instruction status (RS = 0 and RW = 1) */
+#define LCDC_BUSY_FLAG             (0x80)
+#define LCDC_ADDRESS_COUNTER_MASK  (0x7F)
+
+/* Function register */
+#define LCDC_DATA_IS_FOUR_BITS   0
+#define LCDC_DATA_IS_EIGTH_BITS  1
+#define LCDC_ONE_LINE            0
+#define LCDC_TWO_LINES           1
+#define LCDC_FONT_TEN_DOTS       1
+#define LCDC_FONT_EIGHT_DOTS     0
+#define LCDC_SET_FUNC(dl, n, f)   (0x20 | (((dl) & 0x01) << 4) | (((n) & 0x01) << 3) | (((f) & 0x01) << 2))
+
+/* Display ON/OFF */
+#define LCDC_DISPLAY_OFF       (0x08)
+#define LCDC_DISPLAY_ON(d, c, b)  (0x08 | (((d) & 0x01) << 2) | (((c) & 0x01) << 1) | ((b) & 0x01))
+/* Clear all display */
+#define LCDC_DISPLAY_CLEAR     (0x01)
+/* Set cursor home */
+#define LCDC_CURSOR_HOME       (0x02)
+/* Entry mode set */
+#define LCDC_MODE_INCREMENT      1
+#define LCDC_MODE_DECREMENT      0
+#define LCDC_MODE_ACC_DISP_SHIFT 1
+#define LCDC_MODE(inc, ads)    (0x04 | (((inc) & 0x01) << 1) | ((ads) & 0x01))
+/* Cursor and display shift control */
+#define LCDC_SHIFT_CURSOR     0
+#define LCDC_SHIFT_DISPLAY    1
+#define LCDC_SHIFT_RIGHT      1
+#define LCDC_SHIFT_LEFT       0
+#define LCDC_CHIFT_CTRL(sc, rl)  (0x10 | (((sc) & 0x01) << 3) | (((rl) & 0x01) << 2))
+
+/* Set Character Generator Ram address */
+#define LCDC_SET_CGRAM_ADDR(x)   (0x40 | ((x) & 0x3F))
+
+/* Set Data Display Ram address */
+#define LCDC_SET_DDRAM_ADDR(x)   (0x80 | ((x) & 0x7F))
+
+
+
+#endif /* EXTDRV_LCD_CHAR_H */
+