Erweiterte LCD-Ansteuerung

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

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++;
    }
}

Weblinks