Display: add support for the HD44780 display.

All in one chunk because the infrastructure is already there.
This also implements the parallel 4-bit bus used by quite some
displays.

For now you have to add quite a number of #defines to your
config.h. First, there are all the pins required, pin names
changed to your actual board/display connection, of course:

  #define DISPLAY_RS_PIN       PC1
  #define DISPLAY_RW_PIN       PC0
  #define DISPLAY_E_PIN        PD2
  #define DISPLAY_D4_PIN       PD3
  #define DISPLAY_D5_PIN       PD4
  #define DISPLAY_D6_PIN       PD5
  #define DISPLAY_D7_PIN       PD6

And then the information about the display actually existing:

  #define DISPLAY_BUS_4BIT
  #define DISPLAY_TYPE_HD44780

Allowing to do all this in Configtool is forthcoming, of course.
This commit is contained in:
Markus Hitter 2016-05-02 03:56:21 +02:00
parent 12dc74fe62
commit da0d5aec2c
6 changed files with 385 additions and 1 deletions

View File

@ -23,6 +23,7 @@ volatile uint8_t displaybuf[BUFSIZE];
#define TEACUP_C_INCLUDE
#include "display_ssd1306.c"
#include "display_hd44780.c"
#undef TEACUP_C_INCLUDE

View File

@ -23,6 +23,13 @@
#define DISPLAY
#elif defined DISPLAY_TYPE_HD44780
#define DISPLAY_LINES 2
#define DISPLAY_SYMBOLS_PER_LINE 16
#define DISPLAY
#elif defined DISPLAY_TYPE_LCD1302
#error Display type LCD1302 not yet supported.

124
display_hd44780.c Normal file
View File

@ -0,0 +1,124 @@
/** \file
\brief Code specific to the HD44780 display.
*/
/**
TODO list:
- Procedures like display_clear() and display_set_cursor() should be queued
up, too. Just like characters. Fonts start at 0x20, so 0x00..0x1F are
available for command sequences. For example, setting the cursor could
queue up 0x04 0x01 0x20 (3 bytes) to set the cursor to line 1, column 32.
0x04 is the "command", bytes are queued up with display_writechar().
This is necessary to enforce characters and cursor commands to happen in
the right order. Currently, writing a few characters, moving the cursor
elsewhere and writing even more characters results in all characters
being written to the second position, because characters wait in the
queue, while cursor movements are executed immediately.
Code currently in display_set_cursor() would move to display_tick(), then.
*/
#include "display.h"
#if defined TEACUP_C_INCLUDE && defined DISPLAY_TYPE_HD44780
#include "displaybus.h"
#include "delay.h"
#include "sendf.h"
#include "dda.h"
/**
* Initializes the display's controller configuring the way of
* displaying data.
*/
void display_init(void) {
// Minimum initialisation time after power up.
delay_ms(15);
displaybus_init(0);
// Write left to right, no display shifting.
displaybus_write(0x06, parallel_4bit_instruction);
// Display ON, cursor not blinking.
displaybus_write(0x0C, parallel_4bit_instruction);
}
/**
Clear the screen. This display has a dedicated command for doing it.
*/
void display_clear(void) {
displaybus_write(0x01, parallel_4bit_instruction);
}
/**
Sets the cursor to the given position.
\param line The vertical cursor position to set, in lines. First line is
zero. Line height is character height, which is currently
fixed to 8 pixels.
\param column The horizontal cursor position to set, in pixels. First
column is zero.
Use this for debugging purposes, only. Regular display updates happen in
display_clock().
*/
void display_set_cursor(uint8_t line, uint8_t column) {
// Currently unimplemented.
}
/**
Show a nice greeting. Pure eye candy.
*/
void display_greeting(void) {
display_clear();
// We have only 16 characters at our disposal ...
display_writestr_P(PSTR("Welcome @ Teacup"));
// Forward this to the display immediately.
while (buf_canread(display)) {
display_tick();
}
// Allow the user to worship our work for a moment :-)
delay_ms(5000);
}
/**
Regular update of the display. Typically called once a second from clock.c.
*/
void display_clock(void) {
display_clear();
update_current_position();
sendf_P(display_writechar, PSTR("X:%lq Y:%lq"),
current_position.axis[X], current_position.axis[Y]);
}
/**
Forwards a character from the display queue to display bus. As this is a
character based display it's easy.
*/
void display_tick() {
uint8_t data;
if (displaybus_busy()) {
return;
}
if (buf_canread(display)) {
buf_pop(display, data);
displaybus_write(data, parallel_4bit_data);
}
}
#endif /* TEACUP_C_INCLUDE && DISPLAY_TYPE_HD44780 */

View File

@ -24,7 +24,35 @@
#if defined DISPLAY_BUS_4BIT
#error Display connected directly via 4 pins is not yet supported.
#include "parallel-4bit.h"
static void displaybus_init(uint8_t address) __attribute__ ((always_inline));
inline void displaybus_init(uint8_t address) {
return parallel_4bit_init();
}
static uint8_t displaybus_busy(void) __attribute__ ((always_inline));
inline uint8_t displaybus_busy(void) {
return parallel_4bit_busy();
}
/**
Note: we use 'rs' here to decide wether to send data or a command. Other
buses, like I2C, make no such distinction, but have last_byte
instead. This works, because there is currently no display supported
which can use both, I2C and the 4-bit bus.
In case such support is wanted, displaybus_write() likely needs to
take both parameters. The actually best solution will be seen better
if such a display actually appears.
*/
static void displaybus_write(uint8_t data, enum rs_e rs) \
__attribute__ ((always_inline));
inline void displaybus_write(uint8_t data, enum rs_e rs) {
return parallel_4bit_write(data, rs);
}
#define DISPLAY_BUS
#elif defined DISPLAY_BUS_8BIT

206
parallel-4bit.c Normal file
View File

@ -0,0 +1,206 @@
/** \file
\brief Parallel 4-bit subsystem
This implements this custom interface often seen on LCD displays which
uses 4 data lines (D0..D3) and 3 control lines:
RS = Register Select: High for data input, Low for instruction input.
RW = Read/Write: High for read, Low for write.
E = Enable: A line for triggering a read or a write. Its detailed
usage is a bit complicated.
This implementation was written in the hope to be exchangeable with using
I2C or SPI as display bus for the same display.
Other than the I2C implementation, this one uses no send buffer. Bytes can
be sent quickly enough to allow sending them immediately.
*/
#include "parallel-4bit.h"
#ifdef DISPLAY_BUS_4BIT
#include "delay.h"
#include "pinio.h"
// Check for the necessary pins.
#ifndef DISPLAY_RS_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_RS_PIN.
#endif
#ifndef DISPLAY_RW_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_RW_PIN.
#endif
#ifndef DISPLAY_E_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_E_PIN.
#endif
#ifndef DISPLAY_D4_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_D4_PIN.
#endif
#ifndef DISPLAY_D5_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_D5_PIN.
#endif
#ifndef DISPLAY_D6_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_D6_PIN.
#endif
#ifndef DISPLAY_D7_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_D7_PIN.
#endif
static void e_pulse(void) {
WRITE(DISPLAY_E_PIN, 1);
delay_us(1);
WRITE(DISPLAY_E_PIN, 0);
}
/**
Inititalise the subsystem.
*/
void parallel_4bit_init(void) {
SET_OUTPUT(DISPLAY_RS_PIN);
WRITE(DISPLAY_RS_PIN, 0);
SET_OUTPUT(DISPLAY_RW_PIN);
WRITE(DISPLAY_RW_PIN, 0);
SET_OUTPUT(DISPLAY_E_PIN);
WRITE(DISPLAY_E_PIN, 0);
/**
Perform a reset.
*/
SET_OUTPUT(DISPLAY_D4_PIN);
SET_OUTPUT(DISPLAY_D5_PIN);
SET_OUTPUT(DISPLAY_D6_PIN);
SET_OUTPUT(DISPLAY_D7_PIN);
// Initial write is 8 bit.
WRITE(DISPLAY_D4_PIN, 1);
WRITE(DISPLAY_D5_PIN, 1);
WRITE(DISPLAY_D6_PIN, 0);
WRITE(DISPLAY_D7_PIN, 0);
e_pulse();
delay_ms(5); // Delay, busy flag can't be checked here.
// Repeat last command.
e_pulse();
delay_us(100); // Delay, busy flag can't be checked here.
// Repeat last command a third time.
e_pulse();
delay_us(100); // Delay, busy flag can't be checked here.
// Now configure for 4 bit mode.
WRITE(DISPLAY_D4_PIN, 0);
e_pulse();
delay_us(100); // Some displays need this additional delay.
}
/**
Read a byte from the bus. Doing so is e.g. required to detect wether the
display is busy.
\param rs 1: Read data.
0: Read busy flag / address counter.
\return Byte read from LCD controller.
*/
static uint8_t parallel_4bit_read(uint8_t rs) {
uint8_t data;
if (rs)
WRITE(DISPLAY_RS_PIN, 1); // Read data.
else
WRITE(DISPLAY_RS_PIN, 0); // Read busy flag.
WRITE(DISPLAY_RW_PIN, 1); // Read mode.
SET_INPUT(DISPLAY_D4_PIN);
SET_INPUT(DISPLAY_D5_PIN);
SET_INPUT(DISPLAY_D6_PIN);
SET_INPUT(DISPLAY_D7_PIN);
data = 0;
// Read high nibble.
WRITE(DISPLAY_E_PIN, 1);
delay_us(1);
if (READ(DISPLAY_D4_PIN)) data |= 0x10;
if (READ(DISPLAY_D5_PIN)) data |= 0x20;
if (READ(DISPLAY_D6_PIN)) data |= 0x40;
if (READ(DISPLAY_D7_PIN)) data |= 0x80;
WRITE(DISPLAY_E_PIN, 0);
delay_us(1);
// Read low nibble.
WRITE(DISPLAY_E_PIN, 1);
delay_us(1);
if (READ(DISPLAY_D4_PIN)) data |= 0x01;
if (READ(DISPLAY_D5_PIN)) data |= 0x02;
if (READ(DISPLAY_D6_PIN)) data |= 0x04;
if (READ(DISPLAY_D7_PIN)) data |= 0x08;
WRITE(DISPLAY_E_PIN, 0);
return data;
}
/**
Report wether the bus or the connected display is busy.
\return Wether the bus is busy, which means that eventual new transactions
would have to wait.
*/
uint8_t parallel_4bit_busy(void) {
uint8_t status;
status = parallel_4bit_read(0);
return status & 0x80;
}
/**
Send a byte to the bus partner.
\param data The byte to be sent.
\param rs 1 = parallel_4bit_data: Write data.
0 = parallel_4bit_instruction: Write instruction.
Other than other bus implementations we do not buffer here. Writing a byte
takes just some 3 microseconds and there is nothing supporting such writes in
hardware, so the overhead of buffering is most likely not worth the effort.
*/
void parallel_4bit_write(uint8_t data, enum rs_e rs) {
// Wait for the display to become ready.
while (parallel_4bit_busy());
// Setup for writing.
WRITE(DISPLAY_RS_PIN, rs); // Write data / instruction.
WRITE(DISPLAY_RW_PIN, 0); // Write mode.
SET_OUTPUT(DISPLAY_D4_PIN);
SET_OUTPUT(DISPLAY_D5_PIN);
SET_OUTPUT(DISPLAY_D6_PIN);
SET_OUTPUT(DISPLAY_D7_PIN);
// Output high nibble.
WRITE(DISPLAY_D4_PIN, (data & 0x10) ? 1 : 0);
WRITE(DISPLAY_D5_PIN, (data & 0x20) ? 1 : 0);
WRITE(DISPLAY_D6_PIN, (data & 0x40) ? 1 : 0);
WRITE(DISPLAY_D7_PIN, (data & 0x80) ? 1 : 0);
e_pulse();
// Output low nibble.
WRITE(DISPLAY_D4_PIN, (data & 0x01) ? 1 : 0);
WRITE(DISPLAY_D5_PIN, (data & 0x02) ? 1 : 0);
WRITE(DISPLAY_D6_PIN, (data & 0x04) ? 1 : 0);
WRITE(DISPLAY_D7_PIN, (data & 0x08) ? 1 : 0);
e_pulse();
}
#endif /* DISPLAY_BUS_4BIT */

18
parallel-4bit.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef _PARALLEL_4BIT_H
#define _PARALLEL_4BIT_H
#include "config_wrapper.h"
enum rs_e {
parallel_4bit_instruction = 0,
parallel_4bit_data
};
void parallel_4bit_init(void);
uint8_t parallel_4bit_busy(void);
void parallel_4bit_write(uint8_t data, enum rs_e rs);
#endif /* _PARALLEL_4BIT_H */