From 633d2d0fe2d39d7d48c1af548d26b6c368437860 Mon Sep 17 00:00:00 2001 From: Markus Koch Date: Sun, 19 Jul 2020 18:40:44 +0200 Subject: [PATCH] fw: Implement display output --- firmware/Makefile | 5 +- firmware/main.c | 57 +++- firmware/oled-display/.gitignore | 52 ++++ firmware/oled-display/font.c | 136 ++++++++ firmware/oled-display/font.h | 16 + firmware/oled-display/i2c.c | 180 +++++++++++ firmware/oled-display/i2c.h | 46 +++ firmware/oled-display/lcd.c | 518 +++++++++++++++++++++++++++++++ firmware/oled-display/lcd.h | 150 +++++++++ firmware/oled-display/main.c | 19 ++ firmware/oled-display/readme.md | 132 ++++++++ 11 files changed, 1308 insertions(+), 3 deletions(-) create mode 100644 firmware/oled-display/.gitignore create mode 100644 firmware/oled-display/font.c create mode 100644 firmware/oled-display/font.h create mode 100644 firmware/oled-display/i2c.c create mode 100644 firmware/oled-display/i2c.h create mode 100644 firmware/oled-display/lcd.c create mode 100644 firmware/oled-display/lcd.h create mode 100644 firmware/oled-display/main.c create mode 100644 firmware/oled-display/readme.md diff --git a/firmware/Makefile b/firmware/Makefile index 6cd36a8..a3b3aa6 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -1,9 +1,10 @@ MCU=atmega88 -CFLAGS=-g -Wall -mcall-prologues -mmcu=$(MCU) -Os -DF_CPU=8000000 +CFLAGS=-g -Wall -mcall-prologues -mmcu=$(MCU) -Os -DF_CPU=8000000 -IAVR-SSD1306/Files/ LDFLAGS=-Wl,-gc-sections -Wl,-relax CC=avr-gcc TARGET=main -OBJECT_FILES=main.o +_OBJECT_FILES=main.o AVR-SSD1306/Files/SSD1306.c AVR-SSD1306/Files/TWI.c +OBJECT_FILES=main.o oled-display/font.o oled-display/i2c.o oled-display/lcd.o all: $(TARGET).hex diff --git a/firmware/main.c b/firmware/main.c index 2371c9a..16e7830 100644 --- a/firmware/main.c +++ b/firmware/main.c @@ -1,6 +1,61 @@ #include +#include +#include +#include "oled-display/lcd.h" + +#define PORT_SPI(t) t##B +#define PIN_SPI_CS (1 << 2) + +void spi_proc() +{ + static uint8_t spi_cs_last = 0; + static uint8_t x = 0; + static uint8_t y = DISPLAY_HEIGHT / 8 - 1; + uint8_t spi_cs; + + spi_cs = !(PORT_SPI(PIN) & PIN_SPI_CS); + + if (spi_cs) { + if (SPSR & (1 << SPIF)) { + lcd_set_buffer(x, y, SPDR); + if (y == 0) { + y = DISPLAY_HEIGHT / 8 - 1; + x++; + if (x == DISPLAY_WIDTH) + x = 0; + } else { + y--; + } + } + } else { + if (spi_cs_last) { + lcd_display(); + } + x = 0; + y = DISPLAY_HEIGHT / 8 - 1; + } + + spi_cs_last = spi_cs; +} int main() -{ +{ + lcd_init(LCD_DISP_ON); + lcd_gotoxy(0, 0); + lcd_puts_p(PSTR("== Internet Radio ==")); + lcd_drawLine(0, 9, 120, 9, WHITE); + lcd_gotoxy(0, 2); + lcd_puts_p(PSTR("Starting up.")); + lcd_gotoxy(0, 3); + lcd_puts_p(PSTR("Please wait...")); + lcd_display(); + SPCR = (1 << SPE); + + _delay_ms(10); + + while (1) { + spi_proc(); + } + return 0; } diff --git a/firmware/oled-display/.gitignore b/firmware/oled-display/.gitignore new file mode 100644 index 0000000..c6127b3 --- /dev/null +++ b/firmware/oled-display/.gitignore @@ -0,0 +1,52 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/firmware/oled-display/font.c b/firmware/oled-display/font.c new file mode 100644 index 0000000..7dd5bf2 --- /dev/null +++ b/firmware/oled-display/font.c @@ -0,0 +1,136 @@ +/* + * font.c + * i2c + * + * Created by Michael Köhler on 16.09.18. + * Copyright 2018 Skie-Systems. All rights reserved. + * + */ +#include "font.h" + +const char ssd1306oled_font[][6] PROGMEM = { +{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // sp +{0x00, 0x00, 0x00, 0x2f, 0x00, 0x00}, // ! +{0x00, 0x00, 0x07, 0x00, 0x07, 0x00}, // " +{0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14}, // # +{0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12}, // $ +{0x00, 0x62, 0x64, 0x08, 0x13, 0x23}, // % +{0x00, 0x36, 0x49, 0x55, 0x22, 0x50}, // & +{0x00, 0x00, 0x05, 0x03, 0x00, 0x00}, // ' +{0x00, 0x00, 0x1c, 0x22, 0x41, 0x00}, // ( +{0x00, 0x00, 0x41, 0x22, 0x1c, 0x00}, // ) +{0x00, 0x14, 0x08, 0x3E, 0x08, 0x14}, // * +{0x00, 0x08, 0x08, 0x3E, 0x08, 0x08}, // + +{0x00, 0x00, 0x00, 0xA0, 0x60, 0x00}, // , +{0x00, 0x08, 0x08, 0x08, 0x08, 0x08}, // - +{0x00, 0x00, 0x60, 0x60, 0x00, 0x00}, // . +{0x00, 0x20, 0x10, 0x08, 0x04, 0x02}, // / +{0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0 +{0x00, 0x00, 0x42, 0x7F, 0x40, 0x00}, // 1 +{0x00, 0x42, 0x61, 0x51, 0x49, 0x46}, // 2 +{0x00, 0x21, 0x41, 0x45, 0x4B, 0x31}, // 3 +{0x00, 0x18, 0x14, 0x12, 0x7F, 0x10}, // 4 +{0x00, 0x27, 0x45, 0x45, 0x45, 0x39}, // 5 +{0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6 +{0x00, 0x01, 0x71, 0x09, 0x05, 0x03}, // 7 +{0x00, 0x36, 0x49, 0x49, 0x49, 0x36}, // 8 +{0x00, 0x06, 0x49, 0x49, 0x29, 0x1E}, // 9 +{0x00, 0x00, 0x36, 0x36, 0x00, 0x00}, // : +{0x00, 0x00, 0x56, 0x36, 0x00, 0x00}, // ; +{0x00, 0x08, 0x14, 0x22, 0x41, 0x00}, // < +{0x00, 0x14, 0x14, 0x14, 0x14, 0x14}, // = +{0x00, 0x00, 0x41, 0x22, 0x14, 0x08}, // > +{0x00, 0x02, 0x01, 0x51, 0x09, 0x06}, // ? +{0x00, 0x32, 0x49, 0x59, 0x51, 0x3E}, // @ +{0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C}, // A +{0x00, 0x7F, 0x49, 0x49, 0x49, 0x36}, // B +{0x00, 0x3E, 0x41, 0x41, 0x41, 0x22}, // C +{0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C}, // D +{0x00, 0x7F, 0x49, 0x49, 0x49, 0x41}, // E +{0x00, 0x7F, 0x09, 0x09, 0x09, 0x01}, // F +{0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A}, // G +{0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F}, // H +{0x00, 0x00, 0x41, 0x7F, 0x41, 0x00}, // I +{0x00, 0x20, 0x40, 0x41, 0x3F, 0x01}, // J +{0x00, 0x7F, 0x08, 0x14, 0x22, 0x41}, // K +{0x00, 0x7F, 0x40, 0x40, 0x40, 0x40}, // L +{0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F}, // M +{0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F}, // N +{0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E}, // O +{0x00, 0x7F, 0x09, 0x09, 0x09, 0x06}, // P +{0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E}, // Q +{0x00, 0x7F, 0x09, 0x19, 0x29, 0x46}, // R +{0x00, 0x46, 0x49, 0x49, 0x49, 0x31}, // S +{0x00, 0x01, 0x01, 0x7F, 0x01, 0x01}, // T +{0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F}, // U +{0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F}, // V +{0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F}, // W +{0x00, 0x63, 0x14, 0x08, 0x14, 0x63}, // X +{0x00, 0x07, 0x08, 0x70, 0x08, 0x07}, // Y +{0x00, 0x61, 0x51, 0x49, 0x45, 0x43}, // Z +{0x00, 0x00, 0x7F, 0x41, 0x41, 0x00}, // [ +{0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55}, // backslash +{0x00, 0x00, 0x41, 0x41, 0x7F, 0x00}, // ] +{0x00, 0x04, 0x02, 0x01, 0x02, 0x04}, // ^ +{0x00, 0x40, 0x40, 0x40, 0x40, 0x40}, // _ +{0x00, 0x00, 0x01, 0x02, 0x04, 0x00}, // ' +{0x00, 0x20, 0x54, 0x54, 0x54, 0x78}, // a +{0x00, 0x7F, 0x48, 0x44, 0x44, 0x38}, // b +{0x00, 0x38, 0x44, 0x44, 0x44, 0x20}, // c +{0x00, 0x38, 0x44, 0x44, 0x48, 0x7F}, // d +{0x00, 0x38, 0x54, 0x54, 0x54, 0x18}, // e +{0x00, 0x08, 0x7E, 0x09, 0x01, 0x02}, // f +{0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C}, // g +{0x00, 0x7F, 0x08, 0x04, 0x04, 0x78}, // h +{0x00, 0x00, 0x44, 0x7D, 0x40, 0x00}, // i +{0x00, 0x40, 0x80, 0x84, 0x7D, 0x00}, // j +{0x00, 0x7F, 0x10, 0x28, 0x44, 0x00}, // k +{0x00, 0x00, 0x41, 0x7F, 0x40, 0x00}, // l +{0x00, 0x7C, 0x04, 0x18, 0x04, 0x78}, // m +{0x00, 0x7C, 0x08, 0x04, 0x04, 0x78}, // n +{0x00, 0x38, 0x44, 0x44, 0x44, 0x38}, // o +{0x00, 0xFC, 0x24, 0x24, 0x24, 0x18}, // p +{0x00, 0x18, 0x24, 0x24, 0x18, 0xFC}, // q +{0x00, 0x7C, 0x08, 0x04, 0x04, 0x08}, // r +{0x00, 0x48, 0x54, 0x54, 0x54, 0x20}, // s +{0x00, 0x04, 0x3F, 0x44, 0x40, 0x20}, // t +{0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C}, // u +{0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C}, // v +{0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C}, // w +{0x00, 0x44, 0x28, 0x10, 0x28, 0x44}, // x +{0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C}, // y +{0x00, 0x44, 0x64, 0x54, 0x4C, 0x44}, // z +{0x00, 0x00, 0x08, 0x77, 0x41, 0x00}, // { +{0x00, 0x00, 0x00, 0x63, 0x00, 0x00}, // ¦ +{0x00, 0x00, 0x41, 0x77, 0x08, 0x00}, // } +{0x00, 0x08, 0x04, 0x08, 0x08, 0x04}, // ~ +/* end of normal char-set */ +/* put your own signs/chars here, edit special_char too */ +/* be sure that your first special char stand here */ +{0x00, 0x3A, 0x40, 0x40, 0x20, 0x7A}, // ü, !!! Important: this must be special_char[0] !!! +{0x00, 0x3D, 0x40, 0x40, 0x40, 0x3D}, // Ü +{0x00, 0x21, 0x54, 0x54, 0x54, 0x79}, // ä +{0x00, 0x7D, 0x12, 0x11, 0x12, 0x7D}, // Ä +{0x00, 0x39, 0x44, 0x44, 0x44, 0x39}, // ö +{0x00, 0x3D, 0x42, 0x42, 0x42, 0x3D}, // Ö +{0x00, 0x02, 0x05, 0x02, 0x00, 0x00}, // ° +{0x00, 0x7E, 0x01, 0x49, 0x55, 0x73}, // ß +{0x00, 0x7C, 0x10, 0x10, 0x08, 0x1C} // µ +}; +const char special_char[][2] PROGMEM = { + // define position of special char in font + // {special char, position in font} + // be sure that last element of this + // array are {0xff, 0xff} and first element + // are {first special char, first element after normal char-set in font} + {'ü', 95}, // special_char[0] + {'Ü', 96}, + {'ä', 97}, + {'Ä', 98}, + {'ö', 99}, + {'Ö', 100}, + {'°', 101}, + {'ß', 102}, + {'µ', 103}, + {0xff, 0xff} // end of table special_char +}; diff --git a/firmware/oled-display/font.h b/firmware/oled-display/font.h new file mode 100644 index 0000000..da8d44c --- /dev/null +++ b/firmware/oled-display/font.h @@ -0,0 +1,16 @@ +/* + * font.h + * i2c + * + * Created by Michael Köhler on 13.09.18. + * Copyright 2018 Skie-Systems. All rights reserved. + * + */ +#ifndef _font_h_ +#define _font_h_ +#include + +extern const char ssd1306oled_font[][6] PROGMEM; +extern const char special_char[][2] PROGMEM; + +#endif diff --git a/firmware/oled-display/i2c.c b/firmware/oled-display/i2c.c new file mode 100644 index 0000000..877e396 --- /dev/null +++ b/firmware/oled-display/i2c.c @@ -0,0 +1,180 @@ +// +// i2c.c +// i2c +// +// Created by Michael Köhler on 09.10.17. +// +// + +#include "i2c.h" + +#if defined (__AVR_ATmega328__) || defined(__AVR_ATmega328P__) || \ +defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PA__) || \ +defined(__AVR_ATmega88P__) || defined(__AVR_ATmega88__) || \ +defined(__AVR_ATmega8__) || \ +defined(__AVR_ATmega48P__) || \ +defined(__AVR_ATmega1284P__) || \ +defined (__AVR_ATmega324A__) || defined (__AVR_ATmega324P__) || defined (__AVR_ATmega324PA__) || \ +defined (__AVR_ATmega644__) || defined (__AVR_ATmega644A__) || defined (__AVR_ATmega644P__) || defined (__AVR_ATmega644PA__) || \ +defined (__AVR_ATmega1284P__) || \ +defined (__AVR_ATmega2560__) +#if PSC_I2C != 1 && PSC_I2C != 4 && PSC_I2C != 16 && PSC_I2C != 64 +#error "Wrong prescaler for TWI !" +#elif SET_TWBR < 0 || SET_TWBR > 255 +#error "TWBR out of range, change PSC_I2C or F_I2C !" +#endif + +uint8_t I2C_ErrorCode; +/********************************************** + Public Function: i2c_init + + Purpose: Initialise TWI/I2C interface + + Input Parameter: none + + Return Value: none + **********************************************/ +void i2c_init(void){ + // set clock + switch (PSC_I2C) { + case 4: + TWSR = 0x1; + break; + case 16: + TWSR = 0x2; + break; + case 64: + TWSR = 0x3; + break; + default: + TWSR = 0x00; + break; + } + + TWBR = (uint8_t)SET_TWBR; + // enable + TWCR = (1 << TWEN); +} +/********************************************** + Public Function: i2c_start + + Purpose: Start TWI/I2C interface + + Input Parameter: + - uint8_t i2c_addr: Adress of reciever + + Return Value: none + **********************************************/ +void i2c_start(uint8_t i2c_addr){ + // i2c start + TWCR = (1 << TWINT)|(1 << TWSTA)|(1 << TWEN); + uint16_t timeout = F_CPU/F_I2C*2.0; + while((TWCR & (1 << TWINT)) == 0 && + timeout !=0){ + timeout--; + if(timeout == 0){ + I2C_ErrorCode |= (1 << I2C_START); + return; + } + }; + // send adress + TWDR = 0x78; + TWCR = (1 << TWINT)|( 1 << TWEN); + timeout = F_CPU/F_I2C*2.0; + while((TWCR & (1 << TWINT)) == 0 && + timeout !=0){ + timeout--; + if(timeout == 0){ + I2C_ErrorCode |= (1 << I2C_SENDADRESS); + return; + } + }; +} +/********************************************** + Public Function: i2c_stop + + Purpose: Stop TWI/I2C interface + + Input Parameter: none + + Return Value: none + **********************************************/ +void i2c_stop(void){ + // i2c stop + TWCR = (1 << TWINT)|(1 << TWSTO)|(1 << TWEN); +} +/********************************************** + Public Function: i2c_byte + + Purpose: Send byte at TWI/I2C interface + + Input Parameter: + - uint8_t byte: Byte to send to reciever + + Return Value: none + **********************************************/ +void i2c_byte(uint8_t byte){ + TWDR = byte; + TWCR = (1 << TWINT)|( 1 << TWEN); + uint16_t timeout = F_CPU/F_I2C*2.0; + while((TWCR & (1 << TWINT)) == 0 && + timeout !=0){ + timeout--; + if(timeout == 0){ + I2C_ErrorCode |= (1 << I2C_BYTE); + return; + } + }; +} +/********************************************** + Public Function: i2c_readAck + + Purpose: read acknowledge from TWI/I2C Interface + + Input Parameter: none + + Return Value: uint8_t + - TWDR: recieved value at TWI/I2C-Interface, 0 at timeout + - 0: Error at read + **********************************************/ +uint8_t i2c_readAck(void){ + TWCR = (1< +#include + +extern uint8_t I2C_ErrorCode; // variable for communication error at twi + // ckeck it in your code + // 0 means no error + // define bits in I2C-ErrorCode: +#define I2C_START 0 // bit 0: timeout start-condition +#define I2C_SENDADRESS 1 // bit 0: timeout device-adress +#define I2C_BYTE 2 // bit 0: timeout byte-transmission +#define I2C_READACK 3 // bit 0: timeout read acknowledge +#define I2C_READNACK 4 // bit 0: timeout read nacknowledge + +void i2c_init(void); // init hw-i2c +void i2c_start(uint8_t i2c_addr); // send i2c_start_condition +void i2c_stop(void); // send i2c_stop_condition +void i2c_byte(uint8_t byte); // send data_byte + +uint8_t i2c_readAck(void); // read byte with ACK +uint8_t i2c_readNAck(void); // read byte with NACK + +#ifdef __cplusplus +} +#endif + +#endif /* i2c_h */ diff --git a/firmware/oled-display/lcd.c b/firmware/oled-display/lcd.c new file mode 100644 index 0000000..e84c290 --- /dev/null +++ b/firmware/oled-display/lcd.c @@ -0,0 +1,518 @@ +/* + * This file is part of lcd library for ssd1306/ssd1309/sh1106 oled-display. + * + * lcd library for ssd1306/ssd1309/sh1106 oled-display is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * + * lcd library for ssd1306/ssd1309/sh1106 oled-display is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Foobar. If not, see . + * + * Diese Datei ist Teil von lcd library for ssd1306/ssd1309/sh1106 oled-display. + * + * lcd library for ssd1306/ssd1309/sh1106 oled-display ist Freie Software: Sie können es unter den Bedingungen + * der GNU General Public License, wie von der Free Software Foundation, + * Version 3 der Lizenz oder jeder späteren + * veröffentlichten Version, weiterverbreiten und/oder modifizieren. + * + * lcd library for ssd1306/ssd1309/sh1106 oled-display wird in der Hoffnung, dass es nützlich sein wird, aber + * OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite + * Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. + * Siehe die GNU General Public License für weitere Details. + * + * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem + * Programm erhalten haben. Wenn nicht, siehe . + * + * lcd.h + * + * Created by Michael Köhler on 22.12.16. + * Copyright 2016 Skie-Systems. All rights reserved. + * + * lib for OLED-Display with ssd1306/ssd1309/sh1106-Controller + * first dev-version only for I2C-Connection + * at ATMega328P like Arduino Uno + * + * at GRAPHICMODE lib needs static SRAM for display: + * DISPLAY-WIDTH * DISPLAY-HEIGHT + 2 bytes + * + * at TEXTMODE lib need static SRAM for display: + * 2 bytes (cursorPosition) + */ + +#include "lcd.h" +#include "font.h" +#include + +#if defined SPI +#include +#endif + +static struct { + uint8_t x; + uint8_t y; +} cursorPosition; + +static uint8_t charMode = NORMALSIZE; +#if defined GRAPHICMODE +#include +static uint8_t displayBuffer[DISPLAY_HEIGHT/8][DISPLAY_WIDTH]; +#elif defined TEXTMODE +#else +#error "No valid displaymode! Refer lcd.h" +#endif + + +const uint8_t init_sequence [] PROGMEM = { // Initialization Sequence + LCD_DISP_OFF, // Display OFF (sleep mode) + 0x20, 0b00, // Set Memory Addressing Mode + // 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode; + // 10=Page Addressing Mode (RESET); 11=Invalid + 0xB0, // Set Page Start Address for Page Addressing Mode, 0-7 + 0xC8, // Set COM Output Scan Direction + 0x00, // --set low column address + 0x10, // --set high column address + 0x40, // --set start line address + 0x81, 0x3F, // Set contrast control register + 0xA1, // Set Segment Re-map. A0=address mapped; A1=address 127 mapped. + 0xA6, // Set display mode. A6=Normal; A7=Inverse + 0xA8, DISPLAY_HEIGHT-1, // Set multiplex ratio(1 to 64) + 0xA4, // Output RAM to Display + // 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content + 0xD3, 0x00, // Set display offset. 00 = no offset + 0xD5, // --set display clock divide ratio/oscillator frequency + 0xF0, // --set divide ratio + 0xD9, 0x22, // Set pre-charge period + // Set com pins hardware configuration +#if DISPLAY_HEIGHT==64 + 0xDA, 0x12, +#elif DISPLAY_HEIGHT==32 + 0xDA, 0x02, +#endif + 0xDB, // --set vcomh + 0x20, // 0x20,0.77xVcc + 0x8D, 0x14, // Set DC-DC enable + + +}; +#pragma mark LCD COMMUNICATION +void lcd_command(uint8_t cmd[], uint8_t size) { +#if defined I2C + i2c_start((LCD_I2C_ADR << 1) | 0); + i2c_byte(0x00); // 0x00 for command, 0x40 for data + for (uint8_t i=0; i (DISPLAY_WIDTH) || y > (DISPLAY_HEIGHT/8-1)) return;// out of display + cursorPosition.x=x; + cursorPosition.y=y; +#if defined (SSD1306) || defined (SSD1309) + uint8_t commandSequence[] = {0xb0+y, 0x21, x, 0x7f}; +#elif defined SH1106 + uint8_t commandSequence[] = {0xb0+y, 0x21, 0x00+((2+x) & (0x0f)), 0x10+( ((2+x) & (0xf0)) >> 4 ), 0x7f}; +#endif + lcd_command(commandSequence, sizeof(commandSequence)); +} +void lcd_set_buffer(uint8_t x, uint8_t y, uint8_t value) { + displayBuffer[y][x] = value; +} +void lcd_clrscr(void){ +#ifdef GRAPHICMODE + for (uint8_t i = 0; i < DISPLAY_HEIGHT/8; i++){ + memset(displayBuffer[i], 0x00, sizeof(displayBuffer[i])); + } + lcd_display(); +#elif defined TEXTMODE + uint8_t displayBuffer[DISPLAY_WIDTH]; + memset(displayBuffer, 0x00, sizeof(displayBuffer)); + for (uint8_t i = 0; i < DISPLAY_HEIGHT/8; i++){ + lcd_gotoxy(0,i); + lcd_data(displayBuffer, sizeof(displayBuffer)); + } +#endif + lcd_home(); +} +void lcd_home(void){ + lcd_gotoxy(0, 0); +} +void lcd_invert(uint8_t invert){ + uint8_t commandSequence[1]; + if (invert != YES) { + commandSequence[0] = 0xA6; + } else { + commandSequence[0] = 0xA7; + } + lcd_command(commandSequence, 1); +} +void lcd_sleep(uint8_t sleep){ + uint8_t commandSequence[1]; + if (sleep != YES) { + commandSequence[0] = 0xAF; + } else { + commandSequence[0] = 0xAE; + } + lcd_command(commandSequence, 1); +} +void lcd_set_contrast(uint8_t contrast){ + uint8_t commandSequence[2] = {0x81, contrast}; + lcd_command(commandSequence, sizeof(commandSequence)); +} +void lcd_putc(char c){ + switch (c) { + case '\b': + // backspace + lcd_gotoxy(cursorPosition.x-charMode, cursorPosition.y); + lcd_putc(' '); + lcd_gotoxy(cursorPosition.x-charMode, cursorPosition.y); + break; + case '\t': + // tab + if( (cursorPosition.x+charMode*4) < (DISPLAY_WIDTH/ sizeof(FONT[0])-charMode*4) ){ + lcd_gotoxy(cursorPosition.x+charMode*4, cursorPosition.y); + }else{ + lcd_gotoxy(DISPLAY_WIDTH/ sizeof(FONT[0]), cursorPosition.y); + } + break; + case '\n': + // linefeed + if(cursorPosition.y < (DISPLAY_HEIGHT/8-1)){ + lcd_gotoxy(cursorPosition.x, cursorPosition.y+charMode); + } + break; + case '\r': + // carrige return + lcd_gotoxy(0, cursorPosition.y); + break; + default: + // char doesn't fit in line + if( (cursorPosition.x >= DISPLAY_WIDTH-sizeof(FONT[0])) || (c < ' ') ) break; + // mapping char + c -= ' '; + if (c >= pgm_read_byte(&special_char[0][1]) ) { + char temp = c; + c = 0xff; + for (uint8_t i=0; pgm_read_byte(&special_char[i][1]) != 0xff; i++) { + if ( pgm_read_byte(&special_char[i][0])-' ' == temp ) { + c = pgm_read_byte(&special_char[i][1]); + break; + } + } + if ( c == 0xff ) break; + } + // print char at display +#ifdef GRAPHICMODE + if (charMode == DOUBLESIZE) { + uint16_t doubleChar[sizeof(FONT[0])]; + uint8_t dChar; + if ((cursorPosition.x+2*sizeof(FONT[0]))>DISPLAY_WIDTH) break; + + for (uint8_t i=0; i < sizeof(FONT[0]); i++) { + doubleChar[i] = 0; + dChar = pgm_read_byte(&(FONT[(uint8_t)c][i])); + for (uint8_t j=0; j<8; j++) { + if ((dChar & (1 << j))) { + doubleChar[i] |= (1 << (j*2)); + doubleChar[i] |= (1 << ((j*2)+1)); + } + } + } + for (uint8_t i = 0; i < sizeof(FONT[0]); i++) + { + // load bit-pattern from flash + displayBuffer[cursorPosition.y+1][cursorPosition.x+(2*i)] = doubleChar[i] >> 8; + displayBuffer[cursorPosition.y+1][cursorPosition.x+(2*i)+1] = doubleChar[i] >> 8; + displayBuffer[cursorPosition.y][cursorPosition.x+(2*i)] = doubleChar[i] & 0xff; + displayBuffer[cursorPosition.y][cursorPosition.x+(2*i)+1] = doubleChar[i] & 0xff; + } + cursorPosition.x += sizeof(FONT[0])*2; + } else { + if ((cursorPosition.x+sizeof(FONT[0]))>DISPLAY_WIDTH) break; + + for (uint8_t i = 0; i < sizeof(FONT[0]); i++) + { + // load bit-pattern from flash + displayBuffer[cursorPosition.y][cursorPosition.x+i] =pgm_read_byte(&(FONT[(uint8_t)c][i])); + } + cursorPosition.x += sizeof(FONT[0]); + } +#elif defined TEXTMODE + if (charMode == DOUBLESIZE) { + uint16_t doubleChar[sizeof(FONT[0])]; + uint8_t dChar; + if ((cursorPosition.x+2*sizeof(FONT[0]))>DISPLAY_WIDTH) break; + + for (uint8_t i=0; i < sizeof(FONT[0]); i++) { + doubleChar[i] = 0; + dChar = pgm_read_byte(&(FONT[(uint8_t)c][i])); + for (uint8_t j=0; j<8; j++) { + if ((dChar & (1 << j))) { + doubleChar[i] |= (1 << (j*2)); + doubleChar[i] |= (1 << ((j*2)+1)); + } + } + } + uint8_t data[sizeof(FONT[0])*2]; + for (uint8_t i = 0; i < sizeof(FONT[0]); i++) + { + // print font to ram, print 6 columns + data[i<<1]=(doubleChar[i] & 0xff); + data[(i<<1)+1]=(doubleChar[i] & 0xff); + } + lcd_data(data, sizeof(FONT[0])*2); + +#if defined (SSD1306) || defined (SSD1309) + uint8_t commandSequence[] = {0xb0+cursorPosition.y+1, + 0x21, + cursorPosition.x, + 0x7f}; +#elif defined SH1106 + uint8_t commandSequence[] = {0xb0+cursorPosition.y+1, + 0x21, + 0x00+((2+cursorPosition.x) & (0x0f)), + 0x10+( ((2+cursorPosition.x) & (0xf0)) >> 4 ), + 0x7f}; +#endif + lcd_command(commandSequence, sizeof(commandSequence)); + + for (uint8_t i = 0; i < sizeof(FONT[0]); i++) + { + // print font to ram, print 6 columns + data[i<<1]=(doubleChar[i] >> 8); + data[(i<<1)+1]=(doubleChar[i] >> 8); + } + lcd_data(data, sizeof(FONT[0])*2); + + commandSequence[0] = 0xb0+cursorPosition.y; +#if defined (SSD1306) || defined (SSD1309) + commandSequence[2] = cursorPosition.x+(2*sizeof(FONT[0])); +#elif defined SH1106 + commandSequence[2] = 0x00+((2+cursorPosition.x+(2*sizeof(FONT[0]))) & (0x0f)); + commandSequence[3] = 0x10+( ((2+cursorPosition.x+(2*sizeof(FONT[0]))) & (0xf0)) >> 4 ); +#endif + lcd_command(commandSequence, sizeof(commandSequence)); + cursorPosition.x += sizeof(FONT[0])*2; + } else { + uint8_t data[sizeof(FONT[0])]; + if ((cursorPosition.x+sizeof(FONT[0]))>DISPLAY_WIDTH) break; + + for (uint8_t i = 0; i < sizeof(FONT[0]); i++) + { + // print font to ram, print 6 columns + data[i]=(pgm_read_byte(&(FONT[(uint8_t)c][i]))); + } + lcd_data(data, sizeof(FONT[0])); + cursorPosition.x += sizeof(FONT[0]); + } +#endif + break; + } + +} +void lcd_charMode(uint8_t mode){ + charMode = mode; +} +void lcd_puts(const char* s){ + while (*s) { + lcd_putc(*s++); + } +} +void lcd_puts_p(const char* progmem_s){ + register uint8_t c; + while ((c = pgm_read_byte(progmem_s++))) { + lcd_putc(c); + } +} +#ifdef GRAPHICMODE +#pragma mark - +#pragma mark GRAPHIC FUNCTIONS +void lcd_drawPixel(uint8_t x, uint8_t y, uint8_t color){ + if( x > DISPLAY_WIDTH-1 || y > (DISPLAY_HEIGHT-1)) return; // out of Display + if( color == WHITE){ + displayBuffer[(y / 8)][x] |= (1 << (y % 8)); + } else { + displayBuffer[(y / 8)][x] &= ~(1 << (y % 8)); + } +} +void lcd_drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color){ + if( x1 > DISPLAY_WIDTH-1 || + x2 > DISPLAY_WIDTH-1 || + y1 > DISPLAY_HEIGHT-1 || + y2 > DISPLAY_HEIGHT-1) return; + int dx = abs(x2-x1), sx = x1 dy) { err += dy; x1 += sx; } /* e_xy+e_x > 0 */ + if (e2 < dx) { err += dx; y1 += sy; } /* e_xy+e_y < 0 */ + } +} +void lcd_drawRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color){ + if( px1 > DISPLAY_WIDTH-1 || + px2 > DISPLAY_WIDTH-1 || + py1 > DISPLAY_HEIGHT-1 || + py2 > DISPLAY_HEIGHT-1) return; + lcd_drawLine(px1, py1, px2, py1, color); + lcd_drawLine(px2, py1, px2, py2, color); + lcd_drawLine(px2, py2, px1, py2, color); + lcd_drawLine(px1, py2, px1, py1, color); +} +void lcd_fillRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color){ + if( px1 > px2){ + uint8_t temp = px1; + px1 = px2; + px2 = temp; + temp = py1; + py1 = py2; + py2 = temp; + } + for (uint8_t i=0; i<=(py2-py1); i++){ + lcd_drawLine(px1, py1+i, px2, py1+i, color); + } +} +void lcd_drawCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color){ + if( ((center_x + radius) > DISPLAY_WIDTH-1) || + ((center_y + radius) > DISPLAY_HEIGHT-1) || + center_x < radius || + center_y < radius) return; + int16_t f = 1 - radius; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * radius; + int16_t x = 0; + int16_t y = radius; + + lcd_drawPixel(center_x , center_y+radius, color); + lcd_drawPixel(center_x , center_y-radius, color); + lcd_drawPixel(center_x+radius, center_y , color); + lcd_drawPixel(center_x-radius, center_y , color); + + while (x= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + lcd_drawPixel(center_x + x, center_y + y, color); + lcd_drawPixel(center_x - x, center_y + y, color); + lcd_drawPixel(center_x + x, center_y - y, color); + lcd_drawPixel(center_x - x, center_y - y, color); + lcd_drawPixel(center_x + y, center_y + x, color); + lcd_drawPixel(center_x - y, center_y + x, color); + lcd_drawPixel(center_x + y, center_y - x, color); + lcd_drawPixel(center_x - y, center_y - x, color); + } +} +void lcd_fillCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color) { + for(uint8_t i=0; i<= radius;i++){ + lcd_drawCircle(center_x, center_y, i, color); + } +} +void lcd_drawBitmap(uint8_t x, uint8_t y, const uint8_t *picture, uint8_t width, uint8_t height, uint8_t color){ + uint8_t i,j, byteWidth = (width+7)/8; + for (j = 0; j < height; j++) { + for(i=0; i < width;i++){ + if(pgm_read_byte(picture + j * byteWidth + i / 8) & (128 >> (i & 7))){ + lcd_drawPixel(x+i, y+j, color); + } else { + lcd_drawPixel(x+i, y+j, !color); + } + } + } +} +void lcd_display() { +#if defined (SSD1306) || defined (SSD1309) + lcd_gotoxy(0,0); + lcd_data(&displayBuffer[0][0], DISPLAY_WIDTH*DISPLAY_HEIGHT/8); +#elif defined SH1106 + for (uint8_t i = 0; i < DISPLAY_HEIGHT/8; i++){ + lcd_gotoxy(0,i); + lcd_data(displayBuffer[i], sizeof(displayBuffer[i])); + } +#endif +} +void lcd_clear_buffer() { + for (uint8_t i = 0; i < DISPLAY_HEIGHT/8; i++){ + memset(displayBuffer[i], 0x00, sizeof(displayBuffer[i])); + } +} +uint8_t lcd_check_buffer(uint8_t x, uint8_t y) { + if( x > DISPLAY_WIDTH-1 || y > (DISPLAY_HEIGHT-1)) return 0; // out of Display + return displayBuffer[(y / (DISPLAY_HEIGHT/8))][x] & (1 << (y % (DISPLAY_HEIGHT/8))); +} +void lcd_display_block(uint8_t x, uint8_t line, uint8_t width) { + if (line > (DISPLAY_HEIGHT/8-1) || x > DISPLAY_WIDTH - 1){return;} + if (x + width > DISPLAY_WIDTH) { // no -1 here, x alone is width 1 + width = DISPLAY_WIDTH - x; + } + lcd_goto_xpix_y(x,line); + lcd_data(&displayBuffer[line][x], width); +} +#endif diff --git a/firmware/oled-display/lcd.h b/firmware/oled-display/lcd.h new file mode 100644 index 0000000..a05ca2e --- /dev/null +++ b/firmware/oled-display/lcd.h @@ -0,0 +1,150 @@ +/* + * This file is part of lcd library for ssd1306/ssd1309/sh1106 oled-display. + * + * lcd library for ssd1306/ssd1309/sh1106 oled-display is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * + * lcd library for ssd1306/ssd1309/sh1106 oled-display is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Foobar. If not, see . + * + * Diese Datei ist Teil von lcd library for ssd1306/ssd1309/sh1106 oled-display. + * + * lcd library for ssd1306/ssd1309/sh1106 oled-display ist Freie Software: Sie können es unter den Bedingungen + * der GNU General Public License, wie von der Free Software Foundation, + * Version 3 der Lizenz oder jeder späteren + * veröffentlichten Version, weiterverbreiten und/oder modifizieren. + * + * lcd library for ssd1306/ssd1309/sh1106 oled-display wird in der Hoffnung, dass es nützlich sein wird, aber + * OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite + * Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. + * Siehe die GNU General Public License für weitere Details. + * + * Sie sollten eine Kopie der GNU General Public License zusammen mit diesem + * Programm erhalten haben. Wenn nicht, siehe . + * + * lcd.h + * + * Created by Michael Köhler on 22.12.16. + * Copyright 2016 Skie-Systems. All rights reserved. + * + * lib for OLED-Display with ssd1306/ssd1309/sh1106-Controller + * first dev-version only for I2C-Connection + * at ATMega328P like Arduino Uno + * + * at GRAPHICMODE lib needs SRAM for display + * DISPLAY-WIDTH * DISPLAY-HEIGHT + 2 bytes + */ + +#ifndef LCD_H +#define LCD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if (__GNUC__ * 100 + __GNUC_MINOR__) < 303 +#error "This library requires AVR-GCC 3.3 or later, update to newer AVR-GCC compiler !" +#endif + +#include +#include + + /* TODO: define bus */ +#define I2C // I2C or SPI + /* TODO: define displaycontroller */ +#define SSD1306 // SH1106 or SSD1306, check datasheet of your display + /* TODO: define displaymode */ +#define GRAPHICMODE // TEXTMODE for only text to display, + // GRAPHICMODE for text and graphic + /* TODO: define font */ +#define FONT ssd1306oled_font// set font here, refer font-name at font.h/font.c + + /* TODO: define I2C-adress for display */ + + // using 7-bit-adress for lcd-library + // if you use your own library for twi check I2C-adress-handle +#define LCD_I2C_ADR (0x7a >> 1) // 7 bit slave-adress without r/w-bit + // r/w-bit are set/unset by library + // e.g. 8 bit slave-adress: + // 0x78 = adress 0x3C with cleared r/w-bit (write-mode) + + +#ifdef I2C +#include "i2c.h" // library for I2C-communication + // if you want to use other lib for I2C + // edit i2c_xxx commands in this library + // i2c_start(), i2c_byte(), i2c_stop() + +#elif defined SPI + // if you want to use your other lib/function for SPI replace SPI-commands +#define LCD_PORT PORTB +#define LCD_DDR DDRB +#define RES_PIN PB0 +#define DC_PIN PB1 +#define CS_PIN PB2 + +#endif + +#ifndef YES +#define YES 1 +#endif + +#define NORMALSIZE 1 +#define DOUBLESIZE 2 + +#define LCD_DISP_OFF 0xAE +#define LCD_DISP_ON 0xAF + +#define WHITE 0x01 +#define BLACK 0x00 + +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 32 + + + + void lcd_command(uint8_t cmd[], uint8_t size); // transmit command to display + void lcd_data(uint8_t data[], uint16_t size); // transmit data to display + void lcd_init(uint8_t dispAttr); + void lcd_home(void); // set cursor to 0,0 + void lcd_invert(uint8_t invert); // invert display + void lcd_sleep(uint8_t sleep); // display goto sleep (power off) + void lcd_set_contrast(uint8_t contrast); // set contrast for display + void lcd_puts(const char* s); // print string, \n-terminated, from ram on screen (TEXTMODE) + // or buffer (GRAPHICMODE) + void lcd_puts_p(const char* progmem_s); // print string from flash on screen (TEXTMODE) + // or buffer (GRAPHICMODE) + + void lcd_clrscr(void); // clear screen (and buffer at GRFAICMODE) + void lcd_gotoxy(uint8_t x, uint8_t y); // set curser at pos x, y. x means character, + // y means line (page, refer lcd manual) + void lcd_goto_xpix_y(uint8_t x, uint8_t y); // set curser at pos x, y. x means pixel, + // y means line (page, refer lcd manual) + void lcd_putc(char c); // print character on screen at TEXTMODE + // at GRAPHICMODE print character to buffer + void lcd_charMode(uint8_t mode); // set size of chars +#if defined GRAPHICMODE + void lcd_set_buffer(uint8_t x, uint8_t y, uint8_t value); + void lcd_drawPixel(uint8_t x, uint8_t y, uint8_t color); + void lcd_drawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color); + void lcd_drawRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color); + void lcd_fillRect(uint8_t px1, uint8_t py1, uint8_t px2, uint8_t py2, uint8_t color); + void lcd_drawCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color); + void lcd_fillCircle(uint8_t center_x, uint8_t center_y, uint8_t radius, uint8_t color); + void lcd_drawBitmap(uint8_t x, uint8_t y, const uint8_t picture[], uint8_t width, uint8_t height, uint8_t color); + void lcd_display(void); // copy buffer to display RAM + void lcd_clear_buffer(void); // clear display buffer + uint8_t lcd_check_buffer(uint8_t x, uint8_t y); // read a pixel value from the display buffer + void lcd_display_block(uint8_t x, uint8_t line, uint8_t width); // display (part of) a display line +#endif + +#ifdef __cplusplus +} +#endif +#endif /* LCD_H */ diff --git a/firmware/oled-display/main.c b/firmware/oled-display/main.c new file mode 100644 index 0000000..3ad654c --- /dev/null +++ b/firmware/oled-display/main.c @@ -0,0 +1,19 @@ +//****main.c****// +#include "lcd.h" + + +int main(void){ + lcd_init(LCD_DISP_ON); // init lcd and turn on + + lcd_puts("Hello World"); // put string from RAM to display (TEXTMODE) or buffer (GRAPHICMODE) + lcd_gotoxy(0,2); // set cursor to first column at line 3 + lcd_puts_p(PSTR("String from flash")); // puts string form flash to display (TEXTMODE) or buffer (GRAPHICMODE) +#if defined GRAPHICMODE + lcd_drawCircle(64,32,7,WHITE); // draw circle to buffer white lines + lcd_display(); // send buffer to display +#endif + for(;;){ + //main loop + } + return 0; +} diff --git a/firmware/oled-display/readme.md b/firmware/oled-display/readme.md new file mode 100644 index 0000000..43e9eeb --- /dev/null +++ b/firmware/oled-display/readme.md @@ -0,0 +1,132 @@ +# OLED for AVR mikrocontrollers +Library for oled-displays with SSD1306, SSD1309 or SH1106 display-controller connected with I2C or SPI at an AVR Atmel Atmega like Atmega328P. + +Upstream: [https://github.com/Sylaina/oled-display/](https://github.com/Sylaina/oled-display/) + + + +This library allows you to display text or/and graphic at oled-display. +The library need less than 2 kilobytes flash-memory and 3 bytes sram in textmode, in graphicmode library need less than 3 kilobytes flash-memory and 1027 bytes static sram so you can use oled-displays e.g with Atmega48PA (only with textmode). +Library is only tested with 128x64 Pixel display, lower resolution not tested but should work too. + +If you want to use your own I2C library you have to fit i2c-function at lcd-library. +Settings for I2C-bus have to set at i2c.h +Settings for display have to set at lcd.h + +If you want to use characters like e.g. ä set your compiler input-charset to utf-8 and your compiler exec-charset to iso-8859-15 (look at makefile line 115). + +Testcondition: Display: SSD1306 OLED, Compiler Optimizelevel: -Os, µC: Atmega328p @ 8 MHz internal RC + +Memory: + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModulFlashStatic RAM
I2C-Core234 Bytes0 Bytes
FONT644 Bytes0 Bytes
OLED (Text-Mode)1449 Bytes3 Bytes
OLED (Graphic-Mode)2403 Bytes1027 Bytes
+ + + +Speed (print 20 charaters (1 line) in normal size to display): + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModeTimeI2C-Speed
OLED (Text-Mode)4.411 ms400 kHz
OLED (Text-Mode)15.384 ms100 kHz
OLED (Graphic-Mode)26.603 ms400 kHz
OLED (Graphic-Mode)96.294 ms100 kHz
+ + +example: + +```c +//****main.c****// +#include "lcd.h" + + +int main(void){ + lcd_init(LCD_DISP_ON); // init lcd and turn on + + lcd_puts("Hello World"); // put string from RAM to display (TEXTMODE) or buffer (GRAPHICMODE) + lcd_gotoxy(0,2); // set cursor to first column at line 3 + lcd_puts_p(PSTR("String from flash")); // puts string form flash to display (TEXTMODE) or buffer (GRAPHICMODE) +#if defined GRAPHICMODE + lcd_drawCircle(64,32,7,WHITE); // draw circle to buffer + lcd_display(); // send buffer to display +#endif + for(;;){ + //main loop + } + return 0; +} +``` +example for chars with double height: +```c +//****main.c****// +#include "lcd.h" + +int main(void){ + + lcd_init(LCD_DISP_ON); + lcd_clrscr(); + lcd_set_contrast(0x00); + lcd_gotoxy(4,1); + lcd_puts("Normal Size"); + lcd_charMode(DOUBLESIZE); + lcd_gotoxy(0,4); + lcd_puts(" Double \r\n Size"); + lcd_charMode(NORMALSIZE); + +#ifdef GRAPHICMODE + lcd_display(); +#endif + for(;;){ + //main loop + } + return 0; +} +``` + +