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:
parent
12dc74fe62
commit
da0d5aec2c
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
30
displaybus.h
30
displaybus.h
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -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 */
|
||||
Loading…
Reference in New Issue