ThingPulse OLED SSD1306

Dependents:   Turtle_RadioShuttle

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers OLEDDisplay.cpp Source File

OLEDDisplay.cpp

00001 /**
00002  * The MIT License (MIT)
00003  *
00004  * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
00005  * Copyright (c) 2018 by Fabrice Weinberg
00006  * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de
00007  *
00008  * Permission is hereby granted, free of charge, to any person obtaining a copy
00009  * of this software and associated documentation files (the "Software"), to deal
00010  * in the Software without restriction, including without limitation the rights
00011  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00012  * copies of the Software, and to permit persons to whom the Software is
00013  * furnished to do so, subject to the following conditions:
00014  *
00015  * The above copyright notice and this permission notice shall be included in all
00016  * copies or substantial portions of the Software.
00017  *
00018  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00019  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00020  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00021  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00022  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00023  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
00024  * SOFTWARE.
00025  *
00026  * ThingPulse invests considerable time and money to develop these open source libraries.
00027  * Please support us by buying our products (and not the clones) from
00028  * https://thingpulse.com
00029  *
00030  */
00031 
00032  /*
00033   * TODO Helmut
00034   * - test/finish dislplay.printf() on mbed-os
00035   * - Finish _putc with drawLogBuffer when running display
00036   */
00037 
00038 #include "OLEDDisplay.h"
00039 
00040 OLEDDisplay::OLEDDisplay() {
00041 
00042     displayWidth = 128;
00043     displayHeight = 64;
00044     displayBufferSize = displayWidth * displayHeight / 8;
00045     color = WHITE;
00046     geometry = GEOMETRY_128_64;
00047     textAlignment = TEXT_ALIGN_LEFT;
00048     fontData = ArialMT_Plain_10;
00049     fontTableLookupFunction = DefaultFontTableLookup;
00050     buffer = NULL;
00051 #ifdef OLEDDISPLAY_DOUBLE_BUFFER
00052     buffer_back = NULL;
00053 #endif
00054 }
00055 
00056 OLEDDisplay::~OLEDDisplay() {
00057   end();
00058 }
00059 
00060 bool OLEDDisplay::init() {
00061 
00062     logBufferSize = 0;
00063     logBufferFilled = 0;
00064     logBufferLine = 0;
00065     logBufferMaxLines = 0;
00066     logBuffer = NULL;
00067     
00068   if (!this->connect()) {
00069     DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n");
00070     return false;
00071   }
00072 
00073   if(this->buffer==NULL) {
00074     this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset());
00075     this->buffer += getBufferOffset();
00076 
00077   if(!this->buffer) {
00078     DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n");
00079     return false;
00080   }
00081   }
00082 
00083   #ifdef OLEDDISPLAY_DOUBLE_BUFFER
00084   if(this->buffer_back==NULL) {
00085   this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset());
00086   this->buffer_back += getBufferOffset();
00087 
00088   if(!this->buffer_back) {
00089     DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n");
00090     free(this->buffer - getBufferOffset());
00091     return false;
00092   }
00093   }
00094   #endif
00095 
00096   sendInitCommands();
00097   resetDisplay();
00098 
00099   return true;
00100 }
00101 
00102 void OLEDDisplay::end() {
00103   if (this->buffer) { free(this->buffer - getBufferOffset()); this->buffer = NULL; }
00104   #ifdef OLEDDISPLAY_DOUBLE_BUFFER
00105   if (this->buffer_back) { free(this->buffer_back - getBufferOffset()); this->buffer_back = NULL; }
00106   #endif
00107   if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; }
00108 }
00109 
00110 void OLEDDisplay::resetDisplay(void) {
00111   clear();
00112   #ifdef OLEDDISPLAY_DOUBLE_BUFFER
00113   memset(buffer_back, 1, displayBufferSize);
00114   #endif
00115   display();
00116 }
00117 
00118 void OLEDDisplay::setColor(OLEDDISPLAY_COLOR color) {
00119   this->color = color;
00120 }
00121 
00122 OLEDDISPLAY_COLOR OLEDDisplay::getColor() {
00123   return this->color;
00124 }
00125 
00126 void OLEDDisplay::setPixel(int16_t x, int16_t y) {
00127   if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) {
00128     switch (color) {
00129       case WHITE:   buffer[x + (y / 8) * this->width()] |=  (1 << (y & 7)); break;
00130       case BLACK:   buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break;
00131       case INVERSE: buffer[x + (y / 8) * this->width()] ^=  (1 << (y & 7)); break;
00132     }
00133   }
00134 }
00135 
00136 void OLEDDisplay::clearPixel(int16_t x, int16_t y) {
00137   if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) {
00138     switch (color) {
00139       case BLACK:   buffer[x + (y / 8) * this->width()] |=  (1 << (y & 7)); break;
00140       case WHITE:   buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break;
00141       case INVERSE: buffer[x + (y / 8) * this->width()] ^=  (1 << (y & 7)); break;
00142     }
00143   }
00144 }
00145 
00146 
00147 // Bresenham's algorithm - thx wikipedia and Adafruit_GFX
00148 void OLEDDisplay::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
00149   int16_t steep = abs(y1 - y0) > abs(x1 - x0);
00150   if (steep) {
00151     _swap_int16_t(x0, y0);
00152     _swap_int16_t(x1, y1);
00153   }
00154 
00155   if (x0 > x1) {
00156     _swap_int16_t(x0, x1);
00157     _swap_int16_t(y0, y1);
00158   }
00159 
00160   int16_t dx, dy;
00161   dx = x1 - x0;
00162   dy = abs(y1 - y0);
00163 
00164   int16_t err = dx / 2;
00165   int16_t ystep;
00166 
00167   if (y0 < y1) {
00168     ystep = 1;
00169   } else {
00170     ystep = -1;
00171   }
00172 
00173   for (; x0<=x1; x0++) {
00174     if (steep) {
00175       setPixel(y0, x0);
00176     } else {
00177       setPixel(x0, y0);
00178     }
00179     err -= dy;
00180     if (err < 0) {
00181       y0 += ystep;
00182       err += dx;
00183     }
00184   }
00185 }
00186 
00187 void OLEDDisplay::drawRect(int16_t x, int16_t y, int16_t width, int16_t height) {
00188   drawHorizontalLine(x, y, width);
00189   drawVerticalLine(x, y, height);
00190   drawVerticalLine(x + width - 1, y, height);
00191   drawHorizontalLine(x, y + height - 1, width);
00192 }
00193 
00194 void OLEDDisplay::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height) {
00195   for (int16_t x = xMove; x < xMove + width; x++) {
00196     drawVerticalLine(x, yMove, height);
00197   }
00198 }
00199 
00200 void OLEDDisplay::drawCircle(int16_t x0, int16_t y0, int16_t radius) {
00201   int16_t x = 0, y = radius;
00202     int16_t dp = 1 - radius;
00203     do {
00204         if (dp < 0)
00205             dp = dp + 2 * (x++) + 3;
00206         else
00207             dp = dp + 2 * (x++) - 2 * (y--) + 5;
00208 
00209         setPixel(x0 + x, y0 + y);     //For the 8 octants
00210         setPixel(x0 - x, y0 + y);
00211         setPixel(x0 + x, y0 - y);
00212         setPixel(x0 - x, y0 - y);
00213         setPixel(x0 + y, y0 + x);
00214         setPixel(x0 - y, y0 + x);
00215         setPixel(x0 + y, y0 - x);
00216         setPixel(x0 - y, y0 - x);
00217 
00218     } while (x < y);
00219 
00220   setPixel(x0 + radius, y0);
00221   setPixel(x0, y0 + radius);
00222   setPixel(x0 - radius, y0);
00223   setPixel(x0, y0 - radius);
00224 }
00225 
00226 void OLEDDisplay::drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads) {
00227   int16_t x = 0, y = radius;
00228   int16_t dp = 1 - radius;
00229   while (x < y) {
00230     if (dp < 0)
00231       dp = dp + 2 * (x++) + 3;
00232     else
00233       dp = dp + 2 * (x++) - 2 * (y--) + 5;
00234     if (quads & 0x1) {
00235       setPixel(x0 + x, y0 - y);
00236       setPixel(x0 + y, y0 - x);
00237     }
00238     if (quads & 0x2) {
00239       setPixel(x0 - y, y0 - x);
00240       setPixel(x0 - x, y0 - y);
00241     }
00242     if (quads & 0x4) {
00243       setPixel(x0 - y, y0 + x);
00244       setPixel(x0 - x, y0 + y);
00245     }
00246     if (quads & 0x8) {
00247       setPixel(x0 + x, y0 + y);
00248       setPixel(x0 + y, y0 + x);
00249     }
00250   }
00251   if (quads & 0x1 && quads & 0x8) {
00252     setPixel(x0 + radius, y0);
00253   }
00254   if (quads & 0x4 && quads & 0x8) {
00255     setPixel(x0, y0 + radius);
00256   }
00257   if (quads & 0x2 && quads & 0x4) {
00258     setPixel(x0 - radius, y0);
00259   }
00260   if (quads & 0x1 && quads & 0x2) {
00261     setPixel(x0, y0 - radius);
00262   }
00263 }
00264 
00265 
00266 void OLEDDisplay::fillCircle(int16_t x0, int16_t y0, int16_t radius) {
00267   int16_t x = 0, y = radius;
00268     int16_t dp = 1 - radius;
00269     do {
00270         if (dp < 0)
00271             dp = dp + 2 * (x++) + 3;
00272         else
00273             dp = dp + 2 * (x++) - 2 * (y--) + 5;
00274 
00275     drawHorizontalLine(x0 - x, y0 - y, 2*x);
00276     drawHorizontalLine(x0 - x, y0 + y, 2*x);
00277     drawHorizontalLine(x0 - y, y0 - x, 2*y);
00278     drawHorizontalLine(x0 - y, y0 + x, 2*y);
00279 
00280 
00281     } while (x < y);
00282   drawHorizontalLine(x0 - radius, y0, 2 * radius);
00283 
00284 }
00285 
00286 void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) {
00287   if (y < 0 || y >= this->height()) { return; }
00288 
00289   if (x < 0) {
00290     length += x;
00291     x = 0;
00292   }
00293 
00294   if ( (x + length) > this->width()) {
00295     length = (this->width() - x);
00296   }
00297 
00298   if (length <= 0) { return; }
00299 
00300   uint8_t * bufferPtr = buffer;
00301   bufferPtr += (y >> 3) * this->width();
00302   bufferPtr += x;
00303 
00304   uint8_t drawBit = 1 << (y & 7);
00305 
00306   switch (color) {
00307     case WHITE:   while (length--) {
00308         *bufferPtr++ |= drawBit;
00309       }; break;
00310     case BLACK:   drawBit = ~drawBit;   while (length--) {
00311         *bufferPtr++ &= drawBit;
00312       }; break;
00313     case INVERSE: while (length--) {
00314         *bufferPtr++ ^= drawBit;
00315       }; break;
00316   }
00317 }
00318 
00319 void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) {
00320   if (x < 0 || x >= this->width()) return;
00321 
00322   if (y < 0) {
00323     length += y;
00324     y = 0;
00325   }
00326 
00327   if ( (y + length) > this->height()) {
00328     length = (this->height() - y);
00329   }
00330 
00331   if (length <= 0) return;
00332 
00333 
00334   uint8_t yOffset = y & 7;
00335   uint8_t drawBit;
00336   uint8_t *bufferPtr = buffer;
00337 
00338   bufferPtr += (y >> 3) * this->width();
00339   bufferPtr += x;
00340 
00341   if (yOffset) {
00342     yOffset = 8 - yOffset;
00343     drawBit = ~(0xFF >> (yOffset));
00344 
00345     if (length < yOffset) {
00346       drawBit &= (0xFF >> (yOffset - length));
00347     }
00348 
00349     switch (color) {
00350       case WHITE:   *bufferPtr |=  drawBit; break;
00351       case BLACK:   *bufferPtr &= ~drawBit; break;
00352       case INVERSE: *bufferPtr ^=  drawBit; break;
00353     }
00354 
00355     if (length < yOffset) return;
00356 
00357     length -= yOffset;
00358     bufferPtr += this->width();
00359   }
00360 
00361   if (length >= 8) {
00362     switch (color) {
00363       case WHITE:
00364       case BLACK:
00365         drawBit = (color == WHITE) ? 0xFF : 0x00;
00366         do {
00367           *bufferPtr = drawBit;
00368           bufferPtr += this->width();
00369           length -= 8;
00370         } while (length >= 8);
00371         break;
00372       case INVERSE:
00373         do {
00374           *bufferPtr = ~(*bufferPtr);
00375           bufferPtr += this->width();
00376           length -= 8;
00377         } while (length >= 8);
00378         break;
00379     }
00380   }
00381 
00382   if (length > 0) {
00383     drawBit = (1 << (length & 7)) - 1;
00384     switch (color) {
00385       case WHITE:   *bufferPtr |=  drawBit; break;
00386       case BLACK:   *bufferPtr &= ~drawBit; break;
00387       case INVERSE: *bufferPtr ^=  drawBit; break;
00388     }
00389   }
00390 }
00391 
00392 void OLEDDisplay::drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress) {
00393   uint16_t radius = height / 2;
00394   uint16_t xRadius = x + radius;
00395   uint16_t yRadius = y + radius;
00396   uint16_t doubleRadius = 2 * radius;
00397   uint16_t innerRadius = radius - 2;
00398 
00399   setColor(WHITE);
00400   drawCircleQuads(xRadius, yRadius, radius, 0b00000110);
00401   drawHorizontalLine(xRadius, y, width - doubleRadius + 1);
00402   drawHorizontalLine(xRadius, y + height, width - doubleRadius + 1);
00403   drawCircleQuads(x + width - radius, yRadius, radius, 0b00001001);
00404 
00405   uint16_t maxProgressWidth = (width - doubleRadius + 1) * progress / 100;
00406 
00407   fillCircle(xRadius, yRadius, innerRadius);
00408   fillRect(xRadius + 1, y + 2, maxProgressWidth, height - 3);
00409   fillCircle(xRadius + maxProgressWidth, yRadius, innerRadius);
00410 }
00411 
00412 void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *image) {
00413   drawInternal(xMove, yMove, width, height, image, 0, 0);
00414 }
00415 
00416 void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) {
00417   int16_t widthInXbm = (width + 7) / 8;
00418   uint8_t data = 0;
00419 
00420   for(int16_t y = 0; y < height; y++) {
00421     for(int16_t x = 0; x < width; x++ ) {
00422       if (x & 7) {
00423         data >>= 1; // Move a bit
00424       } else {  // Read new data every 8 bit
00425         data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm);
00426       }
00427       // if there is a bit draw it
00428       if (data & 0x01) {
00429         setPixel(xMove + x, yMove + y);
00430       }
00431     }
00432   }
00433 }
00434 
00435 void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth) {
00436   uint8_t textHeight       = pgm_read_byte(fontData + HEIGHT_POS);
00437   uint8_t firstChar        = pgm_read_byte(fontData + FIRST_CHAR_POS);
00438   uint16_t sizeOfJumpTable = pgm_read_byte(fontData + CHAR_NUM_POS)  * JUMPTABLE_BYTES;
00439 
00440   uint16_t cursorX         = 0;
00441   uint16_t cursorY         = 0;
00442 
00443   switch (textAlignment) {
00444     case TEXT_ALIGN_CENTER_BOTH:
00445       yMove -= textHeight >> 1;
00446     // Fallthrough
00447     case TEXT_ALIGN_CENTER:
00448       xMove -= textWidth >> 1; // divide by 2
00449       break;
00450     case TEXT_ALIGN_RIGHT:
00451       xMove -= textWidth;
00452       break;
00453     case TEXT_ALIGN_LEFT:
00454       break;
00455   }
00456 
00457   // Don't draw anything if it is not on the screen.
00458   if (xMove + textWidth  < 0 || xMove > this->width() ) {return;}
00459   if (yMove + textHeight < 0 || yMove > this->width() ) {return;}
00460 
00461   for (uint16_t j = 0; j < textLength; j++) {
00462     int16_t xPos = xMove + cursorX;
00463     int16_t yPos = yMove + cursorY;
00464 
00465     uint8_t code = text[j];
00466     if (code >= firstChar) {
00467       uint8_t charCode = code - firstChar;
00468 
00469       // 4 Bytes per char code
00470       uint8_t msbJumpToChar    = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES );                  // MSB  \ JumpAddress
00471       uint8_t lsbJumpToChar    = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_LSB);   // LSB /
00472       uint8_t charByteSize     = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_SIZE);  // Size
00473       uint8_t currentCharWidth = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); // Width
00474 
00475       // Test if the char is drawable
00476       if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) {
00477         // Get the position of the char data
00478         uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + ((msbJumpToChar << 8) + lsbJumpToChar);
00479         drawInternal(xPos, yPos, currentCharWidth, textHeight, fontData, charDataPosition, charByteSize);
00480       }
00481 
00482       cursorX += currentCharWidth;
00483     }
00484   }
00485 }
00486 
00487 
00488 void OLEDDisplay::drawString(int16_t xMove, int16_t yMove, String strUser) {
00489   uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS);
00490 
00491   // char* text must be freed!
00492   char* text = utf8ascii(strUser);
00493 
00494   uint16_t yOffset = 0;
00495   // If the string should be centered vertically too
00496   // we need to now how heigh the string is.
00497   if (textAlignment == TEXT_ALIGN_CENTER_BOTH) {
00498     uint16_t lb = 0;
00499     // Find number of linebreaks in text
00500     for (uint16_t i=0;text[i] != 0; i++) {
00501       lb += (text[i] == 10);
00502     }
00503     // Calculate center
00504     yOffset = (lb * lineHeight) / 2;
00505   }
00506 
00507   uint16_t line = 0;
00508   char* textPart = strtok(text,"\n");
00509   while (textPart != NULL) {
00510     uint16_t length = strlen(textPart);
00511     drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length));
00512     textPart = strtok(NULL, "\n");
00513   }
00514   free(text);
00515 }
00516 
00517 void OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxLineWidth, String strUser) {
00518   uint16_t firstChar  = pgm_read_byte(fontData + FIRST_CHAR_POS);
00519   uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS);
00520 
00521   char* text = utf8ascii(strUser);
00522 
00523   uint16_t length = strlen(text);
00524   uint16_t lastDrawnPos = 0;
00525   uint16_t lineNumber = 0;
00526   uint16_t strWidth = 0;
00527 
00528   uint16_t preferredBreakpoint = 0;
00529   uint16_t widthAtBreakpoint = 0;
00530 
00531   for (uint16_t i = 0; i < length; i++) {
00532     strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[i] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH);
00533 
00534     // Always try to break on a space or dash
00535     if (text[i] == ' ' || text[i]== '-') {
00536       preferredBreakpoint = i;
00537       widthAtBreakpoint = strWidth;
00538     }
00539 
00540     if (strWidth >= maxLineWidth) {
00541       if (preferredBreakpoint == 0) {
00542         preferredBreakpoint = i;
00543         widthAtBreakpoint = strWidth;
00544       }
00545       drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], preferredBreakpoint - lastDrawnPos, widthAtBreakpoint);
00546       lastDrawnPos = preferredBreakpoint + 1;
00547       // It is possible that we did not draw all letters to i so we need
00548       // to account for the width of the chars from `i - preferredBreakpoint`
00549       // by calculating the width we did not draw yet.
00550       strWidth = strWidth - widthAtBreakpoint;
00551       preferredBreakpoint = 0;
00552     }
00553   }
00554 
00555   // Draw last part if needed
00556   if (lastDrawnPos < length) {
00557     drawStringInternal(xMove, yMove + lineNumber * lineHeight , &text[lastDrawnPos], length - lastDrawnPos, getStringWidth(&text[lastDrawnPos], length - lastDrawnPos));
00558   }
00559 
00560   free(text);
00561 }
00562 
00563 uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) {
00564   uint16_t firstChar        = pgm_read_byte(fontData + FIRST_CHAR_POS);
00565 
00566   uint16_t stringWidth = 0;
00567   uint16_t maxWidth = 0;
00568 
00569   while (length--) {
00570     stringWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[length] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH);
00571     if (text[length] == 10) {
00572       maxWidth = max(maxWidth, stringWidth);
00573       stringWidth = 0;
00574     }
00575   }
00576 
00577   return max(maxWidth, stringWidth);
00578 }
00579 
00580 uint16_t OLEDDisplay::getStringWidth(String strUser) {
00581   char* text = utf8ascii(strUser);
00582   uint16_t length = strlen(text);
00583   uint16_t width = getStringWidth(text, length);
00584   free(text);
00585   return width;
00586 }
00587 
00588 void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) {
00589   this->textAlignment = textAlignment;
00590 }
00591 
00592 void OLEDDisplay::setFont(const uint8_t *fontData) {
00593   this->fontData = fontData;
00594 }
00595 
00596 void OLEDDisplay::displayOn(void) {
00597   sendCommand(DISPLAYON);
00598 }
00599 
00600 void OLEDDisplay::displayOff(void) {
00601   sendCommand(DISPLAYOFF);
00602 }
00603 
00604 void OLEDDisplay::invertDisplay(void) {
00605   sendCommand(INVERTDISPLAY);
00606 }
00607 
00608 void OLEDDisplay::normalDisplay(void) {
00609   sendCommand(NORMALDISPLAY);
00610 }
00611 
00612 void OLEDDisplay::setContrast(uint8_t contrast, uint8_t precharge, uint8_t comdetect) {
00613   sendCommand(SETPRECHARGE); //0xD9
00614   sendCommand(precharge); //0xF1 default, to lower the contrast, put 1-1F
00615   sendCommand(SETCONTRAST);
00616   sendCommand(contrast); // 0-255
00617   sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast)
00618   sendCommand(comdetect);   //0x40 default, to lower the contrast, put 0
00619   sendCommand(DISPLAYALLON_RESUME);
00620   sendCommand(NORMALDISPLAY);
00621   sendCommand(DISPLAYON);
00622 }
00623 
00624 void OLEDDisplay::setBrightness(uint8_t brightness) {
00625   uint8_t contrast = brightness;
00626   if (brightness < 128) {
00627     // Magic values to get a smooth/ step-free transition
00628     contrast = brightness * 1.171;
00629   } else {
00630     contrast = brightness * 1.171 - 43;
00631   }
00632 
00633   uint8_t precharge = 241;
00634   if (brightness == 0) {
00635     precharge = 0;
00636   }
00637   uint8_t comdetect = brightness / 8;
00638 
00639   setContrast(contrast, precharge, comdetect);
00640 }
00641 
00642 void OLEDDisplay::resetOrientation() {
00643   sendCommand(SEGREMAP);
00644   sendCommand(COMSCANINC);           //Reset screen rotation or mirroring
00645 }
00646 
00647 void OLEDDisplay::flipScreenVertically() {
00648   sendCommand(SEGREMAP | 0x01);
00649   sendCommand(COMSCANDEC);           //Rotate screen 180 Deg
00650 }
00651 
00652 void OLEDDisplay::mirrorScreen() {
00653   sendCommand(SEGREMAP);
00654   sendCommand(COMSCANDEC);           //Mirror screen
00655 }
00656 
00657 void OLEDDisplay::clear(void) {
00658   memset(buffer, 0, displayBufferSize);
00659 }
00660 
00661 void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) {
00662   uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS);
00663   // Always align left
00664   setTextAlignment(TEXT_ALIGN_LEFT);
00665 
00666   // State values
00667   uint16_t length   = 0;
00668   uint16_t line     = 0;
00669   uint16_t lastPos  = 0;
00670 
00671   for (uint16_t i=0;i<this->logBufferFilled;i++){
00672     // Everytime we have a \n print
00673     if (this->logBuffer[i] == 10) {
00674       length++;
00675       // Draw string on line `line` from lastPos to length
00676       // Passing 0 as the lenght because we are in TEXT_ALIGN_LEFT
00677       drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0);
00678       // Remember last pos
00679       lastPos = i;
00680       // Reset length
00681       length = 0;
00682     } else {
00683       // Count chars until next linebreak
00684       length++;
00685     }
00686   }
00687   // Draw the remaining string
00688   if (length > 0) {
00689     drawStringInternal(xMove, yMove + line * lineHeight, &this->logBuffer[lastPos], length, 0);
00690   }
00691 }
00692 
00693 uint16_t OLEDDisplay::getWidth(void) {
00694   return displayWidth;
00695 }
00696 
00697 uint16_t OLEDDisplay::getHeight(void) {
00698   return displayHeight;
00699 }
00700 
00701 bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){
00702   if (logBuffer != NULL) free(logBuffer);
00703   uint16_t size = lines * chars;
00704   if (size > 0) {
00705     this->logBufferLine     = 0;      // Lines printed
00706     this->logBufferFilled   = 0;      // Nothing stored yet
00707     this->logBufferMaxLines = lines;  // Lines max printable
00708     this->logBufferSize     = size;   // Total number of characters the buffer can hold
00709     this->logBuffer         = (char *) malloc(size * sizeof(uint8_t));
00710     if(!this->logBuffer) {
00711       DEBUG_OLEDDISPLAY("[OLEDDISPLAY][setLogBuffer] Not enough memory to create log buffer\n");
00712       return false;
00713     }
00714   }
00715   return true;
00716 }
00717 
00718 size_t OLEDDisplay::write(uint8_t c) {
00719   if (this->logBufferSize > 0) {
00720     // Don't waste space on \r\n line endings, dropping \r
00721     if (c == 13) return 1;
00722 
00723     // convert UTF-8 character to font table index
00724     c = (this->fontTableLookupFunction)(c);
00725     // drop unknown character
00726     if (c == 0) return 1;
00727 
00728     bool maxLineNotReached = this->logBufferLine < this->logBufferMaxLines;
00729     bool bufferNotFull = this->logBufferFilled < this->logBufferSize;
00730 
00731     // Can we write to the buffer?
00732     if (bufferNotFull && maxLineNotReached) {
00733       this->logBuffer[logBufferFilled] = c;
00734       this->logBufferFilled++;
00735       // Keep track of lines written
00736       if (c == 10) this->logBufferLine++;
00737     } else {
00738       // Max line number is reached
00739       if (!maxLineNotReached) this->logBufferLine--;
00740 
00741       // Find the end of the first line
00742       uint16_t firstLineEnd = 0;
00743       for (uint16_t i=0;i<this->logBufferFilled;i++) {
00744         if (this->logBuffer[i] == 10){
00745           // Include last char too
00746           firstLineEnd = i + 1;
00747           break;
00748         }
00749       }
00750       // If there was a line ending
00751       if (firstLineEnd > 0) {
00752         // Calculate the new logBufferFilled value
00753         this->logBufferFilled = logBufferFilled - firstLineEnd;
00754         // Now we move the lines infront of the buffer
00755         memcpy(this->logBuffer, &this->logBuffer[firstLineEnd], logBufferFilled);
00756       } else {
00757         // Let's reuse the buffer if it was full
00758         if (!bufferNotFull) {
00759           this->logBufferFilled = 0;
00760         }// else {
00761         //  Nothing to do here
00762         //}
00763       }
00764       write(c);
00765     }
00766   }
00767   // We are always writing all uint8_t to the buffer
00768   return 1;
00769 }
00770 
00771 size_t OLEDDisplay::write(const char* str) {
00772   if (str == NULL) return 0;
00773   size_t length = strlen(str);
00774   for (size_t i = 0; i < length; i++) {
00775     write(str[i]);
00776   }
00777   return length;
00778 }
00779 
00780 #ifdef __MBED__
00781 int OLEDDisplay::_putc(int c) {
00782 
00783     if (!fontData)
00784         return 1;
00785     if (!logBufferSize) {
00786         uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS);
00787         uint16_t lines =  this->displayHeight / textHeight;
00788         uint16_t chars =   2 * (this->displayWidth / textHeight);
00789         
00790         if (this->displayHeight % textHeight)
00791             lines++;
00792         if (this->displayWidth % textHeight)
00793             chars++;
00794         setLogBuffer(lines, chars);
00795     }
00796 
00797     return this->write((uint8_t)c);
00798 }
00799 #endif
00800 
00801 // Private functions
00802 void OLEDDisplay::setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) {
00803   this->geometry = g;
00804   switch (g) {
00805     case GEOMETRY_128_64:
00806         this->displayWidth = 128;
00807         this->displayHeight = 64;
00808         break;
00809     case GEOMETRY_128_32:
00810         this->displayWidth = 128;
00811         this->displayHeight = 32;
00812         break;
00813     case GEOMETRY_RAWMODE:
00814         this->displayWidth = width > 0 ? width : 128;
00815         this->displayHeight = height > 0 ? height : 64;
00816         break;
00817   }
00818   this->displayBufferSize = displayWidth * displayHeight /8;
00819 }
00820 
00821 void OLEDDisplay::sendInitCommands(void) {
00822   if (geometry == GEOMETRY_RAWMODE)
00823     return;
00824   sendCommand(DISPLAYOFF);
00825   sendCommand(SETDISPLAYCLOCKDIV);
00826   sendCommand(0xF0); // Increase speed of the display max ~96Hz
00827   sendCommand(SETMULTIPLEX);
00828   sendCommand(this->height() - 1);
00829   sendCommand(SETDISPLAYOFFSET);
00830   sendCommand(0x00);
00831   sendCommand(SETSTARTLINE);
00832   sendCommand(CHARGEPUMP);
00833   sendCommand(0x14);
00834   sendCommand(MEMORYMODE);
00835   sendCommand(0x00);
00836   sendCommand(SEGREMAP);
00837   sendCommand(COMSCANINC);
00838   sendCommand(SETCOMPINS);
00839 
00840   if (geometry == GEOMETRY_128_64) {
00841     sendCommand(0x12);
00842   } else if (geometry == GEOMETRY_128_32) {
00843     sendCommand(0x02);
00844   }
00845 
00846   sendCommand(SETCONTRAST);
00847 
00848   if (geometry == GEOMETRY_128_64) {
00849     sendCommand(0xCF);
00850   } else if (geometry == GEOMETRY_128_32) {
00851     sendCommand(0x8F);
00852   }
00853 
00854   sendCommand(SETPRECHARGE);
00855   sendCommand(0xF1);
00856   sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast)
00857   sendCommand(0x40);            //0x40 default, to lower the contrast, put 0
00858   sendCommand(DISPLAYALLON_RESUME);
00859   sendCommand(NORMALDISPLAY);
00860   sendCommand(0x2e);            // stop scroll
00861   sendCommand(DISPLAYON);
00862 }
00863 
00864 void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) {
00865   if (width < 0 || height < 0) return;
00866   if (yMove + height < 0 || yMove > this->height())  return;
00867   if (xMove + width  < 0 || xMove > this->width())   return;
00868 
00869   uint8_t  rasterHeight = 1 + ((height - 1) >> 3); // fast ceil(height / 8.0)
00870   int8_t   yOffset      = yMove & 7;
00871 
00872   bytesInData = bytesInData == 0 ? width * rasterHeight : bytesInData;
00873 
00874   int16_t initYMove   = yMove;
00875   int8_t  initYOffset = yOffset;
00876 
00877 
00878   for (uint16_t i = 0; i < bytesInData; i++) {
00879 
00880     // Reset if next horizontal drawing phase is started.
00881     if ( i % rasterHeight == 0) {
00882       yMove   = initYMove;
00883       yOffset = initYOffset;
00884     }
00885 
00886     uint8_t currentByte = pgm_read_byte(data + offset + i);
00887 
00888     int16_t xPos = xMove + (i / rasterHeight);
00889     int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * this->width();
00890 
00891 //    int16_t yScreenPos = yMove + yOffset;
00892     int16_t dataPos    = xPos  + yPos;
00893 
00894     if (dataPos >=  0  && dataPos < displayBufferSize &&
00895         xPos    >=  0  && xPos    < this->width() ) {
00896 
00897       if (yOffset >= 0) {
00898         switch (this->color) {
00899           case WHITE:   buffer[dataPos] |= currentByte << yOffset; break;
00900           case BLACK:   buffer[dataPos] &= ~(currentByte << yOffset); break;
00901           case INVERSE: buffer[dataPos] ^= currentByte << yOffset; break;
00902         }
00903 
00904         if (dataPos < (displayBufferSize - this->width())) {
00905           switch (this->color) {
00906             case WHITE:   buffer[dataPos + this->width()] |= currentByte >> (8 - yOffset); break;
00907             case BLACK:   buffer[dataPos + this->width()] &= ~(currentByte >> (8 - yOffset)); break;
00908             case INVERSE: buffer[dataPos + this->width()] ^= currentByte >> (8 - yOffset); break;
00909           }
00910         }
00911       } else {
00912         // Make new offset position
00913         yOffset = -yOffset;
00914 
00915         switch (this->color) {
00916           case WHITE:   buffer[dataPos] |= currentByte >> yOffset; break;
00917           case BLACK:   buffer[dataPos] &= ~(currentByte >> yOffset); break;
00918           case INVERSE: buffer[dataPos] ^= currentByte >> yOffset; break;
00919         }
00920 
00921         // Prepare for next iteration by moving one block up
00922         yMove -= 8;
00923 
00924         // and setting the new yOffset
00925         yOffset = 8 - yOffset;
00926       }
00927 #ifndef __MBED__
00928       yield();
00929 #endif
00930     }
00931   }
00932 }
00933 
00934 // You need to free the char!
00935 char* OLEDDisplay::utf8ascii(String str) {
00936   uint16_t k = 0;
00937   uint16_t length = str.length() + 1;
00938 
00939   // Copy the string into a char array
00940   char* s = (char*) malloc(length * sizeof(char));
00941   if(!s) {
00942     DEBUG_OLEDDISPLAY("[OLEDDISPLAY][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n");
00943     return (char*) str.c_str();
00944   }
00945   str.toCharArray(s, length);
00946 
00947   length--;
00948 
00949   for (uint16_t i=0; i < length; i++) {
00950     char c = (this->fontTableLookupFunction)(s[i]);
00951     if (c!=0) {
00952       s[k++]=c;
00953     }
00954   }
00955 
00956   s[k]=0;
00957 
00958   // This will leak 's' be sure to free it in the calling function.
00959   return s;
00960 }
00961 
00962 void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) {
00963   this->fontTableLookupFunction = function;
00964 }
00965 
00966 
00967 char DefaultFontTableLookup(const uint8_t ch) {
00968     // UTF-8 to font table index converter
00969     // Code form http://playground.arduino.cc/Main/Utf8ascii
00970     static uint8_t LASTCHAR;
00971 
00972     if (ch < 128) { // Standard ASCII-set 0..0x7F handling
00973         LASTCHAR = 0;
00974         return ch;
00975     }
00976 
00977     uint8_t last = LASTCHAR;   // get last char
00978     LASTCHAR = ch;
00979 
00980     switch (last) {    // conversion depnding on first UTF8-character
00981         case 0xC2: return (uint8_t) ch;
00982         case 0xC3: return (uint8_t) (ch | 0xC0);
00983         case 0x82: if (ch == 0xAC) return (uint8_t) 0x80;    // special case Euro-symbol
00984     }
00985 
00986     return (uint8_t) 0; // otherwise: return zero, if character has to be ignored
00987 }