Erweiterte LCD-Ansteuerung
Einleitung
Die folgende Software beruht zum größten Teil auf dem Tutorial zur LCD-Ansteuerung. Eine Anpassung war jedoch nötig, weil in der Tutorial-Vorlage die vier LCD-Datenleitungen DB4 bis DB7 immer mit den unteren vier Portleitungen PIN0 bis PIN3 des Ausgabeports verbunden sein müssen. Die hier vorgestellte Lösung ermöglicht es, die als Datenleitung verwendeten Pins innerhalb des Ausgabeports freier zu konfigurieren. D.h.
- Die vier Datenleitungen können als Bus beliebig an einem Port gelegt werden
- Allerdings muss die Reihenfolge immer noch eingehalten werden, sprich, DB4-DB7 in aufsteigender Reihenfolge!
- Die beiden anderen Steuersignale E und RS sind auf den übrigen vier Pins eines Ports frei wählbar
- Das Signal RW muss am LCD fest auf GND gelegt sein, es werden nur Schreibzugriffe ausgeführt
- Das Busy-Flag wird nicht genutzt
- Wer die ultimativ freie Zuordnung von Pins über Portgrenzen hinweg haben will, muss die bekannte Bibliothek von Peter Fleury nutzen, siehe Links am Ende des Artikels
Ansonsten sind einige kosmetische Änderungen gemacht worden, die den Code etwas leichter lesbar machen sollen. So sind sämtliche Kommandos und Kommandooptionen als #defines ausgeführt. Gegenüber dem Original sind folgende Änderungen bzw. Erweiterungen gemacht worden:
- konfigurierbare Datenleitungen, z.B. zum Anschluss von DB4-DB7 an PD4-PD7
- Funktionsname lcd_setcursor() statt set_cursor() (einheitliche Namenskonvention)
- einheitliche Verwendung von uint8_t
- neue Funktion lcd_out() für eine 4bit-Operation
- alle Kommandos und Kommandooptionen des LCD als #defines ausgeführt
- neue Funktion lcd_generatechar() und lcd_generatechar_P() zum Generieren eines Zeichens im Character Generator RAM
- ein Demoprogramm, in dem alle Funktionen der Bibliotek in ihrer Nutzung dargestellt sind
Alle Dateien sind als ZIP-Archiv hier verfügbar. Das Programm wurde komplett auf einem ATmega8515 getestet. Es ist auf ein 4x16 LCD ausgelegt. Wenn man das Programm auf seiner eigenen Hardware testen will, müssen folgende Dinge angepasst werden
- in main.h das #define F_CPU für die Taktfrequenz
- in lcd.h die Port- und Pinzuordnung mit LCD_PORT, LCD_DDR etc.
Der Quelltext ist ausgiebig kommentiert und sollte die Funktionen gut erklären.
Die erweiterten LCD-Routinen
Datei lcd.h
// Ansteuerung eines HD44780 kompatiblen LCD im 4-Bit-Interfacemodus
// http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/LCD-Ansteuerung
//
#ifndef LCD_ROUTINES_H
#define LCD_ROUTINES_H
#include <avr/pgmspace.h>
////////////////////////////////////////////////////////////////////////////////
// Pinbelegung für das LCD, an verwendete Pins anpassen
// alle Pins müssen in einem Port liegen
// die 4 Bit für die Daten müssen zusammenliegen, können aber an einer
// beliebigen Postion anfangen
#ifndef LCD_PORTS
#define LCD_PORTS
#define LCD_PORT PORTC
#define LCD_DDR DDRC
// 4 Bit LCD Datenbus DB4-DB7, das unterste Bit DB4 kann auf den Portbits 0..4 liegen
// LCD DB4-DB7 <--> PORTC Bit PC0-PC3
#define LCD_DB PC0
// LCD Steuersignale RS und EN
// LCD RS <--> PORTC Bit PC4 (RS: 0=Data, 1=Command)
#define LCD_RS PC4
// LCD EN <--> PORTC Bit PC5 (EN: 1-Impuls für Daten)
#define LCD_EN PC5
#endif // LCD_PORTS
////////////////////////////////////////////////////////////////////////////////
// LCD Ausführungszeiten (MS=Millisekunden, US=Mikrosekunden)
#ifndef LCD_TIMINGS
#define LCD_TIMINGS
#define LCD_BOOTUP_MS 15
#define LCD_ENABLE_US 1
#define LCD_WRITEDATA_US 46
#define LCD_COMMAND_US 42
#define LCD_SOFT_RESET_MS1 5
#define LCD_SOFT_RESET_MS2 1
#define LCD_SOFT_RESET_MS3 1
#define LCD_SET_4BITMODE_MS 5
#define LCD_CLEAR_DISPLAY_MS 2
#define LCD_CURSOR_HOME_MS 2
#endif // LCD_TIMINGS
////////////////////////////////////////////////////////////////////////////////
// Zeilendefinitionen des verwendeten LCD
// die Einträge hier sollten für ein LCD mit einer Zeilenlänge von 16 Zeichen passen
// bei anderen Zeilenlängen müssen diese Einträge angepasst werden
#define LCD_DDADR_LINE1 0x00
#define LCD_DDADR_LINE2 0x40
#define LCD_DDADR_LINE3 0x10
#define LCD_DDADR_LINE4 0x50
////////////////////////////////////////////////////////////////////////////////
// Initialisierung: muss ganz am Anfang des Programms aufgerufen werden.
void lcd_init( void );
////////////////////////////////////////////////////////////////////////////////
// LCD löschen
void lcd_clear( void );
////////////////////////////////////////////////////////////////////////////////
// Cursor in die erste Zeile, erste Spalte (Position 0,0)
void lcd_home( void );
////////////////////////////////////////////////////////////////////////////////
// Cursor an eine beliebige Position
void lcd_setcursor( uint8_t x, uint8_t y );
////////////////////////////////////////////////////////////////////////////////
// Ausgabe eines einzelnen Zeichens an der aktuellen Cursorposition
void lcd_data( uint8_t data );
////////////////////////////////////////////////////////////////////////////////
// Ausgabe eines Strings an der aktuellen Cursorposition
// String liegt im RAM
void lcd_string( const char *data );
////////////////////////////////////////////////////////////////////////////////
// Ausgabe eines Strings an einer bestimmten Cursorposition
// String liegt im RAM
void lcd_string_xy( uint8_t x, uint8_t y, const char *data );
////////////////////////////////////////////////////////////////////////////////
// Ausgabe einer Zahl an der aktuellen Cursorposition
// Zahl liegt im RAM
void lcd_number( uint8_t number, uint8_t len, uint8_t fill );
////////////////////////////////////////////////////////////////////////////////
// Ausgabe einer Zahl an einer bestimmten Cursorposition
// Zahl liegt im RAM
void lcd_number_xy( uint8_t x, uint8_t y, uint8_t number, uint8_t len, uint8_t fill );
////////////////////////////////////////////////////////////////////////////////
// Ausgabe eines Strings an der aktuellen Cursorposition
// String liegt im Flash
void lcd_string_P( PGM_P data );
////////////////////////////////////////////////////////////////////////////////
// Definition eines benutzerdefinierten Sonderzeichens.
// data muss auf ein Array mit den Zeilencodes des zu definierenden Zeichens
// zeigen, Daten liegen im RAM
void lcd_generatechar( uint8_t code, const uint8_t *data, uint8_t lines );
////////////////////////////////////////////////////////////////////////////////
// Definition eines benutzerdefinierten Sonderzeichens.
// data muss auf ein Array mit den Zeilencodes des zu definierenden Zeichens
// zeigen, Daten liegen im FLASH
void lcd_generatechar_P( uint8_t code, PGM_P data, uint8_t lines );
////////////////////////////////////////////////////////////////////////////////
// Ausgabe eines Kommandos an das LCD.
void lcd_command( uint8_t data );
////////////////////////////////////////////////////////////////////////////////
// LCD Befehle und Argumente.
// zur Verwendung in lcd_command
// Clear Display -------------- 0b00000001
#define LCD_CLEAR_DISPLAY 0x01
// Cursor Home ---------------- 0b0000001x
#define LCD_CURSOR_HOME 0x02
// Set Entry Mode ------------- 0b000001xx
#define LCD_SET_ENTRY 0x04
#define LCD_ENTRY_DECREASE 0x00
#define LCD_ENTRY_INCREASE 0x02
#define LCD_ENTRY_NOSHIFT 0x00
#define LCD_ENTRY_SHIFT 0x01
// Set Display ---------------- 0b00001xxx
#define LCD_SET_DISPLAY 0x08
#define LCD_DISPLAY_OFF 0x00
#define LCD_DISPLAY_ON 0x04
#define LCD_CURSOR_OFF 0x00
#define LCD_CURSOR_ON 0x02
#define LCD_BLINKING_OFF 0x00
#define LCD_BLINKING_ON 0x01
// Set Shift ------------------ 0b0001xxxx
#define LCD_SET_SHIFT 0x10
#define LCD_CURSOR_MOVE 0x00
#define LCD_DISPLAY_SHIFT 0x08
#define LCD_SHIFT_LEFT 0x00
#define LCD_SHIFT_RIGHT 0x01
// Set Function --------------- 0b001xxxxx
#define LCD_SET_FUNCTION 0x20
#define LCD_FUNCTION_4BIT 0x00
#define LCD_FUNCTION_8BIT 0x10
#define LCD_FUNCTION_1LINE 0x00
#define LCD_FUNCTION_2LINE 0x08
#define LCD_FUNCTION_5X7 0x00
#define LCD_FUNCTION_5X10 0x04
#define LCD_SOFT_RESET 0x30
// Set CG RAM Address --------- 0b01xxxxxx (Character Generator RAM)
#define LCD_SET_CGADR 0x40
#define LCD_GC_CHAR0 0
#define LCD_GC_CHAR1 1
#define LCD_GC_CHAR2 2
#define LCD_GC_CHAR3 3
#define LCD_GC_CHAR4 4
#define LCD_GC_CHAR5 5
#define LCD_GC_CHAR6 6
#define LCD_GC_CHAR7 7
// Set DD RAM Address --------- 0b1xxxxxxx (Display Data RAM)
#define LCD_SET_DDADR 0x80
#endif // LCD_ROUTINES_H
Datei lcd.c
// Ansteuerung eines HD44780 kompatiblen LCD im 4-Bit-Interfacemodus
// http://www.mikrocontroller.net/articles/HD44780
// http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/LCD-Ansteuerung
//
// Die Pinbelegung ist über defines in lcd.h einstellbar
#include <avr/io.h>
#include "main.h"
#include "lcd.h"
#include <util/delay.h>
/////////////////////////////////////////////////////////////////////////////////
// Erzeugt einen Enable-Puls
static void lcd_enable( void ) {
LCD_PORT |= (1<<LCD_EN); // Enable auf 1 setzen
_delay_us( LCD_ENABLE_US ); // kurze Pause
LCD_PORT &= ~(1<<LCD_EN); // Enable auf 0 setzen
}
////////////////////////////////////////////////////////////////////////////////
// Sendet eine 4-bit Ausgabeoperation an das LCD
static void lcd_out( uint8_t data ) {
data &= 0xF0; // obere 4 Bit maskieren
LCD_PORT &= ~(0xF0>>(4-LCD_DB)); // Maske löschen
LCD_PORT |= (data>>(4-LCD_DB)); // Bits setzen
lcd_enable();
}
////////////////////////////////////////////////////////////////////////////////
// Initialisierung: muss ganz am Anfang des Programms aufgerufen werden.
void lcd_init( void ) {
// verwendete Pins auf Ausgang schalten
uint8_t pins = (0x0F << LCD_DB) | // 4 Datenleitungen
(1<<LCD_RS) | // R/S Leitung
(1<<LCD_EN); // Enable Leitung
LCD_DDR |= pins;
// initial alle Ausgänge auf Null
LCD_PORT &= ~pins;
// warten auf die Bereitschaft des LCD
_delay_ms( LCD_BOOTUP_MS );
// Soft-Reset muss 3mal hintereinander gesendet werden zur Initialisierung
lcd_out( LCD_SOFT_RESET );
_delay_ms( LCD_SOFT_RESET_MS1 );
lcd_enable();
_delay_ms( LCD_SOFT_RESET_MS2 );
lcd_enable();
_delay_ms( LCD_SOFT_RESET_MS3 );
// 4-bit Modus aktivieren
lcd_out( LCD_SET_FUNCTION |
LCD_FUNCTION_4BIT );
_delay_ms( LCD_SET_4BITMODE_MS );
// 4-bit Modus / 2 Zeilen / 5x7
lcd_command( LCD_SET_FUNCTION |
LCD_FUNCTION_4BIT |
LCD_FUNCTION_2LINE |
LCD_FUNCTION_5X7 );
// Display ein / Cursor aus / Blinken aus
lcd_command(LCD_SET_DISPLAY |
LCD_DISPLAY_ON |
LCD_CURSOR_OFF |
LCD_BLINKING_OFF);
// Cursor inkrement / kein Scrollen
lcd_command( LCD_SET_ENTRY |
LCD_ENTRY_INCREASE |
LCD_ENTRY_NOSHIFT );
lcd_clear();
}
////////////////////////////////////////////////////////////////////////////////
// Sendet ein Datenbyte an das LCD
void lcd_data( uint8_t data ) {
LCD_PORT |= (1<<LCD_RS); // RS auf 1 setzen
lcd_out( data ); // zuerst die oberen,
lcd_out( data<<4 ); // dann die unteren 4 Bit senden
_delay_us( LCD_WRITEDATA_US );
}
////////////////////////////////////////////////////////////////////////////////
// Sendet einen Befehl an das LCD
void lcd_command( uint8_t data ) {
LCD_PORT &= ~(1<<LCD_RS); // RS auf 0 setzen
lcd_out( data ); // zuerst die oberen,
lcd_out( data<<4); // dann die unteren 4 Bit senden
_delay_us(LCD_COMMAND_US );
}
////////////////////////////////////////////////////////////////////////////////
// Sendet den Befehl zur Löschung des Displays
void lcd_clear( void ) {
lcd_command( LCD_CLEAR_DISPLAY );
_delay_ms( LCD_CLEAR_DISPLAY_MS );
}
////////////////////////////////////////////////////////////////////////////////
// Sendet den Befehl: Cursor Home
void lcd_home( void ) {
lcd_command( LCD_CURSOR_HOME );
_delay_ms( LCD_CURSOR_HOME_MS );
}
////////////////////////////////////////////////////////////////////////////////
// Setzt den Cursor in Zeile y (0..3) Spalte x (0..15)
void lcd_setcursor( uint8_t x, uint8_t y ) {
uint8_t data;
switch (y) {
case 0: // 1. Zeile
data = LCD_SET_DDADR + LCD_DDADR_LINE1 + x;
break;
case 1: // 2. Zeile
data = LCD_SET_DDADR + LCD_DDADR_LINE2 + x;
break;
case 2: // 3. Zeile
data = LCD_SET_DDADR + LCD_DDADR_LINE3 + x;
break;
case 3: // 4. Zeile
data = LCD_SET_DDADR + LCD_DDADR_LINE4 + x;
break;
default:
return; // für den Fall einer falschen Zeile
}
lcd_command( data );
}
////////////////////////////////////////////////////////////////////////////////
// Schreibt einen String auf das LCD
void lcd_string( const char *data ) {
while( *data != '\0' )
lcd_data( *data++ );
}
void lcd_string_xy( uint8_t x, uint8_t y, const char *data ) {
lcd_setcursor( x, y );
lcd_string( data );
}
////////////////////////////////////////////////////////////////////////////////
// Schreibt eine Zahl auf das LCD
void lcd_number( uint8_t number, uint8_t len, uint8_t fill ) {
uint8_t digit1 = 0;
uint8_t digit2 = 0;
while (number >= 100) {
digit1++;
number -= 100;
}
while (number >= 10) {
digit2++;
number -= 10;
}
if (len > 2) lcd_data( (digit1 != 0) ? digit1+'0' : fill );
if (len > 1) lcd_data( ((digit1 != 0) || (digit2 != 0)) ? digit2+'0' : fill );
lcd_data( number+'0' );
}
void lcd_number_xy( uint8_t x, uint8_t y, uint8_t number, uint8_t len, uint8_t fill ) {
lcd_setcursor( x, y );
lcd_number( number, len, fill );
}
////////////////////////////////////////////////////////////////////////////////
// Schreibt einen String auf das LCD
// String liegt direkt im Flash Speicher
void lcd_string_P( PGM_P data ) {
uint8_t tmp;
tmp = pgm_read_byte( data );
while( tmp != '\0' ) {
lcd_data( tmp );
data++;
tmp = pgm_read_byte( data );
}
}
////////////////////////////////////////////////////////////////////////////////
// Schreibt ein Zeichen in den Character Generator RAM
// Daten liegen direkt im RAM
void lcd_generatechar( uint8_t code, const uint8_t *data, uint8_t lines ) {
uint8_t i;
// Startposition des Zeichens einstellen
lcd_command( LCD_SET_CGADR | (code<<3) );
// Bitmuster übertragen
for ( i=0; i<lines; i++ ) {
lcd_data( *data++ );
}
}
////////////////////////////////////////////////////////////////////////////////
// Schreibt ein Zeichen in den Character Generator RAM
// Daten liegen direkt im Flash-Speicher
void lcd_generatechar_P( uint8_t code, PGM_P data, uint8_t lines ) {
uint8_t i;
// Startposition des Zeichens einstellen
lcd_command( LCD_SET_CGADR | (code<<3) );
// Bitmuster übertragen
for ( i=0; i<lines; i++ ) {
lcd_data( pgm_read_byte(data) );
data++;
}
}