ThingPulse OLED SSD1306
Dependents: Turtle_RadioShuttle mbed-os5-F303-18650-Manager-tp4056 Kretanje_kroz_izbornike_OLED128x64_4tipke
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 }
Generated on Wed Jul 13 2022 11:33:40 by
1.7.2
Helmut Tschemernjak