From 7b4e583635d2df73130f4ffbf83f3b8f69e3a5ff Mon Sep 17 00:00:00 2001 From: Fabrice Weinberg Date: Sun, 29 May 2016 15:35:33 +0200 Subject: [PATCH] Add abstraction for hardware interface --- SSD1306.cpp => OLEDDisplay.cpp | 251 +++++---------------------- OLEDDisplay.h | 244 ++++++++++++++++++++++++++ SSD1306Fonts.h => OLEDDisplayFonts.h | 4 + SSD1306Ui.cpp => OLEDDisplayUi.cpp | 67 +++---- SSD1306Ui.h => OLEDDisplayUi.h | 38 ++-- SSD1306.h | 237 +------------------------ SSD1306Brzo.h | 149 ++++++++++++++++ SSD1306Spi.h | 150 ++++++++++++++++ SSD1306Wire.h | 147 ++++++++++++++++ 9 files changed, 799 insertions(+), 488 deletions(-) rename SSD1306.cpp => OLEDDisplay.cpp (70%) create mode 100644 OLEDDisplay.h rename SSD1306Fonts.h => OLEDDisplayFonts.h (99%) rename SSD1306Ui.cpp => OLEDDisplayUi.cpp (85%) rename SSD1306Ui.h => OLEDDisplayUi.h (88%) create mode 100644 SSD1306Brzo.h create mode 100644 SSD1306Spi.h create mode 100644 SSD1306Wire.h diff --git a/SSD1306.cpp b/OLEDDisplay.cpp similarity index 70% rename from SSD1306.cpp rename to OLEDDisplay.cpp index e5e39c1..445d41b 100644 --- a/SSD1306.cpp +++ b/OLEDDisplay.cpp @@ -25,63 +25,54 @@ * Credits for parts of this code go to Mike Rankin. Thank you so much for sharing! */ -#include "SSD1306.h" +#include "OLEDDisplay.h" - -SSD1306::SSD1306(uint8_t i2cAddress, uint8_t sda, uint8_t sdc) { - this->i2cAddress = i2cAddress; - this->sda = sda; - this->sdc = sdc; -} - -bool SSD1306::init() { +bool OLEDDisplay::init() { + if (!this->connect()) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); + return false; + } this->buffer = (uint8_t*) malloc(sizeof(uint8_t) * DISPLAY_BUFFER_SIZE); if(!this->buffer) { - DEBUG_SSD1306("[SSD1306][init] Not enough memory to create display\n"); + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); return false; } - #ifdef SSD1306_DOUBLE_BUFFER + #ifdef OLEDDISPLAY_DOUBLE_BUFFER this->buffer_back = (uint8_t*) malloc(sizeof(uint8_t) * DISPLAY_BUFFER_SIZE); if(!this->buffer_back) { - DEBUG_SSD1306("[SSD1306][init] Not enough memory to create back buffer\n"); + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); free(this->buffer); return false; } #endif - reconnect(); sendInitCommands(); resetDisplay(); return true; } -void SSD1306::end() { +void OLEDDisplay::end() { if (this->buffer) free(this->buffer); - #ifdef SSD1306_DOUBLE_BUFFER + #ifdef OLEDDISPLAY_DOUBLE_BUFFER if (this->buffer_back) free(this->buffer_back); #endif } -void SSD1306::resetDisplay(void) { +void OLEDDisplay::resetDisplay(void) { clear(); - display(); -} - -void SSD1306::reconnect() { - #ifdef SSD1306_USE_BRZO - brzo_i2c_setup(this->sda, this->sdc, 2000); - #else - Wire.begin(this->sda, this->sdc); + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + memset(buffer_back, 1, DISPLAY_BUFFER_SIZE); #endif + display(); } -void SSD1306::setColor(SSD1306_COLOR color) { +void OLEDDisplay::setColor(OLEDDISPLAY_COLOR color) { this->color = color; } -void SSD1306::setPixel(int16_t x, int16_t y) { +void OLEDDisplay::setPixel(int16_t x, int16_t y) { if (x >= 0 && x < 128 && y >= 0 && y < 64) { switch (color) { case WHITE: buffer[x + (y / 8) * DISPLAY_WIDTH] |= (1 << (y & 7)); break; @@ -92,7 +83,7 @@ void SSD1306::setPixel(int16_t x, int16_t y) { } // Bresenham's algorithm - thx wikipedia and Adafruit_GFX -void SSD1306::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { +void OLEDDisplay::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { int16_t steep = abs(y1 - y0) > abs(x1 - x0); if (steep) { _swap_int16_t(x0, y0); @@ -131,20 +122,20 @@ void SSD1306::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { } } -void SSD1306::drawRect(int16_t x, int16_t y, int16_t width, int16_t height) { +void OLEDDisplay::drawRect(int16_t x, int16_t y, int16_t width, int16_t height) { drawHorizontalLine(x, y, width); drawVerticalLine(x, y, height); drawVerticalLine(x + width, y, height); drawHorizontalLine(x, y + height, width); } -void SSD1306::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height) { +void OLEDDisplay::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height) { for (int16_t i = yMove; i < yMove + height; i++) { drawHorizontalLine(xMove, i, width); } } -void SSD1306::drawCircle(int16_t x0, int16_t y0, int16_t radius) { +void OLEDDisplay::drawCircle(int16_t x0, int16_t y0, int16_t radius) { int16_t x = 0, y = radius; int16_t dp = 1 - radius; do { @@ -170,7 +161,7 @@ void SSD1306::drawCircle(int16_t x0, int16_t y0, int16_t radius) { setPixel(x0, y0 - radius); } -void SSD1306::fillCircle(int16_t x0, int16_t y0, int16_t radius) { +void OLEDDisplay::fillCircle(int16_t x0, int16_t y0, int16_t radius) { int16_t x = 0, y = radius; int16_t dp = 1 - radius; do { @@ -190,7 +181,7 @@ void SSD1306::fillCircle(int16_t x0, int16_t y0, int16_t radius) { } -void SSD1306::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { +void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { if (y < 0 || y >= DISPLAY_HEIGHT) { return; } if (x < 0) { @@ -223,7 +214,7 @@ void SSD1306::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { } } -void SSD1306::drawVerticalLine(int16_t x, int16_t y, int16_t length) { +void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { if (y < 0 || y > DISPLAY_HEIGHT) return; if (x < 0) { @@ -292,7 +283,7 @@ void SSD1306::drawVerticalLine(int16_t x, int16_t y, int16_t length) { } } -void SSD1306::drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress) { +void OLEDDisplay::drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress) { uint16_t radius = height / 2; uint16_t innerRadius = radius - 3; setColor(WHITE); @@ -309,11 +300,11 @@ void SSD1306::drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t h } -void SSD1306::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *image) { +void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *image) { drawInternal(xMove, yMove, width, height, image, 0, 0); } -void SSD1306::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *xbm) { +void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *xbm) { int16_t widthInXbm = (width + 7) / 8; uint8_t data; @@ -332,7 +323,7 @@ void SSD1306::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t heigh } } -void SSD1306::drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth) { +void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth) { uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); uint8_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); uint16_t sizeOfJumpTable = pgm_read_byte(fontData + CHAR_NUM_POS) * JUMPTABLE_BYTES; @@ -383,7 +374,7 @@ void SSD1306::drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint1 } -void SSD1306::drawString(int16_t xMove, int16_t yMove, String strUser) { +void OLEDDisplay::drawString(int16_t xMove, int16_t yMove, String strUser) { uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); // char* text must be freed! @@ -412,7 +403,7 @@ void SSD1306::drawString(int16_t xMove, int16_t yMove, String strUser) { free(text); } -void SSD1306::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxLineWidth, String strUser) { +void OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxLineWidth, String strUser) { uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); @@ -452,7 +443,7 @@ void SSD1306::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxLineW free(text); } -uint16_t SSD1306::getStringWidth(const char* text, uint16_t length) { +uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) { uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); uint16_t stringWidth = 0; @@ -469,7 +460,7 @@ uint16_t SSD1306::getStringWidth(const char* text, uint16_t length) { return max(maxWidth, stringWidth); } -uint16_t SSD1306::getStringWidth(String strUser) { +uint16_t OLEDDisplay::getStringWidth(String strUser) { char* text = utf8ascii(strUser); uint16_t length = strlen(text); uint16_t width = getStringWidth(text, length); @@ -477,194 +468,46 @@ uint16_t SSD1306::getStringWidth(String strUser) { return width; } -void SSD1306::setTextAlignment(SSD1306_TEXT_ALIGNMENT textAlignment) { +void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { this->textAlignment = textAlignment; } -void SSD1306::setFont(const char *fontData) { +void OLEDDisplay::setFont(const char *fontData) { this->fontData = fontData; } -void SSD1306::displayOn(void) { +void OLEDDisplay::displayOn(void) { sendCommand(DISPLAYON); } -void SSD1306::displayOff(void) { +void OLEDDisplay::displayOff(void) { sendCommand(DISPLAYOFF); } -void SSD1306::invertDisplay(void) { +void OLEDDisplay::invertDisplay(void) { sendCommand(INVERTDISPLAY); } -void SSD1306::normalDisplay(void) { +void OLEDDisplay::normalDisplay(void) { sendCommand(NORMALDISPLAY); } -void SSD1306::setContrast(char contrast) { +void OLEDDisplay::setContrast(char contrast) { sendCommand(SETCONTRAST); sendCommand(contrast); } -void SSD1306::flipScreenVertically() { - sendCommand(SEGREMAP | 0x01); //Rotate screen 180 deg +void OLEDDisplay::flipScreenVertically() { + sendCommand(SEGREMAP | 0x01); sendCommand(COMSCANDEC); //Rotate screen 180 Deg } -void SSD1306::display(void) { - #ifdef SSD1306_DOUBLE_BUFFER - uint16_t minBoundY = ~0; - uint16_t maxBoundY = 0; - - uint16_t minBoundX = ~0; - uint16_t maxBoundX = 0; - - uint16_t x, y; - - // Calculate the Y bounding box of changes - // and copy buffer[pos] to buffer_back[pos]; - for (y = 0; y < 8; y++) { - for (x = 0; x < DISPLAY_WIDTH; x++) { - uint16_t pos = x + y * DISPLAY_WIDTH; - if (buffer[pos] != buffer_back[pos]) { - minBoundY = min(minBoundY, y); - maxBoundY = max(maxBoundY, y); - minBoundX = min(minBoundX, x); - maxBoundX = max(maxBoundX, x); - } - buffer_back[pos] = buffer[pos]; - } - yield(); - } - - // If the minBoundY wasn't updated - // we can savely assume that buffer_back[pos] == buffer[pos] - // holdes true for all values of pos - if (minBoundY == ~0) return; - - sendCommand(COLUMNADDR); - sendCommand(minBoundX); - sendCommand(maxBoundX); - - sendCommand(PAGEADDR); - sendCommand(minBoundY); - sendCommand(maxBoundY); - - byte k = 0; - #ifdef SSD1306_USE_BRZO - uint8_t sendBuffer[17]; - sendBuffer[0] = 0x40; - brzo_i2c_start_transaction(this->i2cAddress, BRZO_I2C_SPEED); - for (y = minBoundY; y <= maxBoundY; y++) { - for (x = minBoundX; x <= maxBoundX; x++) { - k++; - sendBuffer[k] = buffer[x + y * DISPLAY_WIDTH]; - if (k == 16) { - brzo_i2c_write(sendBuffer, 17, true); - k = 0; - } - } - yield(); - } - brzo_i2c_write(sendBuffer, k + 1, true); - brzo_i2c_end_transaction(); - #else - for (y = minBoundY; y <= maxBoundY; y++) { - for (x = minBoundX; x <= maxBoundX; x++) { - if (k == 0) { - Wire.beginTransmission(this->i2cAddress); - Wire.write(0x40); - } - Wire.write(buffer[x + y * DISPLAY_WIDTH]); - k++; - if (k == 16) { - Wire.endTransmission(); - k = 0; - } - } - yield(); - } - - if (k != 0) { - Wire.endTransmission(); - } - #endif - - #else - // No double buffering - sendCommand(COLUMNADDR); - sendCommand(0x0); - sendCommand(0x7F); - - sendCommand(PAGEADDR); - sendCommand(0x0); - sendCommand(0x7); - - #ifdef SSD1306_USE_BRZO - uint8_t sendBuffer[17]; - sendBuffer[0] = 0x40; - brzo_i2c_start_transaction(this->i2cAddress, BRZO_I2C_SPEED); - for (uint16_t i=0; ii2cAddress); - Wire.write(0x40); - } - Wire.write(buffer[x + y * DISPLAY_WIDTH]); - k++; - if (k == 16) { - Wire.endTransmission(); - k = 0; - } - } - yield(); - } - - if (k != 0) { - Wire.endTransmission(); - } - #endif - #endif -} - - -void SSD1306::clear(void) { +void OLEDDisplay::clear(void) { memset(buffer, 0, DISPLAY_BUFFER_SIZE); - #ifdef SSD1306_DOUBLE_BUFFER - memset(buffer_back, 1, DISPLAY_BUFFER_SIZE); - #endif } - // Private functions - -void SSD1306::sendCommand(unsigned char com) { - #ifdef SSD1306_USE_BRZO - uint8_t command[2] = {0x80 /* command mode */, com}; - brzo_i2c_start_transaction(this->i2cAddress, BRZO_I2C_SPEED); - brzo_i2c_write(command, 2, true); - brzo_i2c_end_transaction(); - #else - Wire.beginTransmission(this->i2cAddress); //begin transmitting - Wire.write(0x80); //command mode - Wire.write(com); - Wire.endTransmission(); // stop transmitting - #endif -} - -void SSD1306::sendInitCommands(void) { +void OLEDDisplay::sendInitCommands(void) { sendCommand(DISPLAYOFF); sendCommand(SETDISPLAYCLOCKDIV); sendCommand(0xF0); // Increase speed of the display max ~96Hz @@ -691,7 +534,7 @@ void SSD1306::sendInitCommands(void) { sendCommand(DISPLAYON); } -void SSD1306::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *data, uint16_t offset, uint16_t bytesInData) { +void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *data, uint16_t offset, uint16_t bytesInData) { if (width < 0 || height < 0) return; if (yMove + height < 0 || yMove > DISPLAY_HEIGHT) return; if (xMove + width < 0 || xMove > DISPLAY_WIDTH) return; @@ -760,7 +603,7 @@ void SSD1306::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t } // Code form http://playground.arduino.cc/Main/Utf8ascii -uint8_t SSD1306::utf8ascii(byte ascii) { +uint8_t OLEDDisplay::utf8ascii(byte ascii) { static uint8_t LASTCHAR; if ( ascii < 128 ) { // Standard ASCII-set 0..0x7F handling @@ -781,14 +624,14 @@ uint8_t SSD1306::utf8ascii(byte ascii) { } // You need to free the char! -char* SSD1306::utf8ascii(String str) { +char* OLEDDisplay::utf8ascii(String str) { uint16_t k = 0; uint16_t length = str.length() + 1; // Copy the string into a char array char* s = (char*) malloc(length * sizeof(char)); if(!s) { - DEBUG_SSD1306("[SSD1306][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n"); + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n"); return (char*) str.c_str(); } str.toCharArray(s, length); diff --git a/OLEDDisplay.h b/OLEDDisplay.h new file mode 100644 index 0000000..226ecef --- /dev/null +++ b/OLEDDisplay.h @@ -0,0 +1,244 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2016 by Daniel Eichhorn + * Copyright (c) 2016 by Fabrice Weinberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Credits for parts of this code go to Mike Rankin. Thank you so much for sharing! + */ + +#ifndef OLEDDISPLAY_h +#define OLEDDISPLAY_h + +#include +#include "OLEDDisplayFonts.h" + +//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ ) + +#ifndef DEBUG_OLEDDISPLAY +#define DEBUG_OLEDDISPLAY(...) +#endif + +// Use DOUBLE BUFFERING by default +#ifndef OLEDDISPLAY_REDUCE_MEMORY +#define OLEDDISPLAY_DOUBLE_BUFFER +#endif + + +// Display settings +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 64 +#define DISPLAY_BUFFER_SIZE 1024 + +// Header Values +#define JUMPTABLE_BYTES 4 + +#define JUMPTABLE_LSB 1 +#define JUMPTABLE_SIZE 2 +#define JUMPTABLE_WIDTH 3 +#define JUMPTABLE_START 4 + +#define WIDTH_POS 0 +#define HEIGHT_POS 1 +#define FIRST_CHAR_POS 2 +#define CHAR_NUM_POS 3 + + +// Display commands +#define CHARGEPUMP 0x8D +#define COLUMNADDR 0x21 +#define COMSCANDEC 0xC8 +#define COMSCANINC 0xC0 +#define DISPLAYALLON 0xA5 +#define DISPLAYALLON_RESUME 0xA4 +#define DISPLAYOFF 0xAE +#define DISPLAYON 0xAF +#define EXTERNALVCC 0x1 +#define INVERTDISPLAY 0xA7 +#define MEMORYMODE 0x20 +#define NORMALDISPLAY 0xA6 +#define PAGEADDR 0x22 +#define SEGREMAP 0xA0 +#define SETCOMPINS 0xDA +#define SETCONTRAST 0x81 +#define SETDISPLAYCLOCKDIV 0xD5 +#define SETDISPLAYOFFSET 0xD3 +#define SETHIGHCOLUMN 0x10 +#define SETLOWCOLUMN 0x00 +#define SETMULTIPLEX 0xA8 +#define SETPRECHARGE 0xD9 +#define SETSEGMENTREMAP 0xA1 +#define SETSTARTLINE 0x40 +#define SETVCOMDETECT 0xDB +#define SWITCHCAPVCC 0x2 + +#ifndef _swap_int16_t +#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; } +#endif + +enum OLEDDISPLAY_COLOR { + BLACK = 0, + WHITE = 1, + INVERSE = 2 +}; + +enum OLEDDISPLAY_TEXT_ALIGNMENT { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_RIGHT = 1, + TEXT_ALIGN_CENTER = 2, + TEXT_ALIGN_CENTER_BOTH = 3 +}; + + +class OLEDDisplay { + public: + // Initialize the display + bool init(); + + // Free the memory used by the display + void end(); + + // Cycle through the initialization + void resetDisplay(void); + + /* Drawing functions */ + // Sets the color of all pixel operations + void setColor(OLEDDISPLAY_COLOR color); + + // Draw a pixel at given position + void setPixel(int16_t x, int16_t y); + + // Draw a line from position 0 to position 1 + void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1); + + // Draw the border of a rectangle at the given location + void drawRect(int16_t x, int16_t y, int16_t width, int16_t height); + + // Fill the rectangle + void fillRect(int16_t x, int16_t y, int16_t width, int16_t height); + + // Draw the border of a circle + void drawCircle(int16_t x, int16_t y, int16_t radius); + + // Fill circle + void fillCircle(int16_t x, int16_t y, int16_t radius); + + // Draw a line horizontally + void drawHorizontalLine(int16_t x, int16_t y, int16_t length); + + // Draw a lin vertically + void drawVerticalLine(int16_t x, int16_t y, int16_t length); + + /** + * Draws a rounded progress bar with the outer dimensions given by width and height. + */ + void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress); + + // Draw a bitmap in the internal image format + void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const char *image); + + // Draw a XBM + void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const char *xbm); + + /* Text functions */ + + // Draws a string at the given location + void drawString(int16_t x, int16_t y, String text); + + // Draws a String with a maximum width at the given location. + // If the given String is wider than the specified width + // The text will be wrapped to the next line at a space or dash + void drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, String text); + + // Returns the width of the const char* with the current + // font settings + uint16_t getStringWidth(const char* text, uint16_t length); + + // Convencience method for the const char version + uint16_t getStringWidth(String text); + + // Specifies relative to which anchor point + // the text is rendered. Available constants: + // TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH + void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment); + + // Sets the current font. Available default fonts + // ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24 + void setFont(const char *fontData); + + /* Display functions */ + + // Turn the display on + void displayOn(void); + + // Turn the display offs + void displayOff(void); + + // Inverted display mode + void invertDisplay(void); + + // Normal display mode + void normalDisplay(void); + + // Set display contrast + void setContrast(char contrast); + + // Turn the display upside down + void flipScreenVertically(); + + // Write the buffer to the display memory + virtual void display(void); + + // Clear the local pixel buffer + void clear(void); + + uint8_t *buffer; + + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t *buffer_back; + #endif + + protected: + OLEDDISPLAY_TEXT_ALIGNMENT textAlignment = TEXT_ALIGN_LEFT; + OLEDDISPLAY_COLOR color = WHITE; + + const char *fontData = ArialMT_Plain_10; + + // Send a command to the display (low level function) + virtual void sendCommand(uint8_t com); + + // Connect to the display + virtual bool connect(); + + // Send all the init commands + void sendInitCommands(); + + // converts utf8 characters to extended ascii + static char* utf8ascii(String s); + static byte utf8ascii(byte ascii); + + void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline)); + + void drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth); + +}; + +#endif diff --git a/SSD1306Fonts.h b/OLEDDisplayFonts.h similarity index 99% rename from SSD1306Fonts.h rename to OLEDDisplayFonts.h index 41d91f8..6dd21ef 100644 --- a/SSD1306Fonts.h +++ b/OLEDDisplayFonts.h @@ -1,3 +1,6 @@ +#ifndef OLEDDISPLAYFONTS_h +#define OLEDDISPLAYFONTS_h + const char ArialMT_Plain_10[] PROGMEM = { 0x0A, // Width: 10 0x0D, // Height: 13 @@ -1268,3 +1271,4 @@ const char ArialMT_Plain_24[] PROGMEM = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0xFF,0xFF,0x07,0xE0,0xFF,0xFF,0x07,0x00,0x1C,0x18,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x06,0x30,0x00,0x00,0x0E,0x38,0x00,0x00,0x1C,0x1C,0x00,0x00,0xF8,0x0F,0x00,0x00,0xF0,0x03, // 254 0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x7E,0x00,0x06,0xC0,0xF0,0x01,0x06,0xC0,0x80,0x0F,0x07,0x00,0x00,0xFE,0x03,0x00,0x00,0xFC,0x00,0xC0,0xC0,0x1F,0x00,0xC0,0xF8,0x03,0x00,0x00,0x3E,0x00,0x00,0x00,0x06 // 255 }; +#endif diff --git a/SSD1306Ui.cpp b/OLEDDisplayUi.cpp similarity index 85% rename from SSD1306Ui.cpp rename to OLEDDisplayUi.cpp index d6d7bda..5f6499c 100644 --- a/SSD1306Ui.cpp +++ b/OLEDDisplayUi.cpp @@ -24,17 +24,17 @@ * */ -#include "SSD1306Ui.h" +#include "OLEDDisplayUi.h" -SSD1306Ui::SSD1306Ui(SSD1306 *display) { +OLEDDisplayUi::OLEDDisplayUi(OLEDDisplay *display) { this->display = display; } -void SSD1306Ui::init() { +void OLEDDisplayUi::init() { this->display->init(); } -void SSD1306Ui::setTargetFPS(uint8_t fps){ +void OLEDDisplayUi::setTargetFPS(uint8_t fps){ float oldInterval = this->updateInterval; this->updateInterval = ((float) 1.0 / (float) fps) * 1000; @@ -46,68 +46,68 @@ void SSD1306Ui::setTargetFPS(uint8_t fps){ // -/------ Automatic controll ------\- -void SSD1306Ui::enableAutoTransition(){ +void OLEDDisplayUi::enableAutoTransition(){ this->autoTransition = true; } -void SSD1306Ui::disableAutoTransition(){ +void OLEDDisplayUi::disableAutoTransition(){ this->autoTransition = false; } -void SSD1306Ui::setAutoTransitionForwards(){ +void OLEDDisplayUi::setAutoTransitionForwards(){ this->state.frameTransitionDirection = 1; this->lastTransitionDirection = 1; } -void SSD1306Ui::setAutoTransitionBackwards(){ +void OLEDDisplayUi::setAutoTransitionBackwards(){ this->state.frameTransitionDirection = -1; this->lastTransitionDirection = -1; } -void SSD1306Ui::setTimePerFrame(uint16_t time){ +void OLEDDisplayUi::setTimePerFrame(uint16_t time){ this->ticksPerFrame = (int) ( (float) time / (float) updateInterval); } -void SSD1306Ui::setTimePerTransition(uint16_t time){ +void OLEDDisplayUi::setTimePerTransition(uint16_t time){ this->ticksPerTransition = (int) ( (float) time / (float) updateInterval); } // -/------ Customize indicator position and style -------\- -void SSD1306Ui::enableIndicator(){ +void OLEDDisplayUi::enableIndicator(){ this->state.isIndicatorDrawen = true; } -void SSD1306Ui::disableIndicator(){ +void OLEDDisplayUi::disableIndicator(){ this->state.isIndicatorDrawen = false; } -void SSD1306Ui::setIndicatorPosition(IndicatorPosition pos) { +void OLEDDisplayUi::setIndicatorPosition(IndicatorPosition pos) { this->indicatorPosition = pos; } -void SSD1306Ui::setIndicatorDirection(IndicatorDirection dir) { +void OLEDDisplayUi::setIndicatorDirection(IndicatorDirection dir) { this->indicatorDirection = dir; } -void SSD1306Ui::setActiveSymbol(const char* symbol) { +void OLEDDisplayUi::setActiveSymbol(const char* symbol) { this->activeSymbol = symbol; } -void SSD1306Ui::setInactiveSymbol(const char* symbol) { +void OLEDDisplayUi::setInactiveSymbol(const char* symbol) { this->inactiveSymbol = symbol; } + // -/----- Frame settings -----\- -void SSD1306Ui::setFrameAnimation(AnimationDirection dir) { +void OLEDDisplayUi::setFrameAnimation(AnimationDirection dir) { this->frameAnimationDirection = dir; } -void SSD1306Ui::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) { +void OLEDDisplayUi::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) { this->frameCount = frameCount; this->frameFunctions = frameFunctions; - this->state.currentFrame = 0; } // -/----- Overlays ------\- -void SSD1306Ui::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){ +void OLEDDisplayUi::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){ this->overlayCount = overlayCount; this->overlayFunctions = overlayFunctions; } // -/----- Loading Process -----\- -void SSD1306Ui::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) { +void OLEDDisplayUi::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) { display->setTextAlignment(TEXT_ALIGN_CENTER); display->setFont(ArialMT_Plain_10); @@ -133,7 +133,7 @@ void SSD1306Ui::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) { } // -/----- Manuel control -----\- -void SSD1306Ui::nextFrame() { +void OLEDDisplayUi::nextFrame() { if (this->state.frameState != IN_TRANSITION) { this->state.manuelControll = true; this->state.frameState = IN_TRANSITION; @@ -142,7 +142,7 @@ void SSD1306Ui::nextFrame() { this->state.frameTransitionDirection = 1; } } -void SSD1306Ui::previousFrame() { +void OLEDDisplayUi::previousFrame() { if (this->state.frameState != IN_TRANSITION) { this->state.manuelControll = true; this->state.frameState = IN_TRANSITION; @@ -154,25 +154,26 @@ void SSD1306Ui::previousFrame() { // -/----- State information -----\- -SSD1306UiState* SSD1306Ui::getUiState(){ +OLEDDisplayUiState* OLEDDisplayUi::getUiState(){ return &this->state; } -int8_t SSD1306Ui::update(){ - int8_t timeBudget = this->updateInterval - (millis() - this->state.lastUpdate); +int8_t OLEDDisplayUi::update(){ + long frameStart = millis(); + int8_t timeBudget = this->updateInterval - (frameStart - this->state.lastUpdate); if ( timeBudget <= 0) { // Implement frame skipping to ensure time budget is keept if (this->autoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil(-timeBudget / this->updateInterval); - this->state.lastUpdate = millis(); + this->state.lastUpdate = frameStart; this->tick(); } - return timeBudget; + return this->updateInterval - (millis() - frameStart); } -void SSD1306Ui::tick() { +void OLEDDisplayUi::tick() { this->state.ticksSinceLastStateSwitch++; switch (this->state.frameState) { @@ -205,7 +206,7 @@ void SSD1306Ui::tick() { this->display->display(); } -void SSD1306Ui::drawFrame(){ +void OLEDDisplayUi::drawFrame(){ switch (this->state.frameState){ case IN_TRANSITION: { float progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition; @@ -280,7 +281,7 @@ void SSD1306Ui::drawFrame(){ } } -void SSD1306Ui::drawIndicator() { +void OLEDDisplayUi::drawIndicator() { // Only draw if the indicator is invisible // for both frames or @@ -352,13 +353,13 @@ void SSD1306Ui::drawIndicator() { } } -void SSD1306Ui::drawOverlays() { +void OLEDDisplayUi::drawOverlays() { for (uint8_t i=0;ioverlayCount;i++){ (this->overlayFunctions[i])(this->display, &this->state); } } -uint8_t SSD1306Ui::getNextFrameNumber(){ +uint8_t OLEDDisplayUi::getNextFrameNumber(){ int8_t nextFrame = (this->state.currentFrame + this->state.frameTransitionDirection) % this->frameCount; if (nextFrame < 0){ nextFrame = this->frameCount + nextFrame; diff --git a/SSD1306Ui.h b/OLEDDisplayUi.h similarity index 88% rename from SSD1306Ui.h rename to OLEDDisplayUi.h index 690e37e..560c27c 100644 --- a/SSD1306Ui.h +++ b/OLEDDisplayUi.h @@ -24,15 +24,16 @@ * */ -#pragma once +#ifndef OLEDDISPLAYUI_h +#define OLEDDISPLAYUI_h #include -#include "SSD1306.h" +#include "OLEDDisplay.h" -//#define DEBUG_SSD1306Ui(...) Serial.printf( __VA_ARGS__ ) +//#define DEBUG_OLEDDISPLAYUI(...) Serial.printf( __VA_ARGS__ ) -#ifndef DEBUG_SSD1306Ui -#define DEBUG_SSD1306Ui(...) +#ifndef DEBUG_OLEDDISPLAYUI +#define DEBUG_OLEDDISPLAYUI(...) #endif enum AnimationDirection { @@ -70,7 +71,7 @@ const char ANIMATION_inactiveSymbol[] PROGMEM = { // Structure of the UiState -struct SSD1306UiState { +struct OLEDDisplayUiState { u_int64_t lastUpdate = 0; uint16_t ticksSinceLastStateSwitch = 0; @@ -93,13 +94,13 @@ struct LoadingStage { void (*callback)(); }; -typedef void (*FrameCallback)(SSD1306 *display, SSD1306UiState* state, int16_t x, int16_t y); -typedef void (*OverlayCallback)(SSD1306 *display, SSD1306UiState* state); -typedef void (*LoadingDrawFunction)(SSD1306 *display, LoadingStage* stage, uint8_t progress); +typedef void (*FrameCallback)(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y); +typedef void (*OverlayCallback)(OLEDDisplay *display, OLEDDisplayUiState* state); +typedef void (*LoadingDrawFunction)(OLEDDisplay *display, LoadingStage* stage, uint8_t progress); -class SSD1306Ui { +class OLEDDisplayUi { private: - SSD1306 *display; + OLEDDisplay *display; // Symbols for the Indicator IndicatorPosition indicatorPosition = BOTTOM; @@ -133,16 +134,13 @@ class SSD1306Ui { uint8_t indicatorDrawState = 1; // Loading screen - LoadingDrawFunction loadingDrawFunction = [](SSD1306 *display, LoadingStage* stage, uint8_t progress) { + LoadingDrawFunction loadingDrawFunction = [](OLEDDisplay *display, LoadingStage* stage, uint8_t progress) { display->drawString(64, 20, stage->process); - - // Draw a progress bar. - display->drawRect(4, 32, 120, 8); - display->fillRect(4 + 2, 32 + 2, (120 * ((float)progress / 100)) - 3, 8 - 3); + display->drawProgressBar(4, 32, 120, 8, progress); }; // UI State - SSD1306UiState state; + OLEDDisplayUiState state; // Bookeeping for update uint8_t updateInterval = 33; @@ -155,7 +153,7 @@ class SSD1306Ui { public: - SSD1306Ui(SSD1306 *display); + OLEDDisplayUi(OLEDDisplay *display); /** * Initialise the display @@ -231,6 +229,7 @@ class SSD1306Ui { */ void setInactiveSymbol(const char* symbol); + // Frame settings /** @@ -270,7 +269,8 @@ class SSD1306Ui { void previousFrame(); // State Info - SSD1306UiState* getUiState(); + OLEDDisplayUiState* getUiState(); int8_t update(); }; +#endif diff --git a/SSD1306.h b/SSD1306.h index 2c4400a..f3b7909 100644 --- a/SSD1306.h +++ b/SSD1306.h @@ -25,239 +25,12 @@ * Credits for parts of this code go to Mike Rankin. Thank you so much for sharing! */ -#pragma once +#ifndef SSD1306_h +#define SSD1306_h +#include "SSD1306Wire.h" -#include -#include "SSD1306Fonts.h" - -#ifdef SSD1306_USE_BRZO - #include "brzo_i2c.h" - // Brzo can handle 1Mhz in ESP8266 160Mhz mode - // and 800KHz in 80Mhz mode - #if F_CPU == 160000000L - #define BRZO_I2C_SPEED 1000 - #else - #define BRZO_I2C_SPEED 800 - #endif -#else // Default use Wire - #include -#endif - - -//#define DEBUG_SSD1306(...) Serial.printf( __VA_ARGS__ ) - -#ifndef DEBUG_SSD1306 -#define DEBUG_SSD1306(...) -#endif - -// Use DOUBLE BUFFERING by default -#ifndef SSD1306_REDUCE_MEMORY -#define SSD1306_DOUBLE_BUFFER -#endif +// For legacy support make SSD1306 an alias for SSD1306 +typedef SSD1306Wire SSD1306; -// Display settings -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_BUFFER_SIZE 1024 - -// Header Values -#define JUMPTABLE_BYTES 4 - -#define JUMPTABLE_LSB 1 -#define JUMPTABLE_SIZE 2 -#define JUMPTABLE_WIDTH 3 -#define JUMPTABLE_START 4 - -#define WIDTH_POS 0 -#define HEIGHT_POS 1 -#define FIRST_CHAR_POS 2 -#define CHAR_NUM_POS 3 - - -// Display commands -#define CHARGEPUMP 0x8D -#define COLUMNADDR 0x21 -#define COMSCANDEC 0xC8 -#define COMSCANINC 0xC0 -#define DISPLAYALLON 0xA5 -#define DISPLAYALLON_RESUME 0xA4 -#define DISPLAYOFF 0xAE -#define DISPLAYON 0xAF -#define EXTERNALVCC 0x1 -#define INVERTDISPLAY 0xA7 -#define MEMORYMODE 0x20 -#define NORMALDISPLAY 0xA6 -#define PAGEADDR 0x22 -#define SEGREMAP 0xA0 -#define SETCOMPINS 0xDA -#define SETCONTRAST 0x81 -#define SETDISPLAYCLOCKDIV 0xD5 -#define SETDISPLAYOFFSET 0xD3 -#define SETHIGHCOLUMN 0x10 -#define SETLOWCOLUMN 0x00 -#define SETMULTIPLEX 0xA8 -#define SETPRECHARGE 0xD9 -#define SETSEGMENTREMAP 0xA1 -#define SETSTARTLINE 0x40 -#define SETVCOMDETECT 0xDB -#define SWITCHCAPVCC 0x2 - -#ifndef _swap_int16_t -#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; } #endif - -enum SSD1306_COLOR { - BLACK = 0, - WHITE = 1, - INVERSE = 2 -}; - -enum SSD1306_TEXT_ALIGNMENT { - TEXT_ALIGN_LEFT = 0, - TEXT_ALIGN_RIGHT = 1, - TEXT_ALIGN_CENTER = 2, - TEXT_ALIGN_CENTER_BOTH = 3 -}; - -class SSD1306 { - private: - - uint8_t i2cAddress; - uint8_t sda; - uint8_t sdc; - - uint8_t *buffer; - - #ifdef SSD1306_DOUBLE_BUFFER - uint8_t *buffer_back; - #endif - - SSD1306_TEXT_ALIGNMENT textAlignment = TEXT_ALIGN_LEFT; - SSD1306_COLOR color = WHITE; - - const char *fontData = ArialMT_Plain_10; - - // Send a command to the display (low level function) - void sendCommand(unsigned char com); - - // Send all the init commands - void sendInitCommands(void); - - // converts utf8 characters to extended ascii - byte utf8ascii(byte ascii); - char* utf8ascii(String s); - - void drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline)); - - void drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth); - public: - - // Create the display object connected to pin sda and sdc - SSD1306(uint8_t i2cAddress, uint8_t sda, uint8_t sdc); - - // Initialize the display - bool init(); - - // Free the memory used by the display - void end(); - - // Cycle through the initialization - void resetDisplay(void); - - // Connect again to the display through I2C - void reconnect(void); - - /* Drawing functions */ - - // Sets the color of all pixel operations - void setColor(SSD1306_COLOR color); - - // Draw a pixel at given position - void setPixel(int16_t x, int16_t y); - - // Draw a line from position 0 to position 1 - void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1); - - // Draw the border of a rectangle at the given location - void drawRect(int16_t x, int16_t y, int16_t width, int16_t height); - - // Fill the rectangle - void fillRect(int16_t x, int16_t y, int16_t width, int16_t height); - - // Draw the border of a circle - void drawCircle(int16_t x, int16_t y, int16_t radius); - - // Fill circle - void fillCircle(int16_t x, int16_t y, int16_t radius); - - // Draw a line horizontally - void drawHorizontalLine(int16_t x, int16_t y, int16_t length); - - // Draw a lin vertically - void drawVerticalLine(int16_t x, int16_t y, int16_t length); - - /** - * Draws a rounded progress bar with the outer dimensions given by width and height. - */ - void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress); - - // Draw a bitmap in the internal image format - void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const char *image); - - // Draw a XBM - void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const char *xbm); - - /* Text functions */ - - // Draws a string at the given location - void drawString(int16_t x, int16_t y, String text); - - // Draws a String with a maximum width at the given location. - // If the given String is wider than the specified width - // The text will be wrapped to the next line at a space or dash - void drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, String text); - - // Returns the width of the const char* with the current - // font settings - uint16_t getStringWidth(const char* text, uint16_t length); - - // Convencience method for the const char version - uint16_t getStringWidth(String text); - - // Specifies relative to which anchor point - // the text is rendered. Available constants: - // TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH - void setTextAlignment(SSD1306_TEXT_ALIGNMENT textAlignment); - - // Sets the current font. Available default fonts - // ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24 - void setFont(const char *fontData); - - /* Display functions */ - - // Turn the display on - void displayOn(void); - - // Turn the display offs - void displayOff(void); - - // Inverted display mode - void invertDisplay(void); - - // Normal display mode - void normalDisplay(void); - - // Set display contrast - void setContrast(char contrast); - - // Turn the display upside down - void flipScreenVertically(); - - // Write the buffer to the display memory - void display(void); - - // Clear the local pixel buffer - void clear(void); - -}; diff --git a/SSD1306Brzo.h b/SSD1306Brzo.h new file mode 100644 index 0000000..8098aec --- /dev/null +++ b/SSD1306Brzo.h @@ -0,0 +1,149 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2016 by Daniel Eichhorn + * Copyright (c) 2016 by Fabrice Weinberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Credits for parts of this code go to Mike Rankin. Thank you so much for sharing! + */ + +#ifndef SSD1306Brzo_h +#define SSD1306Brzo_h + +#include "OLEDDisplay.h" +#include "brzo_i2c.h" + +#if F_CPU == 160000000L + #define BRZO_I2C_SPEED 1000 +#else + #define BRZO_I2C_SPEED 800 +#endif + +class SSD1306Brzo : public OLEDDisplay { + private: + uint8_t _address; + uint8_t _sda; + uint8_t _scl; + + public: + SSD1306Brzo(uint8_t _address, uint8_t _sda, uint8_t _scl) { + this->_address = _address; + this->_sda = _sda; + this->_scl = _scl; + } + + bool connect(){ + brzo_i2c_setup(_sda, _scl, 0); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = ~0; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = ~0; + uint8_t maxBoundX = 0; + + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (DISPLAY_HEIGHT / 8); y++) { + for (x = 0; x < DISPLAY_WIDTH; x++) { + uint16_t pos = x + y * DISPLAY_WIDTH; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = _min(minBoundY, y); + maxBoundY = _max(maxBoundY, y); + minBoundX = _min(minBoundX, x); + maxBoundX = _max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == ~0) return; + + sendCommand(COLUMNADDR); + sendCommand(minBoundX); + sendCommand(maxBoundX); + + sendCommand(PAGEADDR); + sendCommand(minBoundY); + sendCommand(maxBoundY); + + byte k = 0; + uint8_t sendBuffer[17]; + sendBuffer[0] = 0x40; + brzo_i2c_start_transaction(this->_address, BRZO_I2C_SPEED); + for (y = minBoundY; y <= maxBoundY; y++) { + for (x = minBoundX; x <= maxBoundX; x++) { + k++; + sendBuffer[k] = buffer[x + y * DISPLAY_WIDTH]; + if (k == 16) { + brzo_i2c_write(sendBuffer, 17, true); + k = 0; + } + } + yield(); + } + brzo_i2c_write(sendBuffer, k + 1, true); + brzo_i2c_end_transaction(); + #else + // No double buffering + sendCommand(COLUMNADDR); + sendCommand(0x0); + sendCommand(0x7F); + + sendCommand(PAGEADDR); + sendCommand(0x0); + sendCommand(0x7); + + uint8_t sendBuffer[17]; + sendBuffer[0] = 0x40; + brzo_i2c_start_transaction(this->_address, BRZO_I2C_SPEED); + for (uint16_t i=0; i + +#if F_CPU == 160000000L + #define BRZO_I2C_SPEED 1000 +#else + #define BRZO_I2C_SPEED 800 +#endif + +class SSD1306Spi : public OLEDDisplay { + private: + uint8_t _rst; + uint8_t _dc; + uint8_t _cs; + + public: + SSD1306Spi(uint8_t _rst, uint8_t _dc, uint8_t _cs) { + this->_rst = _rst; + this->_dc = _dc; + this->_cs = _cs; + } + + bool connect(){ + pinMode(_dc, OUTPUT); + pinMode(_cs, OUTPUT); + pinMode(_rst, OUTPUT); + + SPI.begin (); + SPI.setClockDivider (SPI_CLOCK_DIV2); + + // Pulse Reset low for 10ms + digitalWrite(_rst, HIGH); + delay(1); + digitalWrite(_rst, LOW); + delay(10); + digitalWrite(_rst, HIGH); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = ~0; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = ~0; + uint8_t maxBoundX = 0; + + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (DISPLAY_HEIGHT / 8); y++) { + for (x = 0; x < DISPLAY_WIDTH; x++) { + uint16_t pos = x + y * DISPLAY_WIDTH; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = _min(minBoundY, y); + maxBoundY = _max(maxBoundY, y); + minBoundX = _min(minBoundX, x); + maxBoundX = _max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == ~0) return; + + sendCommand(COLUMNADDR); + sendCommand(minBoundX); + sendCommand(maxBoundX); + + sendCommand(PAGEADDR); + sendCommand(minBoundY); + sendCommand(maxBoundY); + + digitalWrite(_cs, HIGH); + digitalWrite(_dc, HIGH); // data mode + digitalWrite(_cs, LOW); + for (y = minBoundY; y <= maxBoundY; y++) { + for (x = minBoundX; x <= maxBoundX; x++) { + SPI.transfer(buffer[x + y * DISPLAY_WIDTH]); + } + yield(); + } + digitalWrite(_cs, HIGH); + #else + // No double buffering + sendCommand(COLUMNADDR); + sendCommand(0x0); + sendCommand(0x7F); + + sendCommand(PAGEADDR); + sendCommand(0x0); + sendCommand(0x7); + + digitalWrite(_cs, HIGH); + digitalWrite(_dc, HIGH); // data mode + digitalWrite(_cs, LOW); + for (uint16_t i=0; i + +class SSD1306Wire : public OLEDDisplay { + private: + uint8_t _address; + uint8_t _sda; + uint8_t _scl; + + public: + SSD1306Wire(uint8_t _address, uint8_t _sda, uint8_t _scl) { + this->_address = _address; + this->_sda = _sda; + this->_scl = _scl; + } + + bool connect() { + Wire.begin(this->_sda, this->_scl); + // Let's use ~700khz if ESP8266 is in 160Mhz mode + // this will be limited to ~400khz if the ESP8266 in 80Mhz mode. + Wire.setClock(700000); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundY = ~0; + uint8_t maxBoundY = 0; + + uint8_t minBoundX = ~0; + uint8_t maxBoundX = 0; + uint8_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < (DISPLAY_HEIGHT / 8); y++) { + for (x = 0; x < DISPLAY_WIDTH; x++) { + uint16_t pos = x + y * DISPLAY_WIDTH; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = _min(minBoundY, y); + maxBoundY = _max(maxBoundY, y); + minBoundX = _min(minBoundX, x); + maxBoundX = _max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == ~0) return; + + sendCommand(COLUMNADDR); + sendCommand(minBoundX); + sendCommand(maxBoundX); + + sendCommand(PAGEADDR); + sendCommand(minBoundY); + sendCommand(maxBoundY); + + byte k = 0; + for (y = minBoundY; y <= maxBoundY; y++) { + for (x = minBoundX; x <= maxBoundX; x++) { + if (k == 0) { + Wire.beginTransmission(_address); + Wire.write(0x40); + } + Wire.write(buffer[x + y * DISPLAY_WIDTH]); + k++; + if (k == 16) { + Wire.endTransmission(); + k = 0; + } + } + yield(); + } + + if (k != 0) { + Wire.endTransmission(); + } + #else + + sendCommand(COLUMNADDR); + sendCommand(0x0); + sendCommand(0x7F); + + sendCommand(PAGEADDR); + sendCommand(0x0); + sendCommand(0x7); + + for (uint16_t i=0; i < DISPLAY_BUFFER_SIZE; i++) { + Wire.beginTransmission(this->_address); + Wire.write(0x40); + for (uint8_t x = 0; x < 16; x++) { + Wire.write(buffer[i]); + i++; + } + i--; + Wire.endTransmission(); + } + #endif + } + + private: + inline void sendCommand(uint8_t command) __attribute__((always_inline)){ + Wire.beginTransmission(_address); + Wire.write(0x80); + Wire.write(command); + Wire.endTransmission(); + } + + +}; + +#endif