You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

316 lines
10 KiB

/****************************************************************************
Title : HD44780U LCD library / stripped down version for lcd@usb
Author: Peter Fleury <pfleury@gmx.ch> http://jump.to/fleury
lcd@usb modifications by Till Harbaum <till@harbaum.org>
File: $Id: lcd.c,v 1.2 2007/01/14 12:12:27 harbaum Exp $
Software: AVR-GCC 3.3
Target: any AVR device, memory mapped mode only for AT90S4414/8515/Mega
DESCRIPTION
Basic routines for interfacing a HD44780U-based text lcd display
Originally based on Volker Oth's lcd library,
changed lcd_init(), added additional constants for lcd_command(),
added 4-bit I/O mode, improved and optimized code.
Memory mapped mode compatible with Kanda STK200, but supports also
generation of R/W signal through A8 address line.
Major changes for LCD2USB: Removed many functions not needed in
LCD2USB. Added support for a second controller.
USAGE
See the C include lcd.h file for a description of each function
*****************************************************************************/
#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include "lcd.h"
/*
** constants/macros
*/
#define DDR(x) (*(&x - 1)) /* address of data direction register of port x */
#define PIN(x) (*(&x - 2)) /* address of input register of port x */
#define lcd_e_delay() __asm__ __volatile__( "rjmp 1f\n 1:" );
#define lcd_e0_high() LCD_E_PORT |= _BV(LCD_E0_PIN);
#define lcd_e0_low() LCD_E_PORT &= ~_BV(LCD_E0_PIN);
#define lcd_e1_high() LCD_E_PORT |= _BV(LCD_E1_PIN);
#define lcd_e1_low() LCD_E_PORT &= ~_BV(LCD_E1_PIN);
#define lcd_rw_high() LCD_RW_PORT |= _BV(LCD_RW_PIN)
#define lcd_rw_low() LCD_RW_PORT &= ~_BV(LCD_RW_PIN)
#define lcd_rs_high() LCD_RS_PORT |= _BV(LCD_RS_PIN)
#define lcd_rs_low() LCD_RS_PORT &= ~_BV(LCD_RS_PIN)
/* we don't really know anything about the display attached, */
/* so assume that it's a display with two lines */
#define LCD_FUNCTION_DEFAULT LCD_FUNCTION_4BIT_2LINES
/*
** function prototypes
*/
static void lcd_e_toggle(uint8_t ctrl);
/*
** local functions
*/
/*************************************************************************
delay loop for small accurate delays: 16-bit counter, 4 cycles/loop
*************************************************************************/
static inline void _delayFourCycles(unsigned int __count)
{
if ( __count == 0 )
__asm__ __volatile__( "rjmp 1f\n 1:" ); // 2 cycles
else
__asm__ __volatile__ (
"1: sbiw %0,1" "\n\t"
"brne 1b" // 4 cycles/loop
: "=w" (__count)
: "0" (__count)
);
}
/*************************************************************************
delay for a minimum of <us> microseconds
the number of loops is calculated at compile-time from MCU clock frequency
*************************************************************************/
#define delay(us) _delay_us(us)
/* toggle Enable Pin to initiate write */
static void lcd_e_toggle(uint8_t ctrl)
{
if(ctrl & LCD_CTRL_0) lcd_e0_high();
if(ctrl & LCD_CTRL_1) lcd_e1_high();
lcd_e_delay();
if(ctrl & LCD_CTRL_0) lcd_e0_low();
if(ctrl & LCD_CTRL_1) lcd_e1_low();
}
/*************************************************************************
Low-level function to write byte to LCD controller
Input: data byte to write to LCD
rs 1: write data
0: write instruction
Returns: none
*************************************************************************/
static void lcd_write(uint8_t ctrl, uint8_t data, uint8_t rs)
{
unsigned char dataBits ;
if (rs) lcd_rs_high(); /* write data (RS=1, RW=0) */
else lcd_rs_low(); /* write instruction (RS=0, RW=0) */
lcd_rw_low();
/* configure data pins as output */
DDR(LCD_DATA_PORT) |= 0x0F;
/* output high nibble first */
dataBits = LCD_DATA_PORT & 0xF0;
LCD_DATA_PORT = dataBits |((data>>4)&0x0F);
lcd_e_toggle(ctrl);
/* output low nibble */
LCD_DATA_PORT = dataBits | (data&0x0F);
lcd_e_toggle(ctrl);
/* all data pins high (inactive) */
LCD_DATA_PORT = dataBits | 0x0F;
}
/*************************************************************************
Low-level function to read byte from LCD controller
Input: rs 1: read data
0: read busy flag / address counter
Returns: byte read from LCD controller
*************************************************************************/
static uint8_t lcd_read(uint8_t ctrl, uint8_t rs)
{
uint8_t data;
if (rs) lcd_rs_high(); /* RS=1: read data */
else lcd_rs_low(); /* RS=0: read busy flag */
lcd_rw_high(); /* RW=1 read mode */
DDR(LCD_DATA_PORT) &= 0xF0; /* configure data pins as input */
LCD_DATA_PORT |= 0x0F; /* enable pullups to get a busy */
/* on unconnected display */
if(ctrl & LCD_CTRL_0) lcd_e0_high();
if(ctrl & LCD_CTRL_1) lcd_e1_high();
lcd_e_delay();
data = (PIN(LCD_DATA_PORT) & 0x0F)<<4; /* read high nibble first */
if(ctrl & LCD_CTRL_0) lcd_e0_low();
if(ctrl & LCD_CTRL_1) lcd_e1_low();
lcd_e_delay(); /* Enable 500ns low */
if(ctrl & LCD_CTRL_0) lcd_e0_high();
if(ctrl & LCD_CTRL_1) lcd_e1_high();
lcd_e_delay();
data |= (PIN(LCD_DATA_PORT)&0x0f); /* read low nibble */
if(ctrl & LCD_CTRL_0) lcd_e0_low();
if(ctrl & LCD_CTRL_1) lcd_e1_low();
return data;
}
/*************************************************************************
loops while lcd is busy, returns address counter
*************************************************************************/
static void lcd_waitbusy(uint8_t ctrl)
{
#if 1
uint8_t busy;
do {
busy = 0;
/* check all controllers separately */
if(ctrl & LCD_CTRL_0)
if((lcd_read(LCD_CTRL_0, 0)) & (1<<LCD_BUSY))
busy = 1;
if(ctrl & LCD_CTRL_1)
if((lcd_read(LCD_CTRL_1, 0)) & (1<<LCD_BUSY))
busy = 1;
/* wait until busy flag is cleared */
} while (busy);
#else
/* check all controllers at once (ugly!!!) */
while ( (lcd_read(ctrl, 0)) & (1<<LCD_BUSY));
#endif
}
/*
** PUBLIC FUNCTIONS
*/
/*************************************************************************
Send LCD controller instruction command
Input: instruction to send to LCD controller, see HD44780 data sheet
Returns: none
*************************************************************************/
void lcd_command(uint8_t ctrl, uint8_t cmd)
{
lcd_waitbusy(ctrl);
lcd_write(ctrl, cmd, 0);
}
/*************************************************************************
Send data byte to LCD controller
Input: data to send to LCD controller, see HD44780 data sheet
Returns: none
*************************************************************************/
void lcd_data(uint8_t ctrl, uint8_t data)
{
lcd_waitbusy(ctrl);
lcd_write(ctrl, data, 1);
}
/*************************************************************************
Clear display and set cursor to home position
*************************************************************************/
void lcd_clrscr(uint8_t ctrl)
{
lcd_command(ctrl, 1<<LCD_CLR);
}
/*************************************************************************
Display string without auto linefeed
Input: string to be displayed
Returns: none
*************************************************************************/
void lcd_puts(uint8_t ctrl, const char *s)
/* print string on lcd (no auto linefeed) */
{
while ( (*s) )
lcd_data(ctrl, *s++);
}/* lcd_puts */
/*************************************************************************
Initialize display
Returns: 0 on failure, 1 else
*************************************************************************/
uint8_t lcd_init(uint8_t ctrl)
{
/*
* Initialize LCD to 4 bit I/O mode
*/
/* configure all port bits as output (all LCD lines on same port) */
DDR(LCD_DATA_PORT) |= 0x0F;
/* configure all port bits as output (all LCD data lines on same */
/* port, but control lines on different ports) */
DDR(LCD_RS_PORT) |= _BV(LCD_RS_PIN);
DDR(LCD_RW_PORT) |= _BV(LCD_RW_PIN);
DDR(LCD_E_PORT) |= _BV(LCD_E0_PIN); /* first controller */
DDR(LCD_E_PORT) |= _BV(LCD_E1_PIN); /* seconds controller */
delay(16000); /* wait 16ms or more after power-on */
/* initial write to lcd is 8bit */
LCD_DATA_PORT = (LCD_DATA_PORT & 0xF0)| ( _BV(LCD_FUNCTION) |
_BV(LCD_FUNCTION_8BIT)>>4);
lcd_e_toggle(ctrl);
delay(4992); /* delay, busy flag can't be checked here */
/* repeat last command */
lcd_e_toggle(ctrl);
delay(64); /* delay, busy flag can't be checked here */
/* repeat last command a third time */
lcd_e_toggle(ctrl);
delay(64); /* delay, busy flag can't be checked here */
/* now configure for 4bit mode */
LCD_DATA_PORT &= ~(_BV(LCD_FUNCTION_8BIT) >> 4);
lcd_e_toggle(ctrl);
delay(64); /* some displays need this additional delay */
/* from now the LCD only accepts 4 bit I/O, we can use lcd_command() */
/* try to find out of there's a controller and return 0 if not */
/* display must not be busy anymore */
if((lcd_read(ctrl, 0)) & (1<<LCD_BUSY))
return 0;
/* function set: display lines */
lcd_command(ctrl, LCD_FUNCTION_DEFAULT);
/* wait some time */
delay(64);
/* display must not be busy anymore */
if((lcd_read(ctrl, 0)) & (1<<LCD_BUSY))
return 0;
lcd_command(ctrl, LCD_DISP_OFF); /* display off */
lcd_clrscr(ctrl); /* display clear */
lcd_command(ctrl, LCD_MODE_DEFAULT); /* set entry mode */
lcd_command(ctrl, LCD_DISP_ON); /* display/cursor control */
return 1;
}/* lcd_init */