You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
5.1 KiB
C
217 lines
5.1 KiB
C
4 years ago
|
/*
|
||
|
* ----------------------------------------------------------------------------
|
||
|
* "THE BEER-WARE LICENSE" (Revision 42):
|
||
|
* <joerg@FreeBSD.ORG> wrote this file. As long as you retain this notice you
|
||
|
* can do whatever you want with this stuff. If we meet some day, and you think
|
||
|
* this stuff is worth it, you can buy me a beer in return. Joerg Wunsch
|
||
|
* ----------------------------------------------------------------------------
|
||
|
*
|
||
|
* HD44780 LCD display driver
|
||
|
*
|
||
|
* The LCD controller is used in 4-bit mode with a full bi-directional
|
||
|
* interface (i.e. R/~W is connected) so the busy flag can be read.
|
||
|
*
|
||
|
* $Id$
|
||
|
*/
|
||
|
|
||
|
#include "defines.h"
|
||
|
|
||
|
#include <stdbool.h>
|
||
|
#include <stdint.h>
|
||
|
|
||
|
#include <avr/io.h>
|
||
|
#include <util/delay.h>
|
||
|
|
||
|
#include "hd44780.h"
|
||
|
|
||
|
#define GLUE(a, b) a##b
|
||
|
|
||
|
/* single-bit macros, used for control bits */
|
||
|
#define SET_(what, p, m) GLUE(what, p) |= (1 << (m))
|
||
|
#define CLR_(what, p, m) GLUE(what, p) &= ~(1 << (m))
|
||
|
#define GET_(/* PIN, */ p, m) GLUE(PIN, p) & (1 << (m))
|
||
|
#define SET(what, x) SET_(what, x)
|
||
|
#define CLR(what, x) CLR_(what, x)
|
||
|
#define GET(/* PIN, */ x) GET_(x)
|
||
|
|
||
|
/* nibble macros, used for data path */
|
||
|
#define ASSIGN_(what, p, m, v) GLUE(what, p) = (GLUE(what, p) & \
|
||
|
~((1 << (m)) | (1 << ((m) + 1)) | \
|
||
|
(1 << ((m) + 2)) | (1 << ((m) + 3)))) | \
|
||
|
((v) << (m))
|
||
|
#define READ_(what, p, m) (GLUE(what, p) & ((1 << (m)) | (1 << ((m) + 1)) | \
|
||
|
(1 << ((m) + 2)) | (1 << ((m) + 3)))) >> (m)
|
||
|
#define ASSIGN(what, x, v) ASSIGN_(what, x, v)
|
||
|
#define READ(what, x) READ_(what, x)
|
||
|
|
||
|
#define HD44780_BUSYFLAG 0x80
|
||
|
|
||
|
/*
|
||
|
* Send one pulse to the E signal (enable). Mind the timing
|
||
|
* constraints. If readback is set to true, read the HD44780 data
|
||
|
* pins right before the falling edge of E, and return that value.
|
||
|
*/
|
||
|
static inline uint8_t
|
||
|
hd44780_pulse_e(bool readback) __attribute__((always_inline));
|
||
|
|
||
|
static inline uint8_t
|
||
|
hd44780_pulse_e(bool readback)
|
||
|
{
|
||
|
uint8_t x;
|
||
|
|
||
|
SET(PORT, HD44780_E);
|
||
|
/*
|
||
|
* Guarantee at least 500 ns of pulse width. For high CPU
|
||
|
* frequencies, a delay loop is used. For lower frequencies, NOPs
|
||
|
* are used, and at or below 1 MHz, the native pulse width will
|
||
|
* already be 1 us or more so no additional delays are needed.
|
||
|
*/
|
||
|
#if F_CPU > 4000000UL
|
||
|
_delay_us(0.5);
|
||
|
#else
|
||
|
/*
|
||
|
* When reading back, we need one additional NOP, as the value read
|
||
|
* back from the input pin is sampled close to the beginning of a
|
||
|
* CPU clock cycle, while the previous edge on the output pin is
|
||
|
* generated towards the end of a CPU clock cycle.
|
||
|
*/
|
||
|
if (readback)
|
||
|
__asm__ volatile("nop");
|
||
|
# if F_CPU > 1000000UL
|
||
|
__asm__ volatile("nop");
|
||
|
# if F_CPU > 2000000UL
|
||
|
__asm__ volatile("nop");
|
||
|
__asm__ volatile("nop");
|
||
|
# endif /* F_CPU > 2000000UL */
|
||
|
# endif /* F_CPU > 1000000UL */
|
||
|
#endif
|
||
|
if (readback)
|
||
|
x = READ(PIN, HD44780_D4);
|
||
|
else
|
||
|
x = 0;
|
||
|
CLR(PORT, HD44780_E);
|
||
|
|
||
|
return x;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send one nibble out to the LCD controller.
|
||
|
*/
|
||
|
static void
|
||
|
hd44780_outnibble(uint8_t n, uint8_t rs)
|
||
|
{
|
||
|
CLR(PORT, HD44780_RW);
|
||
|
if (rs)
|
||
|
SET(PORT, HD44780_RS);
|
||
|
else
|
||
|
CLR(PORT, HD44780_RS);
|
||
|
ASSIGN(PORT, HD44780_D4, n);
|
||
|
(void)hd44780_pulse_e(false);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send one byte to the LCD controller. As we are in 4-bit mode, we
|
||
|
* have to send two nibbles.
|
||
|
*/
|
||
|
void
|
||
|
hd44780_outbyte(uint8_t b, uint8_t rs)
|
||
|
{
|
||
|
hd44780_outnibble(b >> 4, rs);
|
||
|
hd44780_outnibble(b & 0xf, rs);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Read one nibble from the LCD controller.
|
||
|
*/
|
||
|
static uint8_t
|
||
|
hd44780_innibble(uint8_t rs)
|
||
|
{
|
||
|
uint8_t x;
|
||
|
|
||
|
SET(PORT, HD44780_RW);
|
||
|
ASSIGN(DDR, HD44780_D4, 0x00);
|
||
|
if (rs)
|
||
|
SET(PORT, HD44780_RS);
|
||
|
else
|
||
|
CLR(PORT, HD44780_RS);
|
||
|
x = hd44780_pulse_e(true);
|
||
|
ASSIGN(DDR, HD44780_D4, 0x0F);
|
||
|
CLR(PORT, HD44780_RW);
|
||
|
|
||
|
return x;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Read one byte (i.e. two nibbles) from the LCD controller.
|
||
|
*/
|
||
|
uint8_t
|
||
|
hd44780_inbyte(uint8_t rs)
|
||
|
{
|
||
|
uint8_t x;
|
||
|
|
||
|
x = hd44780_innibble(rs) << 4;
|
||
|
x |= hd44780_innibble(rs);
|
||
|
|
||
|
return x;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Wait until the busy flag is cleared.
|
||
|
*/
|
||
|
void
|
||
|
hd44780_wait_ready(bool longwait)
|
||
|
{
|
||
|
#if USE_BUSY_BIT
|
||
|
while (hd44780_incmd() & HD44780_BUSYFLAG) ;
|
||
|
#else
|
||
|
if (longwait)
|
||
|
_delay_ms(1.52);
|
||
|
else
|
||
|
_delay_us(37);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Initialize the LCD controller.
|
||
|
*
|
||
|
* The initialization sequence has a mandatory timing so the
|
||
|
* controller can safely recognize the type of interface desired.
|
||
|
* This is the only area where timed waits are really needed as
|
||
|
* the busy flag cannot be probed initially.
|
||
|
*/
|
||
|
void
|
||
|
hd44780_init(void)
|
||
|
{
|
||
|
SET(DDR, HD44780_RS);
|
||
|
SET(DDR, HD44780_RW);
|
||
|
SET(DDR, HD44780_E);
|
||
|
ASSIGN(DDR, HD44780_D4, 0x0F);
|
||
|
|
||
|
_delay_ms(15); /* 40 ms needed for Vcc = 2.7 V */
|
||
|
hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0);
|
||
|
_delay_ms(4.1);
|
||
|
hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0);
|
||
|
_delay_ms(0.1);
|
||
|
hd44780_outnibble(HD44780_FNSET(1, 0, 0) >> 4, 0);
|
||
|
_delay_us(37);
|
||
|
|
||
|
hd44780_outnibble(HD44780_FNSET(0, 1, 0) >> 4, 0);
|
||
|
hd44780_wait_ready(false);
|
||
|
hd44780_outcmd(HD44780_FNSET(0, 1, 0));
|
||
|
hd44780_wait_ready(false);
|
||
|
hd44780_outcmd(HD44780_DISPCTL(0, 0, 0));
|
||
|
hd44780_wait_ready(false);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Prepare the LCD controller pins for powerdown.
|
||
|
*/
|
||
|
void
|
||
|
hd44780_powerdown(void)
|
||
|
{
|
||
|
ASSIGN(PORT, HD44780_D4, 0);
|
||
|
CLR(PORT, HD44780_RS);
|
||
|
CLR(PORT, HD44780_RW);
|
||
|
CLR(PORT, HD44780_E);
|
||
|
}
|