/** * \file ks0108.c * \brief KS0108 graphical LCD support * * \subsection Copyright * Copyright (c) 2013-2022 Michael Buesch * * \subsection License * Licensed under the terms of the GNU General Public License version 2. */ #include "ks0108.h" #include "output_extender.h" #include "util.h" #include "main.h" #include /** Kommando-ID: Display control. */ #define KS0108_CMD_DISPLAYCTL(on) (0x3E | ((on) ? 1 : 0)) /** Kommando-ID: Adresse setzen. */ #define KS0108_CMD_SETADDR(addr) (0x40 | ((addr) & 0x3F)) /** Kommando-ID: Seite setzen. */ #define KS0108_CMD_SETPAGE(page) (0xB8 | ((page) & 0x07)) /** Kommando-ID: Startzeile setzen. */ #define KS0108_CMD_STARTLN(line) (0xC0 | ((line) & 0x3F)) /** Status bits */ enum ks0108_status_bits { /** Status bit: LCD verarbeitet einen Befehl. */ KS0108_STAT_BUSY = 0x80, /** Status bit: LCD ist abgeschaltet. */ KS0108_STAT_OFF = 0x20, /** Status bit: LCD ist im Reset. */ KS0108_STAT_RESET = 0x10, }; /** \brief Datenpins initialisieren. */ static inline void ks0108_pins_init(void) { #if BOARDID == BOARDID_PLAIN PORTB &= ~(1 << 5); DDRB |= 1 << 5; #elif BOARDID == BOARDID_MYAVR /* Nothing to do. */ #else # error "Unknown board." #endif } /** \brief Datenrichtung der Datenpins konfigurieren. * * \param out 1: Ausgang. * 0: Eingang. */ static inline void ks0108_data_direction(bool out) { #if BOARDID == BOARDID_PLAIN if (out) { DDRD = 0xFF; } else { PORTD = 0; DDRD = 0; } #elif BOARDID == BOARDID_MYAVR if (out) { DDRD |= 0xFC; DDRB |= 0x30; } else { PORTD &= ~0xFC; DDRD &= ~0xFC; PORTB &= ~0x30; DDRB &= ~0x30; } #else # error "Unknown board." #endif } /** \brief Datenpins einlesen. * * \return Gibt das Datenbyte zurueck. */ static inline uint8_t ks0108_read_data(void) { #if BOARDID == BOARDID_PLAIN return PIND; #elif BOARDID == BOARDID_MYAVR return ((PIND & 0xFC) >> 2) | ((PINB & 0x30) << 2); #else # error "Unknown board." #endif } /** \brief Datenpins schreiben. * * \param data Das Datenbyte. */ static inline void ks0108_write_data(uint8_t data) { #if BOARDID == BOARDID_PLAIN PORTD = data; #elif BOARDID == BOARDID_MYAVR PORTD = (PORTD & ~0xFC) | (data << 2); PORTB = (PORTB & ~0x30) | ((data & 0xC0) >> 2); #else # error "Unknown board." #endif } /** \brief RESET pin setzen/ruecksetzen. * * \param rst 1: Setzen. * 0: Ruecksetzen. */ static inline void ks0108_control_rst(bool rst) { extout_write_bit(EXTOUT_LCD_RST, rst); } /** \brief CHIPSELECT pins setzen/ruecksetzen. * * \param cs1 1: Setzen. * 0: Ruecksetzen. * * \param cs2 1: Setzen. * 0: Ruecksetzen. */ static inline void ks0108_control_cs(bool cs1, bool cs2) { extout_write_bit(EXTOUT_LCD_CS1, cs1); extout_write_bit(EXTOUT_LCD_CS2, cs2); } /** \brief DI pin setzen/ruecksetzen. * * \param di 1: Setzen. * 0: Ruecksetzen. */ static inline void ks0108_control_di(bool di) { extout_write_bit(EXTOUT_LCD_DI, di); } /** \brief RW pin setzen/ruecksetzen. * * \param rw 1: Setzen. * 0: Ruecksetzen. */ static inline void ks0108_control_rw(bool rw) { extout_write_bit(EXTOUT_LCD_RW, rw); } /** \brief E pin setzen/ruecksetzen. * * \param e 1: Setzen. * 0: Ruecksetzen. */ static inline void ks0108_control_e(bool e) { #if BOARDID == BOARDID_PLAIN if (e) PORTB |= 1 << 5; else PORTB &= ~(1 << 5); #elif BOARDID == BOARDID_MYAVR extout_write_bit(EXTOUT_LCD_E, e); #else # error "Unknown board." #endif } /** \brief Control bits auf die Hardware uebertragen. */ static inline void ks0108_write_control_bits(void) { extout_commit(); } /** \brief Daten/Status vom LCD lesen. * * \param di 1: Daten. * 0: Status. */ static uint8_t ks0108_read(uint8_t di) { uint8_t data; ks0108_data_direction(0); ks0108_control_di(di); ks0108_control_rw(1); ks0108_write_control_bits(); ks0108_control_e(1); ks0108_write_control_bits(); _NOP(); data = ks0108_read_data(); ks0108_control_e(0); ks0108_write_control_bits(); return data; } /** \brief Daten/Instruktion auf das LCD schreiben. * * \param di 1: Daten. * 0: Instruktion. * * \param data Die zu schreibenden Daten. */ static void ks0108_write(uint8_t di, uint8_t data) { ks0108_control_di(di); ks0108_control_rw(0); ks0108_write_control_bits(); ks0108_data_direction(1); ks0108_write_data(data); ks0108_control_e(1); ks0108_write_control_bits(); _NOP(); ks0108_control_e(0); ks0108_write_control_bits(); } /** \brief Statusregister des LCD lesen. * * \return Gibt den Inhalt des Statusregisters zurueck. */ static uint8_t ks0108_req_read_status(void) { return ks0108_read(0); } /** \brief Warte solange das BUSY bit im LCD status Register * gesetzt ist. */ static void ks0108_busy_wait(void) { while (ks0108_req_read_status() & KS0108_STAT_BUSY); } /** \brief Kommando an das LCD senden. * * \param command Das zu sendende Kommando. */ static void ks0108_req_command(uint8_t command) { ks0108_busy_wait(); ks0108_write(0, command); } /** \brief Daten an das LCD senden. * * \param data Die zu sendenden Daten. */ static void ks0108_req_write_data(uint8_t data) { ks0108_busy_wait(); ks0108_write(1, data); } #if 0 /** \brief Daten vom LCD lesen. * * \return Gibt die Daten zurueck. **/ static uint8_t ks0108_req_read_data(void) { ks0108_busy_wait(); return ks0108_read(1); } #endif /** \brief LCD Chipselect setzen. * * \param nr Zu setzende Chipnummer. 0 oder 1. */ static void ks0108_chipselect(uint8_t nr) { if (nr == 0) ks0108_control_cs(1, 0); else ks0108_control_cs(0, 1); } /** \brief Ein Bitmap auf dem LCD zeichnen. * * \param bitmap Das zu zeichnende Bitmap. * Wenn NULL, dann werden die Pixel geloescht. * * \param x Absolute X Startkoordinate auf dem LCD. * * \param y Absolute Y Startkoordinate auf dem LCD. * * \param width Breite des zu zeichnenden Bereiches. * * \param height Hoehe des zu zeichnenden Bereiches. */ static void draw_bitmap(const struct bitmap *bitmap, uint8_t x, uint8_t y, uint8_t width, uint8_t height) { uint8_t xx, yy; uint8_t current_chip, chip; uint8_t addr, data = 0; y /= 8; height /= 8; for (yy = y; yy < y + height; yy++) { current_chip = -1; for (xx = x; xx < x + width; xx++) { chip = xx / KS0108_WIDTHPIX_PER_CHIP; addr = xx % KS0108_WIDTHPIX_PER_CHIP; if (chip != current_chip) { current_chip = chip; ks0108_chipselect(chip); ks0108_req_command(KS0108_CMD_SETPAGE(yy)); ks0108_req_command(KS0108_CMD_SETADDR(addr)); } if (bitmap) { data = *bitmap_column_byte(bitmap, xx - x, (yy - y) * 8); } ks0108_req_write_data(data); } } } /** \brief Ein Bitmap auf dem LCD zeichnen. * * \param bitmap Das zu zeichnende Bitmap. * * \param x Absolute X Startkoordinate auf dem LCD. * * \param y Absolute Y Startkoordinate auf dem LCD. */ void ks0108_draw_bitmap(const struct bitmap *bitmap, uint8_t x, uint8_t y) { draw_bitmap(bitmap, x, y, bitmap->width, bitmap->height); } /** \brief LCD initialisieren. */ void ks0108_init(void) { uint8_t chip; /* Pins initialisieren. */ ks0108_pins_init(); ks0108_data_direction(0); ks0108_control_rst(0); ks0108_write_control_bits(); ks0108_chipselect(0); ks0108_control_di(0); ks0108_control_rw(0); ks0108_control_e(0); ks0108_write_control_bits(); _delay_us(10); /* LCD aus dem RESET Status nehmen. */ ks0108_control_rst(1); ks0108_write_control_bits(); _delay_us(10); /* LCD einschalten und Startzeile setzen. */ for (chip = 0; chip < KS0108_NR_CHIPS; chip++) { ks0108_chipselect(chip); ks0108_req_command(KS0108_CMD_DISPLAYCTL(1)); ks0108_req_command(KS0108_CMD_STARTLN(0)); } /* Alle Pixel loeschen. */ draw_bitmap(NULL, 0, 0, KS0108_WIDTH, KS0108_HEIGHT); }