From 39f7058cd42a658189598d4f4fe185d12ce70894 Mon Sep 17 00:00:00 2001 From: NoobTracker <63962365+NoobTracker@users.noreply.github.com> Date: Thu, 23 Apr 2020 13:01:26 +0200 Subject: [PATCH 1/6] OLEDDisplay.cpp -> fillCircle() fixed Circles created with fillCircle() were one pixel taller than wide. --- OLEDDisplay.cpp | 1022 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1022 insertions(+) create mode 100644 OLEDDisplay.cpp diff --git a/OLEDDisplay.cpp b/OLEDDisplay.cpp new file mode 100644 index 0000000..b4d9cd2 --- /dev/null +++ b/OLEDDisplay.cpp @@ -0,0 +1,1022 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + * + * 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. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + + /* + * TODO Helmut + * - test/finish dislplay.printf() on mbed-os + * - Finish _putc with drawLogBuffer when running display + */ + +#include "OLEDDisplay.h" + +OLEDDisplay::OLEDDisplay() { + + displayWidth = 128; + displayHeight = 64; + displayBufferSize = displayWidth * displayHeight / 8; + color = WHITE; + geometry = GEOMETRY_128_64; + textAlignment = TEXT_ALIGN_LEFT; + fontData = ArialMT_Plain_10; + fontTableLookupFunction = DefaultFontTableLookup; + buffer = NULL; +#ifdef OLEDDISPLAY_DOUBLE_BUFFER + buffer_back = NULL; +#endif +} + +OLEDDisplay::~OLEDDisplay() { + end(); +} + +bool OLEDDisplay::allocateBuffer() { + + logBufferSize = 0; + logBufferFilled = 0; + logBufferLine = 0; + logBufferMaxLines = 0; + logBuffer = NULL; + + if (!this->connect()) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); + return false; + } + + if(this->buffer==NULL) { + this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); + this->buffer += getBufferOffset(); + + if(!this->buffer) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); + return false; + } + } + + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + if(this->buffer_back==NULL) { + this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); + this->buffer_back += getBufferOffset(); + + if(!this->buffer_back) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); + free(this->buffer - getBufferOffset()); + return false; + } + } + #endif + + return true; +} + +bool OLEDDisplay::init() { + + if(!allocateBuffer()) { + return false; + } + + sendInitCommands(); + resetDisplay(); + + return true; +} + +void OLEDDisplay::end() { + if (this->buffer) { free(this->buffer - getBufferOffset()); this->buffer = NULL; } + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + if (this->buffer_back) { free(this->buffer_back - getBufferOffset()); this->buffer_back = NULL; } + #endif + if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; } +} + +void OLEDDisplay::resetDisplay(void) { + clear(); + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + memset(buffer_back, 1, displayBufferSize); + #endif + display(); +} + +void OLEDDisplay::setColor(OLEDDISPLAY_COLOR color) { + this->color = color; +} + +OLEDDISPLAY_COLOR OLEDDisplay::getColor() { + return this->color; +} + +void OLEDDisplay::setPixel(int16_t x, int16_t y) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; + case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + +void OLEDDisplay::setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; + case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + +void OLEDDisplay::clearPixel(int16_t x, int16_t y) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case BLACK: buffer[x + (y >> 3) * this->width()] |= (1 << (y & 7)); break; + case WHITE: buffer[x + (y >> 3) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y >> 3) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + + +// Bresenham's algorithm - thx wikipedia and Adafruit_GFX +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); + _swap_int16_t(x1, y1); + } + + if (x0 > x1) { + _swap_int16_t(x0, x1); + _swap_int16_t(y0, y1); + } + + int16_t dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int16_t err = dx / 2; + int16_t ystep; + + if (y0 < y1) { + ystep = 1; + } else { + ystep = -1; + } + + for (; x0<=x1; x0++) { + if (steep) { + setPixel(y0, x0); + } else { + setPixel(x0, y0); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } +} + +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 - 1, y, height); + drawHorizontalLine(x, y + height - 1, width); +} + +void OLEDDisplay::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height) { + for (int16_t x = xMove; x < xMove + width; x++) { + drawVerticalLine(x, yMove, height); + } +} + +void OLEDDisplay::drawCircle(int16_t x0, int16_t y0, int16_t radius) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + do { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + setPixel(x0 + x, y0 + y); //For the 8 octants + setPixel(x0 - x, y0 + y); + setPixel(x0 + x, y0 - y); + setPixel(x0 - x, y0 - y); + setPixel(x0 + y, y0 + x); + setPixel(x0 - y, y0 + x); + setPixel(x0 + y, y0 - x); + setPixel(x0 - y, y0 - x); + + } while (x < y); + + setPixel(x0 + radius, y0); + setPixel(x0, y0 + radius); + setPixel(x0 - radius, y0); + setPixel(x0, y0 - radius); +} + +void OLEDDisplay::drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + while (x < y) { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + if (quads & 0x1) { + setPixel(x0 + x, y0 - y); + setPixel(x0 + y, y0 - x); + } + if (quads & 0x2) { + setPixel(x0 - y, y0 - x); + setPixel(x0 - x, y0 - y); + } + if (quads & 0x4) { + setPixel(x0 - y, y0 + x); + setPixel(x0 - x, y0 + y); + } + if (quads & 0x8) { + setPixel(x0 + x, y0 + y); + setPixel(x0 + y, y0 + x); + } + } + if (quads & 0x1 && quads & 0x8) { + setPixel(x0 + radius, y0); + } + if (quads & 0x4 && quads & 0x8) { + setPixel(x0, y0 + radius); + } + if (quads & 0x2 && quads & 0x4) { + setPixel(x0 - radius, y0); + } + if (quads & 0x1 && quads & 0x2) { + setPixel(x0, y0 - 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 { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + drawHorizontalLine(x0 - x, y0 - y, 2*x + 1); + drawHorizontalLine(x0 - x, y0 + y, 2*x + 1); + drawHorizontalLine(x0 - y, y0 - x, 2*y + 1); + drawHorizontalLine(x0 - y, y0 + x, 2*y + 1); + + + } while (x < y); + drawHorizontalLine(x0 - radius, y0, (2 * radius) + 1); + drawVerticalLine(x0, y0 - radius, 2 * radius); +} + +void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { + if (y < 0 || y >= this->height()) { return; } + + if (x < 0) { + length += x; + x = 0; + } + + if ( (x + length) > this->width()) { + length = (this->width() - x); + } + + if (length <= 0) { return; } + + uint8_t * bufferPtr = buffer; + bufferPtr += (y >> 3) * this->width(); + bufferPtr += x; + + uint8_t drawBit = 1 << (y & 7); + + switch (color) { + case WHITE: while (length--) { + *bufferPtr++ |= drawBit; + }; break; + case BLACK: drawBit = ~drawBit; while (length--) { + *bufferPtr++ &= drawBit; + }; break; + case INVERSE: while (length--) { + *bufferPtr++ ^= drawBit; + }; break; + } +} + +void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { + if (x < 0 || x >= this->width()) return; + + if (y < 0) { + length += y; + y = 0; + } + + if ( (y + length) > this->height()) { + length = (this->height() - y); + } + + if (length <= 0) return; + + + uint8_t yOffset = y & 7; + uint8_t drawBit; + uint8_t *bufferPtr = buffer; + + bufferPtr += (y >> 3) * this->width(); + bufferPtr += x; + + if (yOffset) { + yOffset = 8 - yOffset; + drawBit = ~(0xFF >> (yOffset)); + + if (length < yOffset) { + drawBit &= (0xFF >> (yOffset - length)); + } + + switch (color) { + case WHITE: *bufferPtr |= drawBit; break; + case BLACK: *bufferPtr &= ~drawBit; break; + case INVERSE: *bufferPtr ^= drawBit; break; + } + + if (length < yOffset) return; + + length -= yOffset; + bufferPtr += this->width(); + } + + if (length >= 8) { + switch (color) { + case WHITE: + case BLACK: + drawBit = (color == WHITE) ? 0xFF : 0x00; + do { + *bufferPtr = drawBit; + bufferPtr += this->width(); + length -= 8; + } while (length >= 8); + break; + case INVERSE: + do { + *bufferPtr = ~(*bufferPtr); + bufferPtr += this->width(); + length -= 8; + } while (length >= 8); + break; + } + } + + if (length > 0) { + drawBit = (1 << (length & 7)) - 1; + switch (color) { + case WHITE: *bufferPtr |= drawBit; break; + case BLACK: *bufferPtr &= ~drawBit; break; + case INVERSE: *bufferPtr ^= drawBit; break; + } + } +} + +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 xRadius = x + radius; + uint16_t yRadius = y + radius; + uint16_t doubleRadius = 2 * radius; + uint16_t innerRadius = radius - 2; + + setColor(WHITE); + drawCircleQuads(xRadius, yRadius, radius, 0b00000110); + drawHorizontalLine(xRadius, y, width - doubleRadius + 1); + drawHorizontalLine(xRadius, y + height, width - doubleRadius + 1); + drawCircleQuads(x + width - radius, yRadius, radius, 0b00001001); + + uint16_t maxProgressWidth = (width - doubleRadius + 1) * progress / 100; + + fillCircle(xRadius, yRadius, innerRadius); + fillRect(xRadius + 1, y + 2, maxProgressWidth, height - 3); + fillCircle(xRadius + maxProgressWidth, yRadius, innerRadius); +} + +void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *image) { + drawInternal(xMove, yMove, width, height, image, 0, 0); +} + +void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) { + int16_t widthInXbm = (width + 7) / 8; + uint8_t data = 0; + + for(int16_t y = 0; y < height; y++) { + for(int16_t x = 0; x < width; x++ ) { + if (x & 7) { + data >>= 1; // Move a bit + } else { // Read new data every 8 bit + data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm); + } + // if there is a bit draw it + if (data & 0x01) { + setPixel(xMove + x, yMove + y); + } + } + } +} + +void OLEDDisplay::drawIco16x16(int16_t xMove, int16_t yMove, const char *ico, bool inverse) { + uint16_t data; + + for(int16_t y = 0; y < 16; y++) { + data = pgm_read_byte(ico + (y << 1)) + (pgm_read_byte(ico + (y << 1) + 1) << 8); + for(int16_t x = 0; x < 16; x++ ) { + if ((data & 0x01) ^ inverse) { + setPixelColor(xMove + x, yMove + y, WHITE); + } else { + setPixelColor(xMove + x, yMove + y, BLACK); + } + data >>= 1; // Move a bit + } + } +} + +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; + + uint16_t cursorX = 0; + uint16_t cursorY = 0; + + switch (textAlignment) { + case TEXT_ALIGN_CENTER_BOTH: + yMove -= textHeight >> 1; + // Fallthrough + case TEXT_ALIGN_CENTER: + xMove -= textWidth >> 1; // divide by 2 + break; + case TEXT_ALIGN_RIGHT: + xMove -= textWidth; + break; + case TEXT_ALIGN_LEFT: + break; + } + + // Don't draw anything if it is not on the screen. + if (xMove + textWidth < 0 || xMove > this->width() ) {return;} + if (yMove + textHeight < 0 || yMove > this->width() ) {return;} + + for (uint16_t j = 0; j < textLength; j++) { + int16_t xPos = xMove + cursorX; + int16_t yPos = yMove + cursorY; + + uint8_t code = text[j]; + if (code >= firstChar) { + uint8_t charCode = code - firstChar; + + // 4 Bytes per char code + uint8_t msbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES ); // MSB \ JumpAddress + uint8_t lsbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_LSB); // LSB / + uint8_t charByteSize = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_SIZE); // Size + uint8_t currentCharWidth = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); // Width + + // Test if the char is drawable + if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { + // Get the position of the char data + uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + ((msbJumpToChar << 8) + lsbJumpToChar); + drawInternal(xPos, yPos, currentCharWidth, textHeight, fontData, charDataPosition, charByteSize); + } + + cursorX += currentCharWidth; + } + } +} + + +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! + char* text = utf8ascii(strUser); + + uint16_t yOffset = 0; + // If the string should be centered vertically too + // we need to now how heigh the string is. + if (textAlignment == TEXT_ALIGN_CENTER_BOTH) { + uint16_t lb = 0; + // Find number of linebreaks in text + for (uint16_t i=0;text[i] != 0; i++) { + lb += (text[i] == 10); + } + // Calculate center + yOffset = (lb * lineHeight) / 2; + } + + uint16_t line = 0; + char* textPart = strtok(text,"\n"); + while (textPart != NULL) { + uint16_t length = strlen(textPart); + drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length)); + textPart = strtok(NULL, "\n"); + } + free(text); +} + +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); + + char* text = utf8ascii(strUser); + + uint16_t length = strlen(text); + uint16_t lastDrawnPos = 0; + uint16_t lineNumber = 0; + uint16_t strWidth = 0; + + uint16_t preferredBreakpoint = 0; + uint16_t widthAtBreakpoint = 0; + + for (uint16_t i = 0; i < length; i++) { + strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[i] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); + + // Always try to break on a space or dash + if (text[i] == ' ' || text[i]== '-') { + preferredBreakpoint = i; + widthAtBreakpoint = strWidth; + } + + if (strWidth >= maxLineWidth) { + if (preferredBreakpoint == 0) { + preferredBreakpoint = i; + widthAtBreakpoint = strWidth; + } + drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], preferredBreakpoint - lastDrawnPos, widthAtBreakpoint); + lastDrawnPos = preferredBreakpoint + 1; + // It is possible that we did not draw all letters to i so we need + // to account for the width of the chars from `i - preferredBreakpoint` + // by calculating the width we did not draw yet. + strWidth = strWidth - widthAtBreakpoint; + preferredBreakpoint = 0; + } + } + + // Draw last part if needed + if (lastDrawnPos < length) { + drawStringInternal(xMove, yMove + lineNumber * lineHeight , &text[lastDrawnPos], length - lastDrawnPos, getStringWidth(&text[lastDrawnPos], length - lastDrawnPos)); + } + + free(text); +} + +uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) { + uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + + uint16_t stringWidth = 0; + uint16_t maxWidth = 0; + + while (length--) { + stringWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[length] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); + if (text[length] == 10) { + maxWidth = max(maxWidth, stringWidth); + stringWidth = 0; + } + } + + return max(maxWidth, stringWidth); +} + +uint16_t OLEDDisplay::getStringWidth(String strUser) { + char* text = utf8ascii(strUser); + uint16_t length = strlen(text); + uint16_t width = getStringWidth(text, length); + free(text); + return width; +} + +void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { + this->textAlignment = textAlignment; +} + +void OLEDDisplay::setFont(const uint8_t *fontData) { + this->fontData = fontData; +} + +void OLEDDisplay::displayOn(void) { + sendCommand(DISPLAYON); +} + +void OLEDDisplay::displayOff(void) { + sendCommand(DISPLAYOFF); +} + +void OLEDDisplay::invertDisplay(void) { + sendCommand(INVERTDISPLAY); +} + +void OLEDDisplay::normalDisplay(void) { + sendCommand(NORMALDISPLAY); +} + +void OLEDDisplay::setContrast(uint8_t contrast, uint8_t precharge, uint8_t comdetect) { + sendCommand(SETPRECHARGE); //0xD9 + sendCommand(precharge); //0xF1 default, to lower the contrast, put 1-1F + sendCommand(SETCONTRAST); + sendCommand(contrast); // 0-255 + sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) + sendCommand(comdetect); //0x40 default, to lower the contrast, put 0 + sendCommand(DISPLAYALLON_RESUME); + sendCommand(NORMALDISPLAY); + sendCommand(DISPLAYON); +} + +void OLEDDisplay::setBrightness(uint8_t brightness) { + uint8_t contrast = brightness; + if (brightness < 128) { + // Magic values to get a smooth/ step-free transition + contrast = brightness * 1.171; + } else { + contrast = brightness * 1.171 - 43; + } + + uint8_t precharge = 241; + if (brightness == 0) { + precharge = 0; + } + uint8_t comdetect = brightness / 8; + + setContrast(contrast, precharge, comdetect); +} + +void OLEDDisplay::resetOrientation() { + sendCommand(SEGREMAP); + sendCommand(COMSCANINC); //Reset screen rotation or mirroring +} + +void OLEDDisplay::flipScreenVertically() { + sendCommand(SEGREMAP | 0x01); + sendCommand(COMSCANDEC); //Rotate screen 180 Deg +} + +void OLEDDisplay::mirrorScreen() { + sendCommand(SEGREMAP); + sendCommand(COMSCANDEC); //Mirror screen +} + +void OLEDDisplay::clear(void) { + memset(buffer, 0, displayBufferSize); +} + +void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { + uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); + // Always align left + setTextAlignment(TEXT_ALIGN_LEFT); + + // State values + uint16_t length = 0; + uint16_t line = 0; + uint16_t lastPos = 0; + + for (uint16_t i=0;ilogBufferFilled;i++){ + // Everytime we have a \n print + if (this->logBuffer[i] == 10) { + length++; + // Draw string on line `line` from lastPos to length + // Passing 0 as the lenght because we are in TEXT_ALIGN_LEFT + drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0); + // Remember last pos + lastPos = i; + // Reset length + length = 0; + } else { + // Count chars until next linebreak + length++; + } + } + // Draw the remaining string + if (length > 0) { + drawStringInternal(xMove, yMove + line * lineHeight, &this->logBuffer[lastPos], length, 0); + } +} + +uint16_t OLEDDisplay::getWidth(void) { + return displayWidth; +} + +uint16_t OLEDDisplay::getHeight(void) { + return displayHeight; +} + +bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){ + if (logBuffer != NULL) free(logBuffer); + uint16_t size = lines * chars; + if (size > 0) { + this->logBufferLine = 0; // Lines printed + this->logBufferFilled = 0; // Nothing stored yet + this->logBufferMaxLines = lines; // Lines max printable + this->logBufferSize = size; // Total number of characters the buffer can hold + this->logBuffer = (char *) malloc(size * sizeof(uint8_t)); + if(!this->logBuffer) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][setLogBuffer] Not enough memory to create log buffer\n"); + return false; + } + } + return true; +} + +size_t OLEDDisplay::write(uint8_t c) { + if (this->logBufferSize > 0) { + // Don't waste space on \r\n line endings, dropping \r + if (c == 13) return 1; + + // convert UTF-8 character to font table index + c = (this->fontTableLookupFunction)(c); + // drop unknown character + if (c == 0) return 1; + + bool maxLineNotReached = this->logBufferLine < this->logBufferMaxLines; + bool bufferNotFull = this->logBufferFilled < this->logBufferSize; + + // Can we write to the buffer? + if (bufferNotFull && maxLineNotReached) { + this->logBuffer[logBufferFilled] = c; + this->logBufferFilled++; + // Keep track of lines written + if (c == 10) this->logBufferLine++; + } else { + // Max line number is reached + if (!maxLineNotReached) this->logBufferLine--; + + // Find the end of the first line + uint16_t firstLineEnd = 0; + for (uint16_t i=0;ilogBufferFilled;i++) { + if (this->logBuffer[i] == 10){ + // Include last char too + firstLineEnd = i + 1; + break; + } + } + // If there was a line ending + if (firstLineEnd > 0) { + // Calculate the new logBufferFilled value + this->logBufferFilled = logBufferFilled - firstLineEnd; + // Now we move the lines infront of the buffer + memcpy(this->logBuffer, &this->logBuffer[firstLineEnd], logBufferFilled); + } else { + // Let's reuse the buffer if it was full + if (!bufferNotFull) { + this->logBufferFilled = 0; + }// else { + // Nothing to do here + //} + } + write(c); + } + } + // We are always writing all uint8_t to the buffer + return 1; +} + +size_t OLEDDisplay::write(const char* str) { + if (str == NULL) return 0; + size_t length = strlen(str); + for (size_t i = 0; i < length; i++) { + write(str[i]); + } + return length; +} + +#ifdef __MBED__ +int OLEDDisplay::_putc(int c) { + + if (!fontData) + return 1; + if (!logBufferSize) { + uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); + uint16_t lines = this->displayHeight / textHeight; + uint16_t chars = 2 * (this->displayWidth / textHeight); + + if (this->displayHeight % textHeight) + lines++; + if (this->displayWidth % textHeight) + chars++; + setLogBuffer(lines, chars); + } + + return this->write((uint8_t)c); +} +#endif + +// Private functions +void OLEDDisplay::setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { + this->geometry = g; + switch (g) { + case GEOMETRY_128_64: + this->displayWidth = 128; + this->displayHeight = 64; + break; + case GEOMETRY_128_32: + this->displayWidth = 128; + this->displayHeight = 32; + break; + case GEOMETRY_RAWMODE: + this->displayWidth = width > 0 ? width : 128; + this->displayHeight = height > 0 ? height : 64; + break; + } + this->displayBufferSize = displayWidth * displayHeight /8; +} + +void OLEDDisplay::sendInitCommands(void) { + if (geometry == GEOMETRY_RAWMODE) + return; + sendCommand(DISPLAYOFF); + sendCommand(SETDISPLAYCLOCKDIV); + sendCommand(0xF0); // Increase speed of the display max ~96Hz + sendCommand(SETMULTIPLEX); + sendCommand(this->height() - 1); + sendCommand(SETDISPLAYOFFSET); + sendCommand(0x00); + sendCommand(SETSTARTLINE); + sendCommand(CHARGEPUMP); + sendCommand(0x14); + sendCommand(MEMORYMODE); + sendCommand(0x00); + sendCommand(SEGREMAP); + sendCommand(COMSCANINC); + sendCommand(SETCOMPINS); + + if (geometry == GEOMETRY_128_64) { + sendCommand(0x12); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x02); + } + + sendCommand(SETCONTRAST); + + if (geometry == GEOMETRY_128_64) { + sendCommand(0xCF); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x8F); + } + + sendCommand(SETPRECHARGE); + sendCommand(0xF1); + sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) + sendCommand(0x40); //0x40 default, to lower the contrast, put 0 + sendCommand(DISPLAYALLON_RESUME); + sendCommand(NORMALDISPLAY); + sendCommand(0x2e); // stop scroll + sendCommand(DISPLAYON); +} + +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) { + if (width < 0 || height < 0) return; + if (yMove + height < 0 || yMove > this->height()) return; + if (xMove + width < 0 || xMove > this->width()) return; + + uint8_t rasterHeight = 1 + ((height - 1) >> 3); // fast ceil(height / 8.0) + int8_t yOffset = yMove & 7; + + bytesInData = bytesInData == 0 ? width * rasterHeight : bytesInData; + + int16_t initYMove = yMove; + int8_t initYOffset = yOffset; + + + for (uint16_t i = 0; i < bytesInData; i++) { + + // Reset if next horizontal drawing phase is started. + if ( i % rasterHeight == 0) { + yMove = initYMove; + yOffset = initYOffset; + } + + uint8_t currentByte = pgm_read_byte(data + offset + i); + + int16_t xPos = xMove + (i / rasterHeight); + int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * this->width(); + +// int16_t yScreenPos = yMove + yOffset; + int16_t dataPos = xPos + yPos; + + if (dataPos >= 0 && dataPos < displayBufferSize && + xPos >= 0 && xPos < this->width() ) { + + if (yOffset >= 0) { + switch (this->color) { + case WHITE: buffer[dataPos] |= currentByte << yOffset; break; + case BLACK: buffer[dataPos] &= ~(currentByte << yOffset); break; + case INVERSE: buffer[dataPos] ^= currentByte << yOffset; break; + } + + if (dataPos < (displayBufferSize - this->width())) { + switch (this->color) { + case WHITE: buffer[dataPos + this->width()] |= currentByte >> (8 - yOffset); break; + case BLACK: buffer[dataPos + this->width()] &= ~(currentByte >> (8 - yOffset)); break; + case INVERSE: buffer[dataPos + this->width()] ^= currentByte >> (8 - yOffset); break; + } + } + } else { + // Make new offset position + yOffset = -yOffset; + + switch (this->color) { + case WHITE: buffer[dataPos] |= currentByte >> yOffset; break; + case BLACK: buffer[dataPos] &= ~(currentByte >> yOffset); break; + case INVERSE: buffer[dataPos] ^= currentByte >> yOffset; break; + } + + // Prepare for next iteration by moving one block up + yMove -= 8; + + // and setting the new yOffset + yOffset = 8 - yOffset; + } +#ifndef __MBED__ + yield(); +#endif + } + } +} + +// You need to free the char! +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_OLEDDISPLAY("[OLEDDISPLAY][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n"); + return (char*) str.c_str(); + } + str.toCharArray(s, length); + + length--; + + for (uint16_t i=0; i < length; i++) { + char c = (this->fontTableLookupFunction)(s[i]); + if (c!=0) { + s[k++]=c; + } + } + + s[k]=0; + + // This will leak 's' be sure to free it in the calling function. + return s; +} + +void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) { + this->fontTableLookupFunction = function; +} + + +char DefaultFontTableLookup(const uint8_t ch) { + // UTF-8 to font table index converter + // Code form http://playground.arduino.cc/Main/Utf8ascii + static uint8_t LASTCHAR; + + if (ch < 128) { // Standard ASCII-set 0..0x7F handling + LASTCHAR = 0; + return ch; + } + + uint8_t last = LASTCHAR; // get last char + LASTCHAR = ch; + + switch (last) { // conversion depnding on first UTF8-character + case 0xC2: return (uint8_t) ch; + case 0xC3: return (uint8_t) (ch | 0xC0); + case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol + } + + return (uint8_t) 0; // otherwise: return zero, if character has to be ignored +} From bba9c0f2a99cdf5d119a72384fe2623662c5160a Mon Sep 17 00:00:00 2001 From: NoobTracker <63962365+NoobTracker@users.noreply.github.com> Date: Thu, 23 Apr 2020 13:28:33 +0200 Subject: [PATCH 2/6] Moved OLEDDisplay.cpp back into src. I'm new. Sorry. --- OLEDDisplay.cpp | 1022 ----------------------------------------------- 1 file changed, 1022 deletions(-) delete mode 100644 OLEDDisplay.cpp diff --git a/OLEDDisplay.cpp b/OLEDDisplay.cpp deleted file mode 100644 index b4d9cd2..0000000 --- a/OLEDDisplay.cpp +++ /dev/null @@ -1,1022 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn - * Copyright (c) 2018 by Fabrice Weinberg - * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de - * - * 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. - * - * ThingPulse invests considerable time and money to develop these open source libraries. - * Please support us by buying our products (and not the clones) from - * https://thingpulse.com - * - */ - - /* - * TODO Helmut - * - test/finish dislplay.printf() on mbed-os - * - Finish _putc with drawLogBuffer when running display - */ - -#include "OLEDDisplay.h" - -OLEDDisplay::OLEDDisplay() { - - displayWidth = 128; - displayHeight = 64; - displayBufferSize = displayWidth * displayHeight / 8; - color = WHITE; - geometry = GEOMETRY_128_64; - textAlignment = TEXT_ALIGN_LEFT; - fontData = ArialMT_Plain_10; - fontTableLookupFunction = DefaultFontTableLookup; - buffer = NULL; -#ifdef OLEDDISPLAY_DOUBLE_BUFFER - buffer_back = NULL; -#endif -} - -OLEDDisplay::~OLEDDisplay() { - end(); -} - -bool OLEDDisplay::allocateBuffer() { - - logBufferSize = 0; - logBufferFilled = 0; - logBufferLine = 0; - logBufferMaxLines = 0; - logBuffer = NULL; - - if (!this->connect()) { - DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); - return false; - } - - if(this->buffer==NULL) { - this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); - this->buffer += getBufferOffset(); - - if(!this->buffer) { - DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); - return false; - } - } - - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - if(this->buffer_back==NULL) { - this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); - this->buffer_back += getBufferOffset(); - - if(!this->buffer_back) { - DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); - free(this->buffer - getBufferOffset()); - return false; - } - } - #endif - - return true; -} - -bool OLEDDisplay::init() { - - if(!allocateBuffer()) { - return false; - } - - sendInitCommands(); - resetDisplay(); - - return true; -} - -void OLEDDisplay::end() { - if (this->buffer) { free(this->buffer - getBufferOffset()); this->buffer = NULL; } - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - if (this->buffer_back) { free(this->buffer_back - getBufferOffset()); this->buffer_back = NULL; } - #endif - if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; } -} - -void OLEDDisplay::resetDisplay(void) { - clear(); - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - memset(buffer_back, 1, displayBufferSize); - #endif - display(); -} - -void OLEDDisplay::setColor(OLEDDISPLAY_COLOR color) { - this->color = color; -} - -OLEDDISPLAY_COLOR OLEDDisplay::getColor() { - return this->color; -} - -void OLEDDisplay::setPixel(int16_t x, int16_t y) { - if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { - switch (color) { - case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; - case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; - case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; - } - } -} - -void OLEDDisplay::setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color) { - if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { - switch (color) { - case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; - case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; - case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; - } - } -} - -void OLEDDisplay::clearPixel(int16_t x, int16_t y) { - if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { - switch (color) { - case BLACK: buffer[x + (y >> 3) * this->width()] |= (1 << (y & 7)); break; - case WHITE: buffer[x + (y >> 3) * this->width()] &= ~(1 << (y & 7)); break; - case INVERSE: buffer[x + (y >> 3) * this->width()] ^= (1 << (y & 7)); break; - } - } -} - - -// Bresenham's algorithm - thx wikipedia and Adafruit_GFX -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); - _swap_int16_t(x1, y1); - } - - if (x0 > x1) { - _swap_int16_t(x0, x1); - _swap_int16_t(y0, y1); - } - - int16_t dx, dy; - dx = x1 - x0; - dy = abs(y1 - y0); - - int16_t err = dx / 2; - int16_t ystep; - - if (y0 < y1) { - ystep = 1; - } else { - ystep = -1; - } - - for (; x0<=x1; x0++) { - if (steep) { - setPixel(y0, x0); - } else { - setPixel(x0, y0); - } - err -= dy; - if (err < 0) { - y0 += ystep; - err += dx; - } - } -} - -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 - 1, y, height); - drawHorizontalLine(x, y + height - 1, width); -} - -void OLEDDisplay::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height) { - for (int16_t x = xMove; x < xMove + width; x++) { - drawVerticalLine(x, yMove, height); - } -} - -void OLEDDisplay::drawCircle(int16_t x0, int16_t y0, int16_t radius) { - int16_t x = 0, y = radius; - int16_t dp = 1 - radius; - do { - if (dp < 0) - dp = dp + (x++) * 2 + 3; - else - dp = dp + (x++) * 2 - (y--) * 2 + 5; - - setPixel(x0 + x, y0 + y); //For the 8 octants - setPixel(x0 - x, y0 + y); - setPixel(x0 + x, y0 - y); - setPixel(x0 - x, y0 - y); - setPixel(x0 + y, y0 + x); - setPixel(x0 - y, y0 + x); - setPixel(x0 + y, y0 - x); - setPixel(x0 - y, y0 - x); - - } while (x < y); - - setPixel(x0 + radius, y0); - setPixel(x0, y0 + radius); - setPixel(x0 - radius, y0); - setPixel(x0, y0 - radius); -} - -void OLEDDisplay::drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads) { - int16_t x = 0, y = radius; - int16_t dp = 1 - radius; - while (x < y) { - if (dp < 0) - dp = dp + (x++) * 2 + 3; - else - dp = dp + (x++) * 2 - (y--) * 2 + 5; - if (quads & 0x1) { - setPixel(x0 + x, y0 - y); - setPixel(x0 + y, y0 - x); - } - if (quads & 0x2) { - setPixel(x0 - y, y0 - x); - setPixel(x0 - x, y0 - y); - } - if (quads & 0x4) { - setPixel(x0 - y, y0 + x); - setPixel(x0 - x, y0 + y); - } - if (quads & 0x8) { - setPixel(x0 + x, y0 + y); - setPixel(x0 + y, y0 + x); - } - } - if (quads & 0x1 && quads & 0x8) { - setPixel(x0 + radius, y0); - } - if (quads & 0x4 && quads & 0x8) { - setPixel(x0, y0 + radius); - } - if (quads & 0x2 && quads & 0x4) { - setPixel(x0 - radius, y0); - } - if (quads & 0x1 && quads & 0x2) { - setPixel(x0, y0 - 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 { - if (dp < 0) - dp = dp + (x++) * 2 + 3; - else - dp = dp + (x++) * 2 - (y--) * 2 + 5; - - drawHorizontalLine(x0 - x, y0 - y, 2*x + 1); - drawHorizontalLine(x0 - x, y0 + y, 2*x + 1); - drawHorizontalLine(x0 - y, y0 - x, 2*y + 1); - drawHorizontalLine(x0 - y, y0 + x, 2*y + 1); - - - } while (x < y); - drawHorizontalLine(x0 - radius, y0, (2 * radius) + 1); - drawVerticalLine(x0, y0 - radius, 2 * radius); -} - -void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { - if (y < 0 || y >= this->height()) { return; } - - if (x < 0) { - length += x; - x = 0; - } - - if ( (x + length) > this->width()) { - length = (this->width() - x); - } - - if (length <= 0) { return; } - - uint8_t * bufferPtr = buffer; - bufferPtr += (y >> 3) * this->width(); - bufferPtr += x; - - uint8_t drawBit = 1 << (y & 7); - - switch (color) { - case WHITE: while (length--) { - *bufferPtr++ |= drawBit; - }; break; - case BLACK: drawBit = ~drawBit; while (length--) { - *bufferPtr++ &= drawBit; - }; break; - case INVERSE: while (length--) { - *bufferPtr++ ^= drawBit; - }; break; - } -} - -void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { - if (x < 0 || x >= this->width()) return; - - if (y < 0) { - length += y; - y = 0; - } - - if ( (y + length) > this->height()) { - length = (this->height() - y); - } - - if (length <= 0) return; - - - uint8_t yOffset = y & 7; - uint8_t drawBit; - uint8_t *bufferPtr = buffer; - - bufferPtr += (y >> 3) * this->width(); - bufferPtr += x; - - if (yOffset) { - yOffset = 8 - yOffset; - drawBit = ~(0xFF >> (yOffset)); - - if (length < yOffset) { - drawBit &= (0xFF >> (yOffset - length)); - } - - switch (color) { - case WHITE: *bufferPtr |= drawBit; break; - case BLACK: *bufferPtr &= ~drawBit; break; - case INVERSE: *bufferPtr ^= drawBit; break; - } - - if (length < yOffset) return; - - length -= yOffset; - bufferPtr += this->width(); - } - - if (length >= 8) { - switch (color) { - case WHITE: - case BLACK: - drawBit = (color == WHITE) ? 0xFF : 0x00; - do { - *bufferPtr = drawBit; - bufferPtr += this->width(); - length -= 8; - } while (length >= 8); - break; - case INVERSE: - do { - *bufferPtr = ~(*bufferPtr); - bufferPtr += this->width(); - length -= 8; - } while (length >= 8); - break; - } - } - - if (length > 0) { - drawBit = (1 << (length & 7)) - 1; - switch (color) { - case WHITE: *bufferPtr |= drawBit; break; - case BLACK: *bufferPtr &= ~drawBit; break; - case INVERSE: *bufferPtr ^= drawBit; break; - } - } -} - -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 xRadius = x + radius; - uint16_t yRadius = y + radius; - uint16_t doubleRadius = 2 * radius; - uint16_t innerRadius = radius - 2; - - setColor(WHITE); - drawCircleQuads(xRadius, yRadius, radius, 0b00000110); - drawHorizontalLine(xRadius, y, width - doubleRadius + 1); - drawHorizontalLine(xRadius, y + height, width - doubleRadius + 1); - drawCircleQuads(x + width - radius, yRadius, radius, 0b00001001); - - uint16_t maxProgressWidth = (width - doubleRadius + 1) * progress / 100; - - fillCircle(xRadius, yRadius, innerRadius); - fillRect(xRadius + 1, y + 2, maxProgressWidth, height - 3); - fillCircle(xRadius + maxProgressWidth, yRadius, innerRadius); -} - -void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *image) { - drawInternal(xMove, yMove, width, height, image, 0, 0); -} - -void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) { - int16_t widthInXbm = (width + 7) / 8; - uint8_t data = 0; - - for(int16_t y = 0; y < height; y++) { - for(int16_t x = 0; x < width; x++ ) { - if (x & 7) { - data >>= 1; // Move a bit - } else { // Read new data every 8 bit - data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm); - } - // if there is a bit draw it - if (data & 0x01) { - setPixel(xMove + x, yMove + y); - } - } - } -} - -void OLEDDisplay::drawIco16x16(int16_t xMove, int16_t yMove, const char *ico, bool inverse) { - uint16_t data; - - for(int16_t y = 0; y < 16; y++) { - data = pgm_read_byte(ico + (y << 1)) + (pgm_read_byte(ico + (y << 1) + 1) << 8); - for(int16_t x = 0; x < 16; x++ ) { - if ((data & 0x01) ^ inverse) { - setPixelColor(xMove + x, yMove + y, WHITE); - } else { - setPixelColor(xMove + x, yMove + y, BLACK); - } - data >>= 1; // Move a bit - } - } -} - -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; - - uint16_t cursorX = 0; - uint16_t cursorY = 0; - - switch (textAlignment) { - case TEXT_ALIGN_CENTER_BOTH: - yMove -= textHeight >> 1; - // Fallthrough - case TEXT_ALIGN_CENTER: - xMove -= textWidth >> 1; // divide by 2 - break; - case TEXT_ALIGN_RIGHT: - xMove -= textWidth; - break; - case TEXT_ALIGN_LEFT: - break; - } - - // Don't draw anything if it is not on the screen. - if (xMove + textWidth < 0 || xMove > this->width() ) {return;} - if (yMove + textHeight < 0 || yMove > this->width() ) {return;} - - for (uint16_t j = 0; j < textLength; j++) { - int16_t xPos = xMove + cursorX; - int16_t yPos = yMove + cursorY; - - uint8_t code = text[j]; - if (code >= firstChar) { - uint8_t charCode = code - firstChar; - - // 4 Bytes per char code - uint8_t msbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES ); // MSB \ JumpAddress - uint8_t lsbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_LSB); // LSB / - uint8_t charByteSize = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_SIZE); // Size - uint8_t currentCharWidth = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); // Width - - // Test if the char is drawable - if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { - // Get the position of the char data - uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + ((msbJumpToChar << 8) + lsbJumpToChar); - drawInternal(xPos, yPos, currentCharWidth, textHeight, fontData, charDataPosition, charByteSize); - } - - cursorX += currentCharWidth; - } - } -} - - -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! - char* text = utf8ascii(strUser); - - uint16_t yOffset = 0; - // If the string should be centered vertically too - // we need to now how heigh the string is. - if (textAlignment == TEXT_ALIGN_CENTER_BOTH) { - uint16_t lb = 0; - // Find number of linebreaks in text - for (uint16_t i=0;text[i] != 0; i++) { - lb += (text[i] == 10); - } - // Calculate center - yOffset = (lb * lineHeight) / 2; - } - - uint16_t line = 0; - char* textPart = strtok(text,"\n"); - while (textPart != NULL) { - uint16_t length = strlen(textPart); - drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length)); - textPart = strtok(NULL, "\n"); - } - free(text); -} - -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); - - char* text = utf8ascii(strUser); - - uint16_t length = strlen(text); - uint16_t lastDrawnPos = 0; - uint16_t lineNumber = 0; - uint16_t strWidth = 0; - - uint16_t preferredBreakpoint = 0; - uint16_t widthAtBreakpoint = 0; - - for (uint16_t i = 0; i < length; i++) { - strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[i] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); - - // Always try to break on a space or dash - if (text[i] == ' ' || text[i]== '-') { - preferredBreakpoint = i; - widthAtBreakpoint = strWidth; - } - - if (strWidth >= maxLineWidth) { - if (preferredBreakpoint == 0) { - preferredBreakpoint = i; - widthAtBreakpoint = strWidth; - } - drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], preferredBreakpoint - lastDrawnPos, widthAtBreakpoint); - lastDrawnPos = preferredBreakpoint + 1; - // It is possible that we did not draw all letters to i so we need - // to account for the width of the chars from `i - preferredBreakpoint` - // by calculating the width we did not draw yet. - strWidth = strWidth - widthAtBreakpoint; - preferredBreakpoint = 0; - } - } - - // Draw last part if needed - if (lastDrawnPos < length) { - drawStringInternal(xMove, yMove + lineNumber * lineHeight , &text[lastDrawnPos], length - lastDrawnPos, getStringWidth(&text[lastDrawnPos], length - lastDrawnPos)); - } - - free(text); -} - -uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) { - uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); - - uint16_t stringWidth = 0; - uint16_t maxWidth = 0; - - while (length--) { - stringWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[length] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); - if (text[length] == 10) { - maxWidth = max(maxWidth, stringWidth); - stringWidth = 0; - } - } - - return max(maxWidth, stringWidth); -} - -uint16_t OLEDDisplay::getStringWidth(String strUser) { - char* text = utf8ascii(strUser); - uint16_t length = strlen(text); - uint16_t width = getStringWidth(text, length); - free(text); - return width; -} - -void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { - this->textAlignment = textAlignment; -} - -void OLEDDisplay::setFont(const uint8_t *fontData) { - this->fontData = fontData; -} - -void OLEDDisplay::displayOn(void) { - sendCommand(DISPLAYON); -} - -void OLEDDisplay::displayOff(void) { - sendCommand(DISPLAYOFF); -} - -void OLEDDisplay::invertDisplay(void) { - sendCommand(INVERTDISPLAY); -} - -void OLEDDisplay::normalDisplay(void) { - sendCommand(NORMALDISPLAY); -} - -void OLEDDisplay::setContrast(uint8_t contrast, uint8_t precharge, uint8_t comdetect) { - sendCommand(SETPRECHARGE); //0xD9 - sendCommand(precharge); //0xF1 default, to lower the contrast, put 1-1F - sendCommand(SETCONTRAST); - sendCommand(contrast); // 0-255 - sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) - sendCommand(comdetect); //0x40 default, to lower the contrast, put 0 - sendCommand(DISPLAYALLON_RESUME); - sendCommand(NORMALDISPLAY); - sendCommand(DISPLAYON); -} - -void OLEDDisplay::setBrightness(uint8_t brightness) { - uint8_t contrast = brightness; - if (brightness < 128) { - // Magic values to get a smooth/ step-free transition - contrast = brightness * 1.171; - } else { - contrast = brightness * 1.171 - 43; - } - - uint8_t precharge = 241; - if (brightness == 0) { - precharge = 0; - } - uint8_t comdetect = brightness / 8; - - setContrast(contrast, precharge, comdetect); -} - -void OLEDDisplay::resetOrientation() { - sendCommand(SEGREMAP); - sendCommand(COMSCANINC); //Reset screen rotation or mirroring -} - -void OLEDDisplay::flipScreenVertically() { - sendCommand(SEGREMAP | 0x01); - sendCommand(COMSCANDEC); //Rotate screen 180 Deg -} - -void OLEDDisplay::mirrorScreen() { - sendCommand(SEGREMAP); - sendCommand(COMSCANDEC); //Mirror screen -} - -void OLEDDisplay::clear(void) { - memset(buffer, 0, displayBufferSize); -} - -void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { - uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); - // Always align left - setTextAlignment(TEXT_ALIGN_LEFT); - - // State values - uint16_t length = 0; - uint16_t line = 0; - uint16_t lastPos = 0; - - for (uint16_t i=0;ilogBufferFilled;i++){ - // Everytime we have a \n print - if (this->logBuffer[i] == 10) { - length++; - // Draw string on line `line` from lastPos to length - // Passing 0 as the lenght because we are in TEXT_ALIGN_LEFT - drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0); - // Remember last pos - lastPos = i; - // Reset length - length = 0; - } else { - // Count chars until next linebreak - length++; - } - } - // Draw the remaining string - if (length > 0) { - drawStringInternal(xMove, yMove + line * lineHeight, &this->logBuffer[lastPos], length, 0); - } -} - -uint16_t OLEDDisplay::getWidth(void) { - return displayWidth; -} - -uint16_t OLEDDisplay::getHeight(void) { - return displayHeight; -} - -bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){ - if (logBuffer != NULL) free(logBuffer); - uint16_t size = lines * chars; - if (size > 0) { - this->logBufferLine = 0; // Lines printed - this->logBufferFilled = 0; // Nothing stored yet - this->logBufferMaxLines = lines; // Lines max printable - this->logBufferSize = size; // Total number of characters the buffer can hold - this->logBuffer = (char *) malloc(size * sizeof(uint8_t)); - if(!this->logBuffer) { - DEBUG_OLEDDISPLAY("[OLEDDISPLAY][setLogBuffer] Not enough memory to create log buffer\n"); - return false; - } - } - return true; -} - -size_t OLEDDisplay::write(uint8_t c) { - if (this->logBufferSize > 0) { - // Don't waste space on \r\n line endings, dropping \r - if (c == 13) return 1; - - // convert UTF-8 character to font table index - c = (this->fontTableLookupFunction)(c); - // drop unknown character - if (c == 0) return 1; - - bool maxLineNotReached = this->logBufferLine < this->logBufferMaxLines; - bool bufferNotFull = this->logBufferFilled < this->logBufferSize; - - // Can we write to the buffer? - if (bufferNotFull && maxLineNotReached) { - this->logBuffer[logBufferFilled] = c; - this->logBufferFilled++; - // Keep track of lines written - if (c == 10) this->logBufferLine++; - } else { - // Max line number is reached - if (!maxLineNotReached) this->logBufferLine--; - - // Find the end of the first line - uint16_t firstLineEnd = 0; - for (uint16_t i=0;ilogBufferFilled;i++) { - if (this->logBuffer[i] == 10){ - // Include last char too - firstLineEnd = i + 1; - break; - } - } - // If there was a line ending - if (firstLineEnd > 0) { - // Calculate the new logBufferFilled value - this->logBufferFilled = logBufferFilled - firstLineEnd; - // Now we move the lines infront of the buffer - memcpy(this->logBuffer, &this->logBuffer[firstLineEnd], logBufferFilled); - } else { - // Let's reuse the buffer if it was full - if (!bufferNotFull) { - this->logBufferFilled = 0; - }// else { - // Nothing to do here - //} - } - write(c); - } - } - // We are always writing all uint8_t to the buffer - return 1; -} - -size_t OLEDDisplay::write(const char* str) { - if (str == NULL) return 0; - size_t length = strlen(str); - for (size_t i = 0; i < length; i++) { - write(str[i]); - } - return length; -} - -#ifdef __MBED__ -int OLEDDisplay::_putc(int c) { - - if (!fontData) - return 1; - if (!logBufferSize) { - uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); - uint16_t lines = this->displayHeight / textHeight; - uint16_t chars = 2 * (this->displayWidth / textHeight); - - if (this->displayHeight % textHeight) - lines++; - if (this->displayWidth % textHeight) - chars++; - setLogBuffer(lines, chars); - } - - return this->write((uint8_t)c); -} -#endif - -// Private functions -void OLEDDisplay::setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { - this->geometry = g; - switch (g) { - case GEOMETRY_128_64: - this->displayWidth = 128; - this->displayHeight = 64; - break; - case GEOMETRY_128_32: - this->displayWidth = 128; - this->displayHeight = 32; - break; - case GEOMETRY_RAWMODE: - this->displayWidth = width > 0 ? width : 128; - this->displayHeight = height > 0 ? height : 64; - break; - } - this->displayBufferSize = displayWidth * displayHeight /8; -} - -void OLEDDisplay::sendInitCommands(void) { - if (geometry == GEOMETRY_RAWMODE) - return; - sendCommand(DISPLAYOFF); - sendCommand(SETDISPLAYCLOCKDIV); - sendCommand(0xF0); // Increase speed of the display max ~96Hz - sendCommand(SETMULTIPLEX); - sendCommand(this->height() - 1); - sendCommand(SETDISPLAYOFFSET); - sendCommand(0x00); - sendCommand(SETSTARTLINE); - sendCommand(CHARGEPUMP); - sendCommand(0x14); - sendCommand(MEMORYMODE); - sendCommand(0x00); - sendCommand(SEGREMAP); - sendCommand(COMSCANINC); - sendCommand(SETCOMPINS); - - if (geometry == GEOMETRY_128_64) { - sendCommand(0x12); - } else if (geometry == GEOMETRY_128_32) { - sendCommand(0x02); - } - - sendCommand(SETCONTRAST); - - if (geometry == GEOMETRY_128_64) { - sendCommand(0xCF); - } else if (geometry == GEOMETRY_128_32) { - sendCommand(0x8F); - } - - sendCommand(SETPRECHARGE); - sendCommand(0xF1); - sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) - sendCommand(0x40); //0x40 default, to lower the contrast, put 0 - sendCommand(DISPLAYALLON_RESUME); - sendCommand(NORMALDISPLAY); - sendCommand(0x2e); // stop scroll - sendCommand(DISPLAYON); -} - -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) { - if (width < 0 || height < 0) return; - if (yMove + height < 0 || yMove > this->height()) return; - if (xMove + width < 0 || xMove > this->width()) return; - - uint8_t rasterHeight = 1 + ((height - 1) >> 3); // fast ceil(height / 8.0) - int8_t yOffset = yMove & 7; - - bytesInData = bytesInData == 0 ? width * rasterHeight : bytesInData; - - int16_t initYMove = yMove; - int8_t initYOffset = yOffset; - - - for (uint16_t i = 0; i < bytesInData; i++) { - - // Reset if next horizontal drawing phase is started. - if ( i % rasterHeight == 0) { - yMove = initYMove; - yOffset = initYOffset; - } - - uint8_t currentByte = pgm_read_byte(data + offset + i); - - int16_t xPos = xMove + (i / rasterHeight); - int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * this->width(); - -// int16_t yScreenPos = yMove + yOffset; - int16_t dataPos = xPos + yPos; - - if (dataPos >= 0 && dataPos < displayBufferSize && - xPos >= 0 && xPos < this->width() ) { - - if (yOffset >= 0) { - switch (this->color) { - case WHITE: buffer[dataPos] |= currentByte << yOffset; break; - case BLACK: buffer[dataPos] &= ~(currentByte << yOffset); break; - case INVERSE: buffer[dataPos] ^= currentByte << yOffset; break; - } - - if (dataPos < (displayBufferSize - this->width())) { - switch (this->color) { - case WHITE: buffer[dataPos + this->width()] |= currentByte >> (8 - yOffset); break; - case BLACK: buffer[dataPos + this->width()] &= ~(currentByte >> (8 - yOffset)); break; - case INVERSE: buffer[dataPos + this->width()] ^= currentByte >> (8 - yOffset); break; - } - } - } else { - // Make new offset position - yOffset = -yOffset; - - switch (this->color) { - case WHITE: buffer[dataPos] |= currentByte >> yOffset; break; - case BLACK: buffer[dataPos] &= ~(currentByte >> yOffset); break; - case INVERSE: buffer[dataPos] ^= currentByte >> yOffset; break; - } - - // Prepare for next iteration by moving one block up - yMove -= 8; - - // and setting the new yOffset - yOffset = 8 - yOffset; - } -#ifndef __MBED__ - yield(); -#endif - } - } -} - -// You need to free the char! -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_OLEDDISPLAY("[OLEDDISPLAY][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n"); - return (char*) str.c_str(); - } - str.toCharArray(s, length); - - length--; - - for (uint16_t i=0; i < length; i++) { - char c = (this->fontTableLookupFunction)(s[i]); - if (c!=0) { - s[k++]=c; - } - } - - s[k]=0; - - // This will leak 's' be sure to free it in the calling function. - return s; -} - -void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) { - this->fontTableLookupFunction = function; -} - - -char DefaultFontTableLookup(const uint8_t ch) { - // UTF-8 to font table index converter - // Code form http://playground.arduino.cc/Main/Utf8ascii - static uint8_t LASTCHAR; - - if (ch < 128) { // Standard ASCII-set 0..0x7F handling - LASTCHAR = 0; - return ch; - } - - uint8_t last = LASTCHAR; // get last char - LASTCHAR = ch; - - switch (last) { // conversion depnding on first UTF8-character - case 0xC2: return (uint8_t) ch; - case 0xC3: return (uint8_t) (ch | 0xC0); - case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol - } - - return (uint8_t) 0; // otherwise: return zero, if character has to be ignored -} From 487e43bc437151ce8c6fbcefd513e3b77790633c Mon Sep 17 00:00:00 2001 From: NoobTracker <63962365+NoobTracker@users.noreply.github.com> Date: Thu, 23 Apr 2020 13:29:29 +0200 Subject: [PATCH 3/6] Fixed fillCircle() --- src/OLEDDisplay.cpp | 2044 +++++++++++++++++++++---------------------- 1 file changed, 1022 insertions(+), 1022 deletions(-) diff --git a/src/OLEDDisplay.cpp b/src/OLEDDisplay.cpp index df1832f..b4d9cd2 100644 --- a/src/OLEDDisplay.cpp +++ b/src/OLEDDisplay.cpp @@ -1,1022 +1,1022 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn - * Copyright (c) 2018 by Fabrice Weinberg - * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de - * - * 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. - * - * ThingPulse invests considerable time and money to develop these open source libraries. - * Please support us by buying our products (and not the clones) from - * https://thingpulse.com - * - */ - - /* - * TODO Helmut - * - test/finish dislplay.printf() on mbed-os - * - Finish _putc with drawLogBuffer when running display - */ - -#include "OLEDDisplay.h" - -OLEDDisplay::OLEDDisplay() { - - displayWidth = 128; - displayHeight = 64; - displayBufferSize = displayWidth * displayHeight / 8; - color = WHITE; - geometry = GEOMETRY_128_64; - textAlignment = TEXT_ALIGN_LEFT; - fontData = ArialMT_Plain_10; - fontTableLookupFunction = DefaultFontTableLookup; - buffer = NULL; -#ifdef OLEDDISPLAY_DOUBLE_BUFFER - buffer_back = NULL; -#endif -} - -OLEDDisplay::~OLEDDisplay() { - end(); -} - -bool OLEDDisplay::allocateBuffer() { - - logBufferSize = 0; - logBufferFilled = 0; - logBufferLine = 0; - logBufferMaxLines = 0; - logBuffer = NULL; - - if (!this->connect()) { - DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); - return false; - } - - if(this->buffer==NULL) { - this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); - this->buffer += getBufferOffset(); - - if(!this->buffer) { - DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); - return false; - } - } - - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - if(this->buffer_back==NULL) { - this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); - this->buffer_back += getBufferOffset(); - - if(!this->buffer_back) { - DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); - free(this->buffer - getBufferOffset()); - return false; - } - } - #endif - - return true; -} - -bool OLEDDisplay::init() { - - if(!allocateBuffer()) { - return false; - } - - sendInitCommands(); - resetDisplay(); - - return true; -} - -void OLEDDisplay::end() { - if (this->buffer) { free(this->buffer - getBufferOffset()); this->buffer = NULL; } - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - if (this->buffer_back) { free(this->buffer_back - getBufferOffset()); this->buffer_back = NULL; } - #endif - if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; } -} - -void OLEDDisplay::resetDisplay(void) { - clear(); - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - memset(buffer_back, 1, displayBufferSize); - #endif - display(); -} - -void OLEDDisplay::setColor(OLEDDISPLAY_COLOR color) { - this->color = color; -} - -OLEDDISPLAY_COLOR OLEDDisplay::getColor() { - return this->color; -} - -void OLEDDisplay::setPixel(int16_t x, int16_t y) { - if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { - switch (color) { - case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; - case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; - case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; - } - } -} - -void OLEDDisplay::setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color) { - if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { - switch (color) { - case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; - case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; - case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; - } - } -} - -void OLEDDisplay::clearPixel(int16_t x, int16_t y) { - if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { - switch (color) { - case BLACK: buffer[x + (y >> 3) * this->width()] |= (1 << (y & 7)); break; - case WHITE: buffer[x + (y >> 3) * this->width()] &= ~(1 << (y & 7)); break; - case INVERSE: buffer[x + (y >> 3) * this->width()] ^= (1 << (y & 7)); break; - } - } -} - - -// Bresenham's algorithm - thx wikipedia and Adafruit_GFX -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); - _swap_int16_t(x1, y1); - } - - if (x0 > x1) { - _swap_int16_t(x0, x1); - _swap_int16_t(y0, y1); - } - - int16_t dx, dy; - dx = x1 - x0; - dy = abs(y1 - y0); - - int16_t err = dx / 2; - int16_t ystep; - - if (y0 < y1) { - ystep = 1; - } else { - ystep = -1; - } - - for (; x0<=x1; x0++) { - if (steep) { - setPixel(y0, x0); - } else { - setPixel(x0, y0); - } - err -= dy; - if (err < 0) { - y0 += ystep; - err += dx; - } - } -} - -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 - 1, y, height); - drawHorizontalLine(x, y + height - 1, width); -} - -void OLEDDisplay::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height) { - for (int16_t x = xMove; x < xMove + width; x++) { - drawVerticalLine(x, yMove, height); - } -} - -void OLEDDisplay::drawCircle(int16_t x0, int16_t y0, int16_t radius) { - int16_t x = 0, y = radius; - int16_t dp = 1 - radius; - do { - if (dp < 0) - dp = dp + (x++) * 2 + 3; - else - dp = dp + (x++) * 2 - (y--) * 2 + 5; - - setPixel(x0 + x, y0 + y); //For the 8 octants - setPixel(x0 - x, y0 + y); - setPixel(x0 + x, y0 - y); - setPixel(x0 - x, y0 - y); - setPixel(x0 + y, y0 + x); - setPixel(x0 - y, y0 + x); - setPixel(x0 + y, y0 - x); - setPixel(x0 - y, y0 - x); - - } while (x < y); - - setPixel(x0 + radius, y0); - setPixel(x0, y0 + radius); - setPixel(x0 - radius, y0); - setPixel(x0, y0 - radius); -} - -void OLEDDisplay::drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads) { - int16_t x = 0, y = radius; - int16_t dp = 1 - radius; - while (x < y) { - if (dp < 0) - dp = dp + (x++) * 2 + 3; - else - dp = dp + (x++) * 2 - (y--) * 2 + 5; - if (quads & 0x1) { - setPixel(x0 + x, y0 - y); - setPixel(x0 + y, y0 - x); - } - if (quads & 0x2) { - setPixel(x0 - y, y0 - x); - setPixel(x0 - x, y0 - y); - } - if (quads & 0x4) { - setPixel(x0 - y, y0 + x); - setPixel(x0 - x, y0 + y); - } - if (quads & 0x8) { - setPixel(x0 + x, y0 + y); - setPixel(x0 + y, y0 + x); - } - } - if (quads & 0x1 && quads & 0x8) { - setPixel(x0 + radius, y0); - } - if (quads & 0x4 && quads & 0x8) { - setPixel(x0, y0 + radius); - } - if (quads & 0x2 && quads & 0x4) { - setPixel(x0 - radius, y0); - } - if (quads & 0x1 && quads & 0x2) { - setPixel(x0, y0 - 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 { - if (dp < 0) - dp = dp + (x++) * 2 + 3; - else - dp = dp + (x++) * 2 - (y--) * 2 + 5; - - drawHorizontalLine(x0 - x, y0 - y, 2*x); - drawHorizontalLine(x0 - x, y0 + y, 2*x); - drawHorizontalLine(x0 - y, y0 - x, 2*y); - drawHorizontalLine(x0 - y, y0 + x, 2*y); - - - } while (x < y); - drawHorizontalLine(x0 - radius, y0, 2 * radius); - -} - -void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { - if (y < 0 || y >= this->height()) { return; } - - if (x < 0) { - length += x; - x = 0; - } - - if ( (x + length) > this->width()) { - length = (this->width() - x); - } - - if (length <= 0) { return; } - - uint8_t * bufferPtr = buffer; - bufferPtr += (y >> 3) * this->width(); - bufferPtr += x; - - uint8_t drawBit = 1 << (y & 7); - - switch (color) { - case WHITE: while (length--) { - *bufferPtr++ |= drawBit; - }; break; - case BLACK: drawBit = ~drawBit; while (length--) { - *bufferPtr++ &= drawBit; - }; break; - case INVERSE: while (length--) { - *bufferPtr++ ^= drawBit; - }; break; - } -} - -void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { - if (x < 0 || x >= this->width()) return; - - if (y < 0) { - length += y; - y = 0; - } - - if ( (y + length) > this->height()) { - length = (this->height() - y); - } - - if (length <= 0) return; - - - uint8_t yOffset = y & 7; - uint8_t drawBit; - uint8_t *bufferPtr = buffer; - - bufferPtr += (y >> 3) * this->width(); - bufferPtr += x; - - if (yOffset) { - yOffset = 8 - yOffset; - drawBit = ~(0xFF >> (yOffset)); - - if (length < yOffset) { - drawBit &= (0xFF >> (yOffset - length)); - } - - switch (color) { - case WHITE: *bufferPtr |= drawBit; break; - case BLACK: *bufferPtr &= ~drawBit; break; - case INVERSE: *bufferPtr ^= drawBit; break; - } - - if (length < yOffset) return; - - length -= yOffset; - bufferPtr += this->width(); - } - - if (length >= 8) { - switch (color) { - case WHITE: - case BLACK: - drawBit = (color == WHITE) ? 0xFF : 0x00; - do { - *bufferPtr = drawBit; - bufferPtr += this->width(); - length -= 8; - } while (length >= 8); - break; - case INVERSE: - do { - *bufferPtr = ~(*bufferPtr); - bufferPtr += this->width(); - length -= 8; - } while (length >= 8); - break; - } - } - - if (length > 0) { - drawBit = (1 << (length & 7)) - 1; - switch (color) { - case WHITE: *bufferPtr |= drawBit; break; - case BLACK: *bufferPtr &= ~drawBit; break; - case INVERSE: *bufferPtr ^= drawBit; break; - } - } -} - -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 xRadius = x + radius; - uint16_t yRadius = y + radius; - uint16_t doubleRadius = 2 * radius; - uint16_t innerRadius = radius - 2; - - setColor(WHITE); - drawCircleQuads(xRadius, yRadius, radius, 0b00000110); - drawHorizontalLine(xRadius, y, width - doubleRadius + 1); - drawHorizontalLine(xRadius, y + height, width - doubleRadius + 1); - drawCircleQuads(x + width - radius, yRadius, radius, 0b00001001); - - uint16_t maxProgressWidth = (width - doubleRadius + 1) * progress / 100; - - fillCircle(xRadius, yRadius, innerRadius); - fillRect(xRadius + 1, y + 2, maxProgressWidth, height - 3); - fillCircle(xRadius + maxProgressWidth, yRadius, innerRadius); -} - -void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *image) { - drawInternal(xMove, yMove, width, height, image, 0, 0); -} - -void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) { - int16_t widthInXbm = (width + 7) / 8; - uint8_t data = 0; - - for(int16_t y = 0; y < height; y++) { - for(int16_t x = 0; x < width; x++ ) { - if (x & 7) { - data >>= 1; // Move a bit - } else { // Read new data every 8 bit - data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm); - } - // if there is a bit draw it - if (data & 0x01) { - setPixel(xMove + x, yMove + y); - } - } - } -} - -void OLEDDisplay::drawIco16x16(int16_t xMove, int16_t yMove, const char *ico, bool inverse) { - uint16_t data; - - for(int16_t y = 0; y < 16; y++) { - data = pgm_read_byte(ico + (y << 1)) + (pgm_read_byte(ico + (y << 1) + 1) << 8); - for(int16_t x = 0; x < 16; x++ ) { - if ((data & 0x01) ^ inverse) { - setPixelColor(xMove + x, yMove + y, WHITE); - } else { - setPixelColor(xMove + x, yMove + y, BLACK); - } - data >>= 1; // Move a bit - } - } -} - -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; - - uint16_t cursorX = 0; - uint16_t cursorY = 0; - - switch (textAlignment) { - case TEXT_ALIGN_CENTER_BOTH: - yMove -= textHeight >> 1; - // Fallthrough - case TEXT_ALIGN_CENTER: - xMove -= textWidth >> 1; // divide by 2 - break; - case TEXT_ALIGN_RIGHT: - xMove -= textWidth; - break; - case TEXT_ALIGN_LEFT: - break; - } - - // Don't draw anything if it is not on the screen. - if (xMove + textWidth < 0 || xMove > this->width() ) {return;} - if (yMove + textHeight < 0 || yMove > this->width() ) {return;} - - for (uint16_t j = 0; j < textLength; j++) { - int16_t xPos = xMove + cursorX; - int16_t yPos = yMove + cursorY; - - uint8_t code = text[j]; - if (code >= firstChar) { - uint8_t charCode = code - firstChar; - - // 4 Bytes per char code - uint8_t msbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES ); // MSB \ JumpAddress - uint8_t lsbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_LSB); // LSB / - uint8_t charByteSize = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_SIZE); // Size - uint8_t currentCharWidth = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); // Width - - // Test if the char is drawable - if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { - // Get the position of the char data - uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + ((msbJumpToChar << 8) + lsbJumpToChar); - drawInternal(xPos, yPos, currentCharWidth, textHeight, fontData, charDataPosition, charByteSize); - } - - cursorX += currentCharWidth; - } - } -} - - -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! - char* text = utf8ascii(strUser); - - uint16_t yOffset = 0; - // If the string should be centered vertically too - // we need to now how heigh the string is. - if (textAlignment == TEXT_ALIGN_CENTER_BOTH) { - uint16_t lb = 0; - // Find number of linebreaks in text - for (uint16_t i=0;text[i] != 0; i++) { - lb += (text[i] == 10); - } - // Calculate center - yOffset = (lb * lineHeight) / 2; - } - - uint16_t line = 0; - char* textPart = strtok(text,"\n"); - while (textPart != NULL) { - uint16_t length = strlen(textPart); - drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length)); - textPart = strtok(NULL, "\n"); - } - free(text); -} - -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); - - char* text = utf8ascii(strUser); - - uint16_t length = strlen(text); - uint16_t lastDrawnPos = 0; - uint16_t lineNumber = 0; - uint16_t strWidth = 0; - - uint16_t preferredBreakpoint = 0; - uint16_t widthAtBreakpoint = 0; - - for (uint16_t i = 0; i < length; i++) { - strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[i] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); - - // Always try to break on a space or dash - if (text[i] == ' ' || text[i]== '-') { - preferredBreakpoint = i; - widthAtBreakpoint = strWidth; - } - - if (strWidth >= maxLineWidth) { - if (preferredBreakpoint == 0) { - preferredBreakpoint = i; - widthAtBreakpoint = strWidth; - } - drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], preferredBreakpoint - lastDrawnPos, widthAtBreakpoint); - lastDrawnPos = preferredBreakpoint + 1; - // It is possible that we did not draw all letters to i so we need - // to account for the width of the chars from `i - preferredBreakpoint` - // by calculating the width we did not draw yet. - strWidth = strWidth - widthAtBreakpoint; - preferredBreakpoint = 0; - } - } - - // Draw last part if needed - if (lastDrawnPos < length) { - drawStringInternal(xMove, yMove + lineNumber * lineHeight , &text[lastDrawnPos], length - lastDrawnPos, getStringWidth(&text[lastDrawnPos], length - lastDrawnPos)); - } - - free(text); -} - -uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) { - uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); - - uint16_t stringWidth = 0; - uint16_t maxWidth = 0; - - while (length--) { - stringWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[length] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); - if (text[length] == 10) { - maxWidth = max(maxWidth, stringWidth); - stringWidth = 0; - } - } - - return max(maxWidth, stringWidth); -} - -uint16_t OLEDDisplay::getStringWidth(String strUser) { - char* text = utf8ascii(strUser); - uint16_t length = strlen(text); - uint16_t width = getStringWidth(text, length); - free(text); - return width; -} - -void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { - this->textAlignment = textAlignment; -} - -void OLEDDisplay::setFont(const uint8_t *fontData) { - this->fontData = fontData; -} - -void OLEDDisplay::displayOn(void) { - sendCommand(DISPLAYON); -} - -void OLEDDisplay::displayOff(void) { - sendCommand(DISPLAYOFF); -} - -void OLEDDisplay::invertDisplay(void) { - sendCommand(INVERTDISPLAY); -} - -void OLEDDisplay::normalDisplay(void) { - sendCommand(NORMALDISPLAY); -} - -void OLEDDisplay::setContrast(uint8_t contrast, uint8_t precharge, uint8_t comdetect) { - sendCommand(SETPRECHARGE); //0xD9 - sendCommand(precharge); //0xF1 default, to lower the contrast, put 1-1F - sendCommand(SETCONTRAST); - sendCommand(contrast); // 0-255 - sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) - sendCommand(comdetect); //0x40 default, to lower the contrast, put 0 - sendCommand(DISPLAYALLON_RESUME); - sendCommand(NORMALDISPLAY); - sendCommand(DISPLAYON); -} - -void OLEDDisplay::setBrightness(uint8_t brightness) { - uint8_t contrast = brightness; - if (brightness < 128) { - // Magic values to get a smooth/ step-free transition - contrast = brightness * 1.171; - } else { - contrast = brightness * 1.171 - 43; - } - - uint8_t precharge = 241; - if (brightness == 0) { - precharge = 0; - } - uint8_t comdetect = brightness / 8; - - setContrast(contrast, precharge, comdetect); -} - -void OLEDDisplay::resetOrientation() { - sendCommand(SEGREMAP); - sendCommand(COMSCANINC); //Reset screen rotation or mirroring -} - -void OLEDDisplay::flipScreenVertically() { - sendCommand(SEGREMAP | 0x01); - sendCommand(COMSCANDEC); //Rotate screen 180 Deg -} - -void OLEDDisplay::mirrorScreen() { - sendCommand(SEGREMAP); - sendCommand(COMSCANDEC); //Mirror screen -} - -void OLEDDisplay::clear(void) { - memset(buffer, 0, displayBufferSize); -} - -void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { - uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); - // Always align left - setTextAlignment(TEXT_ALIGN_LEFT); - - // State values - uint16_t length = 0; - uint16_t line = 0; - uint16_t lastPos = 0; - - for (uint16_t i=0;ilogBufferFilled;i++){ - // Everytime we have a \n print - if (this->logBuffer[i] == 10) { - length++; - // Draw string on line `line` from lastPos to length - // Passing 0 as the lenght because we are in TEXT_ALIGN_LEFT - drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0); - // Remember last pos - lastPos = i; - // Reset length - length = 0; - } else { - // Count chars until next linebreak - length++; - } - } - // Draw the remaining string - if (length > 0) { - drawStringInternal(xMove, yMove + line * lineHeight, &this->logBuffer[lastPos], length, 0); - } -} - -uint16_t OLEDDisplay::getWidth(void) { - return displayWidth; -} - -uint16_t OLEDDisplay::getHeight(void) { - return displayHeight; -} - -bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){ - if (logBuffer != NULL) free(logBuffer); - uint16_t size = lines * chars; - if (size > 0) { - this->logBufferLine = 0; // Lines printed - this->logBufferFilled = 0; // Nothing stored yet - this->logBufferMaxLines = lines; // Lines max printable - this->logBufferSize = size; // Total number of characters the buffer can hold - this->logBuffer = (char *) malloc(size * sizeof(uint8_t)); - if(!this->logBuffer) { - DEBUG_OLEDDISPLAY("[OLEDDISPLAY][setLogBuffer] Not enough memory to create log buffer\n"); - return false; - } - } - return true; -} - -size_t OLEDDisplay::write(uint8_t c) { - if (this->logBufferSize > 0) { - // Don't waste space on \r\n line endings, dropping \r - if (c == 13) return 1; - - // convert UTF-8 character to font table index - c = (this->fontTableLookupFunction)(c); - // drop unknown character - if (c == 0) return 1; - - bool maxLineNotReached = this->logBufferLine < this->logBufferMaxLines; - bool bufferNotFull = this->logBufferFilled < this->logBufferSize; - - // Can we write to the buffer? - if (bufferNotFull && maxLineNotReached) { - this->logBuffer[logBufferFilled] = c; - this->logBufferFilled++; - // Keep track of lines written - if (c == 10) this->logBufferLine++; - } else { - // Max line number is reached - if (!maxLineNotReached) this->logBufferLine--; - - // Find the end of the first line - uint16_t firstLineEnd = 0; - for (uint16_t i=0;ilogBufferFilled;i++) { - if (this->logBuffer[i] == 10){ - // Include last char too - firstLineEnd = i + 1; - break; - } - } - // If there was a line ending - if (firstLineEnd > 0) { - // Calculate the new logBufferFilled value - this->logBufferFilled = logBufferFilled - firstLineEnd; - // Now we move the lines infront of the buffer - memcpy(this->logBuffer, &this->logBuffer[firstLineEnd], logBufferFilled); - } else { - // Let's reuse the buffer if it was full - if (!bufferNotFull) { - this->logBufferFilled = 0; - }// else { - // Nothing to do here - //} - } - write(c); - } - } - // We are always writing all uint8_t to the buffer - return 1; -} - -size_t OLEDDisplay::write(const char* str) { - if (str == NULL) return 0; - size_t length = strlen(str); - for (size_t i = 0; i < length; i++) { - write(str[i]); - } - return length; -} - -#ifdef __MBED__ -int OLEDDisplay::_putc(int c) { - - if (!fontData) - return 1; - if (!logBufferSize) { - uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); - uint16_t lines = this->displayHeight / textHeight; - uint16_t chars = 2 * (this->displayWidth / textHeight); - - if (this->displayHeight % textHeight) - lines++; - if (this->displayWidth % textHeight) - chars++; - setLogBuffer(lines, chars); - } - - return this->write((uint8_t)c); -} -#endif - -// Private functions -void OLEDDisplay::setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { - this->geometry = g; - switch (g) { - case GEOMETRY_128_64: - this->displayWidth = 128; - this->displayHeight = 64; - break; - case GEOMETRY_128_32: - this->displayWidth = 128; - this->displayHeight = 32; - break; - case GEOMETRY_RAWMODE: - this->displayWidth = width > 0 ? width : 128; - this->displayHeight = height > 0 ? height : 64; - break; - } - this->displayBufferSize = displayWidth * displayHeight /8; -} - -void OLEDDisplay::sendInitCommands(void) { - if (geometry == GEOMETRY_RAWMODE) - return; - sendCommand(DISPLAYOFF); - sendCommand(SETDISPLAYCLOCKDIV); - sendCommand(0xF0); // Increase speed of the display max ~96Hz - sendCommand(SETMULTIPLEX); - sendCommand(this->height() - 1); - sendCommand(SETDISPLAYOFFSET); - sendCommand(0x00); - sendCommand(SETSTARTLINE); - sendCommand(CHARGEPUMP); - sendCommand(0x14); - sendCommand(MEMORYMODE); - sendCommand(0x00); - sendCommand(SEGREMAP); - sendCommand(COMSCANINC); - sendCommand(SETCOMPINS); - - if (geometry == GEOMETRY_128_64) { - sendCommand(0x12); - } else if (geometry == GEOMETRY_128_32) { - sendCommand(0x02); - } - - sendCommand(SETCONTRAST); - - if (geometry == GEOMETRY_128_64) { - sendCommand(0xCF); - } else if (geometry == GEOMETRY_128_32) { - sendCommand(0x8F); - } - - sendCommand(SETPRECHARGE); - sendCommand(0xF1); - sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) - sendCommand(0x40); //0x40 default, to lower the contrast, put 0 - sendCommand(DISPLAYALLON_RESUME); - sendCommand(NORMALDISPLAY); - sendCommand(0x2e); // stop scroll - sendCommand(DISPLAYON); -} - -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) { - if (width < 0 || height < 0) return; - if (yMove + height < 0 || yMove > this->height()) return; - if (xMove + width < 0 || xMove > this->width()) return; - - uint8_t rasterHeight = 1 + ((height - 1) >> 3); // fast ceil(height / 8.0) - int8_t yOffset = yMove & 7; - - bytesInData = bytesInData == 0 ? width * rasterHeight : bytesInData; - - int16_t initYMove = yMove; - int8_t initYOffset = yOffset; - - - for (uint16_t i = 0; i < bytesInData; i++) { - - // Reset if next horizontal drawing phase is started. - if ( i % rasterHeight == 0) { - yMove = initYMove; - yOffset = initYOffset; - } - - uint8_t currentByte = pgm_read_byte(data + offset + i); - - int16_t xPos = xMove + (i / rasterHeight); - int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * this->width(); - -// int16_t yScreenPos = yMove + yOffset; - int16_t dataPos = xPos + yPos; - - if (dataPos >= 0 && dataPos < displayBufferSize && - xPos >= 0 && xPos < this->width() ) { - - if (yOffset >= 0) { - switch (this->color) { - case WHITE: buffer[dataPos] |= currentByte << yOffset; break; - case BLACK: buffer[dataPos] &= ~(currentByte << yOffset); break; - case INVERSE: buffer[dataPos] ^= currentByte << yOffset; break; - } - - if (dataPos < (displayBufferSize - this->width())) { - switch (this->color) { - case WHITE: buffer[dataPos + this->width()] |= currentByte >> (8 - yOffset); break; - case BLACK: buffer[dataPos + this->width()] &= ~(currentByte >> (8 - yOffset)); break; - case INVERSE: buffer[dataPos + this->width()] ^= currentByte >> (8 - yOffset); break; - } - } - } else { - // Make new offset position - yOffset = -yOffset; - - switch (this->color) { - case WHITE: buffer[dataPos] |= currentByte >> yOffset; break; - case BLACK: buffer[dataPos] &= ~(currentByte >> yOffset); break; - case INVERSE: buffer[dataPos] ^= currentByte >> yOffset; break; - } - - // Prepare for next iteration by moving one block up - yMove -= 8; - - // and setting the new yOffset - yOffset = 8 - yOffset; - } -#ifndef __MBED__ - yield(); -#endif - } - } -} - -// You need to free the char! -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_OLEDDISPLAY("[OLEDDISPLAY][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n"); - return (char*) str.c_str(); - } - str.toCharArray(s, length); - - length--; - - for (uint16_t i=0; i < length; i++) { - char c = (this->fontTableLookupFunction)(s[i]); - if (c!=0) { - s[k++]=c; - } - } - - s[k]=0; - - // This will leak 's' be sure to free it in the calling function. - return s; -} - -void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) { - this->fontTableLookupFunction = function; -} - - -char DefaultFontTableLookup(const uint8_t ch) { - // UTF-8 to font table index converter - // Code form http://playground.arduino.cc/Main/Utf8ascii - static uint8_t LASTCHAR; - - if (ch < 128) { // Standard ASCII-set 0..0x7F handling - LASTCHAR = 0; - return ch; - } - - uint8_t last = LASTCHAR; // get last char - LASTCHAR = ch; - - switch (last) { // conversion depnding on first UTF8-character - case 0xC2: return (uint8_t) ch; - case 0xC3: return (uint8_t) (ch | 0xC0); - case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol - } - - return (uint8_t) 0; // otherwise: return zero, if character has to be ignored -} +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + * + * 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. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + + /* + * TODO Helmut + * - test/finish dislplay.printf() on mbed-os + * - Finish _putc with drawLogBuffer when running display + */ + +#include "OLEDDisplay.h" + +OLEDDisplay::OLEDDisplay() { + + displayWidth = 128; + displayHeight = 64; + displayBufferSize = displayWidth * displayHeight / 8; + color = WHITE; + geometry = GEOMETRY_128_64; + textAlignment = TEXT_ALIGN_LEFT; + fontData = ArialMT_Plain_10; + fontTableLookupFunction = DefaultFontTableLookup; + buffer = NULL; +#ifdef OLEDDISPLAY_DOUBLE_BUFFER + buffer_back = NULL; +#endif +} + +OLEDDisplay::~OLEDDisplay() { + end(); +} + +bool OLEDDisplay::allocateBuffer() { + + logBufferSize = 0; + logBufferFilled = 0; + logBufferLine = 0; + logBufferMaxLines = 0; + logBuffer = NULL; + + if (!this->connect()) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); + return false; + } + + if(this->buffer==NULL) { + this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); + this->buffer += getBufferOffset(); + + if(!this->buffer) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); + return false; + } + } + + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + if(this->buffer_back==NULL) { + this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); + this->buffer_back += getBufferOffset(); + + if(!this->buffer_back) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); + free(this->buffer - getBufferOffset()); + return false; + } + } + #endif + + return true; +} + +bool OLEDDisplay::init() { + + if(!allocateBuffer()) { + return false; + } + + sendInitCommands(); + resetDisplay(); + + return true; +} + +void OLEDDisplay::end() { + if (this->buffer) { free(this->buffer - getBufferOffset()); this->buffer = NULL; } + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + if (this->buffer_back) { free(this->buffer_back - getBufferOffset()); this->buffer_back = NULL; } + #endif + if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; } +} + +void OLEDDisplay::resetDisplay(void) { + clear(); + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + memset(buffer_back, 1, displayBufferSize); + #endif + display(); +} + +void OLEDDisplay::setColor(OLEDDISPLAY_COLOR color) { + this->color = color; +} + +OLEDDISPLAY_COLOR OLEDDisplay::getColor() { + return this->color; +} + +void OLEDDisplay::setPixel(int16_t x, int16_t y) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; + case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + +void OLEDDisplay::setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case WHITE: buffer[x + (y / 8) * this->width()] |= (1 << (y & 7)); break; + case BLACK: buffer[x + (y / 8) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y / 8) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + +void OLEDDisplay::clearPixel(int16_t x, int16_t y) { + if (x >= 0 && x < this->width() && y >= 0 && y < this->height()) { + switch (color) { + case BLACK: buffer[x + (y >> 3) * this->width()] |= (1 << (y & 7)); break; + case WHITE: buffer[x + (y >> 3) * this->width()] &= ~(1 << (y & 7)); break; + case INVERSE: buffer[x + (y >> 3) * this->width()] ^= (1 << (y & 7)); break; + } + } +} + + +// Bresenham's algorithm - thx wikipedia and Adafruit_GFX +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); + _swap_int16_t(x1, y1); + } + + if (x0 > x1) { + _swap_int16_t(x0, x1); + _swap_int16_t(y0, y1); + } + + int16_t dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int16_t err = dx / 2; + int16_t ystep; + + if (y0 < y1) { + ystep = 1; + } else { + ystep = -1; + } + + for (; x0<=x1; x0++) { + if (steep) { + setPixel(y0, x0); + } else { + setPixel(x0, y0); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } +} + +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 - 1, y, height); + drawHorizontalLine(x, y + height - 1, width); +} + +void OLEDDisplay::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t height) { + for (int16_t x = xMove; x < xMove + width; x++) { + drawVerticalLine(x, yMove, height); + } +} + +void OLEDDisplay::drawCircle(int16_t x0, int16_t y0, int16_t radius) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + do { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + setPixel(x0 + x, y0 + y); //For the 8 octants + setPixel(x0 - x, y0 + y); + setPixel(x0 + x, y0 - y); + setPixel(x0 - x, y0 - y); + setPixel(x0 + y, y0 + x); + setPixel(x0 - y, y0 + x); + setPixel(x0 + y, y0 - x); + setPixel(x0 - y, y0 - x); + + } while (x < y); + + setPixel(x0 + radius, y0); + setPixel(x0, y0 + radius); + setPixel(x0 - radius, y0); + setPixel(x0, y0 - radius); +} + +void OLEDDisplay::drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads) { + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + while (x < y) { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + if (quads & 0x1) { + setPixel(x0 + x, y0 - y); + setPixel(x0 + y, y0 - x); + } + if (quads & 0x2) { + setPixel(x0 - y, y0 - x); + setPixel(x0 - x, y0 - y); + } + if (quads & 0x4) { + setPixel(x0 - y, y0 + x); + setPixel(x0 - x, y0 + y); + } + if (quads & 0x8) { + setPixel(x0 + x, y0 + y); + setPixel(x0 + y, y0 + x); + } + } + if (quads & 0x1 && quads & 0x8) { + setPixel(x0 + radius, y0); + } + if (quads & 0x4 && quads & 0x8) { + setPixel(x0, y0 + radius); + } + if (quads & 0x2 && quads & 0x4) { + setPixel(x0 - radius, y0); + } + if (quads & 0x1 && quads & 0x2) { + setPixel(x0, y0 - 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 { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + drawHorizontalLine(x0 - x, y0 - y, 2*x + 1); + drawHorizontalLine(x0 - x, y0 + y, 2*x + 1); + drawHorizontalLine(x0 - y, y0 - x, 2*y + 1); + drawHorizontalLine(x0 - y, y0 + x, 2*y + 1); + + + } while (x < y); + drawHorizontalLine(x0 - radius, y0, (2 * radius) + 1); + drawVerticalLine(x0, y0 - radius, 2 * radius); +} + +void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { + if (y < 0 || y >= this->height()) { return; } + + if (x < 0) { + length += x; + x = 0; + } + + if ( (x + length) > this->width()) { + length = (this->width() - x); + } + + if (length <= 0) { return; } + + uint8_t * bufferPtr = buffer; + bufferPtr += (y >> 3) * this->width(); + bufferPtr += x; + + uint8_t drawBit = 1 << (y & 7); + + switch (color) { + case WHITE: while (length--) { + *bufferPtr++ |= drawBit; + }; break; + case BLACK: drawBit = ~drawBit; while (length--) { + *bufferPtr++ &= drawBit; + }; break; + case INVERSE: while (length--) { + *bufferPtr++ ^= drawBit; + }; break; + } +} + +void OLEDDisplay::drawVerticalLine(int16_t x, int16_t y, int16_t length) { + if (x < 0 || x >= this->width()) return; + + if (y < 0) { + length += y; + y = 0; + } + + if ( (y + length) > this->height()) { + length = (this->height() - y); + } + + if (length <= 0) return; + + + uint8_t yOffset = y & 7; + uint8_t drawBit; + uint8_t *bufferPtr = buffer; + + bufferPtr += (y >> 3) * this->width(); + bufferPtr += x; + + if (yOffset) { + yOffset = 8 - yOffset; + drawBit = ~(0xFF >> (yOffset)); + + if (length < yOffset) { + drawBit &= (0xFF >> (yOffset - length)); + } + + switch (color) { + case WHITE: *bufferPtr |= drawBit; break; + case BLACK: *bufferPtr &= ~drawBit; break; + case INVERSE: *bufferPtr ^= drawBit; break; + } + + if (length < yOffset) return; + + length -= yOffset; + bufferPtr += this->width(); + } + + if (length >= 8) { + switch (color) { + case WHITE: + case BLACK: + drawBit = (color == WHITE) ? 0xFF : 0x00; + do { + *bufferPtr = drawBit; + bufferPtr += this->width(); + length -= 8; + } while (length >= 8); + break; + case INVERSE: + do { + *bufferPtr = ~(*bufferPtr); + bufferPtr += this->width(); + length -= 8; + } while (length >= 8); + break; + } + } + + if (length > 0) { + drawBit = (1 << (length & 7)) - 1; + switch (color) { + case WHITE: *bufferPtr |= drawBit; break; + case BLACK: *bufferPtr &= ~drawBit; break; + case INVERSE: *bufferPtr ^= drawBit; break; + } + } +} + +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 xRadius = x + radius; + uint16_t yRadius = y + radius; + uint16_t doubleRadius = 2 * radius; + uint16_t innerRadius = radius - 2; + + setColor(WHITE); + drawCircleQuads(xRadius, yRadius, radius, 0b00000110); + drawHorizontalLine(xRadius, y, width - doubleRadius + 1); + drawHorizontalLine(xRadius, y + height, width - doubleRadius + 1); + drawCircleQuads(x + width - radius, yRadius, radius, 0b00001001); + + uint16_t maxProgressWidth = (width - doubleRadius + 1) * progress / 100; + + fillCircle(xRadius, yRadius, innerRadius); + fillRect(xRadius + 1, y + 2, maxProgressWidth, height - 3); + fillCircle(xRadius + maxProgressWidth, yRadius, innerRadius); +} + +void OLEDDisplay::drawFastImage(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *image) { + drawInternal(xMove, yMove, width, height, image, 0, 0); +} + +void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) { + int16_t widthInXbm = (width + 7) / 8; + uint8_t data = 0; + + for(int16_t y = 0; y < height; y++) { + for(int16_t x = 0; x < width; x++ ) { + if (x & 7) { + data >>= 1; // Move a bit + } else { // Read new data every 8 bit + data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm); + } + // if there is a bit draw it + if (data & 0x01) { + setPixel(xMove + x, yMove + y); + } + } + } +} + +void OLEDDisplay::drawIco16x16(int16_t xMove, int16_t yMove, const char *ico, bool inverse) { + uint16_t data; + + for(int16_t y = 0; y < 16; y++) { + data = pgm_read_byte(ico + (y << 1)) + (pgm_read_byte(ico + (y << 1) + 1) << 8); + for(int16_t x = 0; x < 16; x++ ) { + if ((data & 0x01) ^ inverse) { + setPixelColor(xMove + x, yMove + y, WHITE); + } else { + setPixelColor(xMove + x, yMove + y, BLACK); + } + data >>= 1; // Move a bit + } + } +} + +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; + + uint16_t cursorX = 0; + uint16_t cursorY = 0; + + switch (textAlignment) { + case TEXT_ALIGN_CENTER_BOTH: + yMove -= textHeight >> 1; + // Fallthrough + case TEXT_ALIGN_CENTER: + xMove -= textWidth >> 1; // divide by 2 + break; + case TEXT_ALIGN_RIGHT: + xMove -= textWidth; + break; + case TEXT_ALIGN_LEFT: + break; + } + + // Don't draw anything if it is not on the screen. + if (xMove + textWidth < 0 || xMove > this->width() ) {return;} + if (yMove + textHeight < 0 || yMove > this->width() ) {return;} + + for (uint16_t j = 0; j < textLength; j++) { + int16_t xPos = xMove + cursorX; + int16_t yPos = yMove + cursorY; + + uint8_t code = text[j]; + if (code >= firstChar) { + uint8_t charCode = code - firstChar; + + // 4 Bytes per char code + uint8_t msbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES ); // MSB \ JumpAddress + uint8_t lsbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_LSB); // LSB / + uint8_t charByteSize = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_SIZE); // Size + uint8_t currentCharWidth = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); // Width + + // Test if the char is drawable + if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { + // Get the position of the char data + uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + ((msbJumpToChar << 8) + lsbJumpToChar); + drawInternal(xPos, yPos, currentCharWidth, textHeight, fontData, charDataPosition, charByteSize); + } + + cursorX += currentCharWidth; + } + } +} + + +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! + char* text = utf8ascii(strUser); + + uint16_t yOffset = 0; + // If the string should be centered vertically too + // we need to now how heigh the string is. + if (textAlignment == TEXT_ALIGN_CENTER_BOTH) { + uint16_t lb = 0; + // Find number of linebreaks in text + for (uint16_t i=0;text[i] != 0; i++) { + lb += (text[i] == 10); + } + // Calculate center + yOffset = (lb * lineHeight) / 2; + } + + uint16_t line = 0; + char* textPart = strtok(text,"\n"); + while (textPart != NULL) { + uint16_t length = strlen(textPart); + drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length)); + textPart = strtok(NULL, "\n"); + } + free(text); +} + +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); + + char* text = utf8ascii(strUser); + + uint16_t length = strlen(text); + uint16_t lastDrawnPos = 0; + uint16_t lineNumber = 0; + uint16_t strWidth = 0; + + uint16_t preferredBreakpoint = 0; + uint16_t widthAtBreakpoint = 0; + + for (uint16_t i = 0; i < length; i++) { + strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[i] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); + + // Always try to break on a space or dash + if (text[i] == ' ' || text[i]== '-') { + preferredBreakpoint = i; + widthAtBreakpoint = strWidth; + } + + if (strWidth >= maxLineWidth) { + if (preferredBreakpoint == 0) { + preferredBreakpoint = i; + widthAtBreakpoint = strWidth; + } + drawStringInternal(xMove, yMove + (lineNumber++) * lineHeight , &text[lastDrawnPos], preferredBreakpoint - lastDrawnPos, widthAtBreakpoint); + lastDrawnPos = preferredBreakpoint + 1; + // It is possible that we did not draw all letters to i so we need + // to account for the width of the chars from `i - preferredBreakpoint` + // by calculating the width we did not draw yet. + strWidth = strWidth - widthAtBreakpoint; + preferredBreakpoint = 0; + } + } + + // Draw last part if needed + if (lastDrawnPos < length) { + drawStringInternal(xMove, yMove + lineNumber * lineHeight , &text[lastDrawnPos], length - lastDrawnPos, getStringWidth(&text[lastDrawnPos], length - lastDrawnPos)); + } + + free(text); +} + +uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) { + uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + + uint16_t stringWidth = 0; + uint16_t maxWidth = 0; + + while (length--) { + stringWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[length] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); + if (text[length] == 10) { + maxWidth = max(maxWidth, stringWidth); + stringWidth = 0; + } + } + + return max(maxWidth, stringWidth); +} + +uint16_t OLEDDisplay::getStringWidth(String strUser) { + char* text = utf8ascii(strUser); + uint16_t length = strlen(text); + uint16_t width = getStringWidth(text, length); + free(text); + return width; +} + +void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { + this->textAlignment = textAlignment; +} + +void OLEDDisplay::setFont(const uint8_t *fontData) { + this->fontData = fontData; +} + +void OLEDDisplay::displayOn(void) { + sendCommand(DISPLAYON); +} + +void OLEDDisplay::displayOff(void) { + sendCommand(DISPLAYOFF); +} + +void OLEDDisplay::invertDisplay(void) { + sendCommand(INVERTDISPLAY); +} + +void OLEDDisplay::normalDisplay(void) { + sendCommand(NORMALDISPLAY); +} + +void OLEDDisplay::setContrast(uint8_t contrast, uint8_t precharge, uint8_t comdetect) { + sendCommand(SETPRECHARGE); //0xD9 + sendCommand(precharge); //0xF1 default, to lower the contrast, put 1-1F + sendCommand(SETCONTRAST); + sendCommand(contrast); // 0-255 + sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) + sendCommand(comdetect); //0x40 default, to lower the contrast, put 0 + sendCommand(DISPLAYALLON_RESUME); + sendCommand(NORMALDISPLAY); + sendCommand(DISPLAYON); +} + +void OLEDDisplay::setBrightness(uint8_t brightness) { + uint8_t contrast = brightness; + if (brightness < 128) { + // Magic values to get a smooth/ step-free transition + contrast = brightness * 1.171; + } else { + contrast = brightness * 1.171 - 43; + } + + uint8_t precharge = 241; + if (brightness == 0) { + precharge = 0; + } + uint8_t comdetect = brightness / 8; + + setContrast(contrast, precharge, comdetect); +} + +void OLEDDisplay::resetOrientation() { + sendCommand(SEGREMAP); + sendCommand(COMSCANINC); //Reset screen rotation or mirroring +} + +void OLEDDisplay::flipScreenVertically() { + sendCommand(SEGREMAP | 0x01); + sendCommand(COMSCANDEC); //Rotate screen 180 Deg +} + +void OLEDDisplay::mirrorScreen() { + sendCommand(SEGREMAP); + sendCommand(COMSCANDEC); //Mirror screen +} + +void OLEDDisplay::clear(void) { + memset(buffer, 0, displayBufferSize); +} + +void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { + uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); + // Always align left + setTextAlignment(TEXT_ALIGN_LEFT); + + // State values + uint16_t length = 0; + uint16_t line = 0; + uint16_t lastPos = 0; + + for (uint16_t i=0;ilogBufferFilled;i++){ + // Everytime we have a \n print + if (this->logBuffer[i] == 10) { + length++; + // Draw string on line `line` from lastPos to length + // Passing 0 as the lenght because we are in TEXT_ALIGN_LEFT + drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0); + // Remember last pos + lastPos = i; + // Reset length + length = 0; + } else { + // Count chars until next linebreak + length++; + } + } + // Draw the remaining string + if (length > 0) { + drawStringInternal(xMove, yMove + line * lineHeight, &this->logBuffer[lastPos], length, 0); + } +} + +uint16_t OLEDDisplay::getWidth(void) { + return displayWidth; +} + +uint16_t OLEDDisplay::getHeight(void) { + return displayHeight; +} + +bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){ + if (logBuffer != NULL) free(logBuffer); + uint16_t size = lines * chars; + if (size > 0) { + this->logBufferLine = 0; // Lines printed + this->logBufferFilled = 0; // Nothing stored yet + this->logBufferMaxLines = lines; // Lines max printable + this->logBufferSize = size; // Total number of characters the buffer can hold + this->logBuffer = (char *) malloc(size * sizeof(uint8_t)); + if(!this->logBuffer) { + DEBUG_OLEDDISPLAY("[OLEDDISPLAY][setLogBuffer] Not enough memory to create log buffer\n"); + return false; + } + } + return true; +} + +size_t OLEDDisplay::write(uint8_t c) { + if (this->logBufferSize > 0) { + // Don't waste space on \r\n line endings, dropping \r + if (c == 13) return 1; + + // convert UTF-8 character to font table index + c = (this->fontTableLookupFunction)(c); + // drop unknown character + if (c == 0) return 1; + + bool maxLineNotReached = this->logBufferLine < this->logBufferMaxLines; + bool bufferNotFull = this->logBufferFilled < this->logBufferSize; + + // Can we write to the buffer? + if (bufferNotFull && maxLineNotReached) { + this->logBuffer[logBufferFilled] = c; + this->logBufferFilled++; + // Keep track of lines written + if (c == 10) this->logBufferLine++; + } else { + // Max line number is reached + if (!maxLineNotReached) this->logBufferLine--; + + // Find the end of the first line + uint16_t firstLineEnd = 0; + for (uint16_t i=0;ilogBufferFilled;i++) { + if (this->logBuffer[i] == 10){ + // Include last char too + firstLineEnd = i + 1; + break; + } + } + // If there was a line ending + if (firstLineEnd > 0) { + // Calculate the new logBufferFilled value + this->logBufferFilled = logBufferFilled - firstLineEnd; + // Now we move the lines infront of the buffer + memcpy(this->logBuffer, &this->logBuffer[firstLineEnd], logBufferFilled); + } else { + // Let's reuse the buffer if it was full + if (!bufferNotFull) { + this->logBufferFilled = 0; + }// else { + // Nothing to do here + //} + } + write(c); + } + } + // We are always writing all uint8_t to the buffer + return 1; +} + +size_t OLEDDisplay::write(const char* str) { + if (str == NULL) return 0; + size_t length = strlen(str); + for (size_t i = 0; i < length; i++) { + write(str[i]); + } + return length; +} + +#ifdef __MBED__ +int OLEDDisplay::_putc(int c) { + + if (!fontData) + return 1; + if (!logBufferSize) { + uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); + uint16_t lines = this->displayHeight / textHeight; + uint16_t chars = 2 * (this->displayWidth / textHeight); + + if (this->displayHeight % textHeight) + lines++; + if (this->displayWidth % textHeight) + chars++; + setLogBuffer(lines, chars); + } + + return this->write((uint8_t)c); +} +#endif + +// Private functions +void OLEDDisplay::setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { + this->geometry = g; + switch (g) { + case GEOMETRY_128_64: + this->displayWidth = 128; + this->displayHeight = 64; + break; + case GEOMETRY_128_32: + this->displayWidth = 128; + this->displayHeight = 32; + break; + case GEOMETRY_RAWMODE: + this->displayWidth = width > 0 ? width : 128; + this->displayHeight = height > 0 ? height : 64; + break; + } + this->displayBufferSize = displayWidth * displayHeight /8; +} + +void OLEDDisplay::sendInitCommands(void) { + if (geometry == GEOMETRY_RAWMODE) + return; + sendCommand(DISPLAYOFF); + sendCommand(SETDISPLAYCLOCKDIV); + sendCommand(0xF0); // Increase speed of the display max ~96Hz + sendCommand(SETMULTIPLEX); + sendCommand(this->height() - 1); + sendCommand(SETDISPLAYOFFSET); + sendCommand(0x00); + sendCommand(SETSTARTLINE); + sendCommand(CHARGEPUMP); + sendCommand(0x14); + sendCommand(MEMORYMODE); + sendCommand(0x00); + sendCommand(SEGREMAP); + sendCommand(COMSCANINC); + sendCommand(SETCOMPINS); + + if (geometry == GEOMETRY_128_64) { + sendCommand(0x12); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x02); + } + + sendCommand(SETCONTRAST); + + if (geometry == GEOMETRY_128_64) { + sendCommand(0xCF); + } else if (geometry == GEOMETRY_128_32) { + sendCommand(0x8F); + } + + sendCommand(SETPRECHARGE); + sendCommand(0xF1); + sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) + sendCommand(0x40); //0x40 default, to lower the contrast, put 0 + sendCommand(DISPLAYALLON_RESUME); + sendCommand(NORMALDISPLAY); + sendCommand(0x2e); // stop scroll + sendCommand(DISPLAYON); +} + +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) { + if (width < 0 || height < 0) return; + if (yMove + height < 0 || yMove > this->height()) return; + if (xMove + width < 0 || xMove > this->width()) return; + + uint8_t rasterHeight = 1 + ((height - 1) >> 3); // fast ceil(height / 8.0) + int8_t yOffset = yMove & 7; + + bytesInData = bytesInData == 0 ? width * rasterHeight : bytesInData; + + int16_t initYMove = yMove; + int8_t initYOffset = yOffset; + + + for (uint16_t i = 0; i < bytesInData; i++) { + + // Reset if next horizontal drawing phase is started. + if ( i % rasterHeight == 0) { + yMove = initYMove; + yOffset = initYOffset; + } + + uint8_t currentByte = pgm_read_byte(data + offset + i); + + int16_t xPos = xMove + (i / rasterHeight); + int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * this->width(); + +// int16_t yScreenPos = yMove + yOffset; + int16_t dataPos = xPos + yPos; + + if (dataPos >= 0 && dataPos < displayBufferSize && + xPos >= 0 && xPos < this->width() ) { + + if (yOffset >= 0) { + switch (this->color) { + case WHITE: buffer[dataPos] |= currentByte << yOffset; break; + case BLACK: buffer[dataPos] &= ~(currentByte << yOffset); break; + case INVERSE: buffer[dataPos] ^= currentByte << yOffset; break; + } + + if (dataPos < (displayBufferSize - this->width())) { + switch (this->color) { + case WHITE: buffer[dataPos + this->width()] |= currentByte >> (8 - yOffset); break; + case BLACK: buffer[dataPos + this->width()] &= ~(currentByte >> (8 - yOffset)); break; + case INVERSE: buffer[dataPos + this->width()] ^= currentByte >> (8 - yOffset); break; + } + } + } else { + // Make new offset position + yOffset = -yOffset; + + switch (this->color) { + case WHITE: buffer[dataPos] |= currentByte >> yOffset; break; + case BLACK: buffer[dataPos] &= ~(currentByte >> yOffset); break; + case INVERSE: buffer[dataPos] ^= currentByte >> yOffset; break; + } + + // Prepare for next iteration by moving one block up + yMove -= 8; + + // and setting the new yOffset + yOffset = 8 - yOffset; + } +#ifndef __MBED__ + yield(); +#endif + } + } +} + +// You need to free the char! +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_OLEDDISPLAY("[OLEDDISPLAY][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n"); + return (char*) str.c_str(); + } + str.toCharArray(s, length); + + length--; + + for (uint16_t i=0; i < length; i++) { + char c = (this->fontTableLookupFunction)(s[i]); + if (c!=0) { + s[k++]=c; + } + } + + s[k]=0; + + // This will leak 's' be sure to free it in the calling function. + return s; +} + +void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) { + this->fontTableLookupFunction = function; +} + + +char DefaultFontTableLookup(const uint8_t ch) { + // UTF-8 to font table index converter + // Code form http://playground.arduino.cc/Main/Utf8ascii + static uint8_t LASTCHAR; + + if (ch < 128) { // Standard ASCII-set 0..0x7F handling + LASTCHAR = 0; + return ch; + } + + uint8_t last = LASTCHAR; // get last char + LASTCHAR = ch; + + switch (last) { // conversion depnding on first UTF8-character + case 0xC2: return (uint8_t) ch; + case 0xC3: return (uint8_t) (ch | 0xC0); + case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol + } + + return (uint8_t) 0; // otherwise: return zero, if character has to be ignored +} From 660ef9504238b03020605aa7f8f1fa0331db89eb Mon Sep 17 00:00:00 2001 From: NoobTracker <63962365+NoobTracker@users.noreply.github.com> Date: Thu, 23 Apr 2020 15:43:57 +0200 Subject: [PATCH 4/6] Improved Circle() functions, added fillRing() Improved existing Circle() functions, added fillRing(). --- src/OLEDDisplay.cpp | 527 ++++++++++++++++++++++++++++---------------- 1 file changed, 341 insertions(+), 186 deletions(-) diff --git a/src/OLEDDisplay.cpp b/src/OLEDDisplay.cpp index b4d9cd2..c6511c6 100644 --- a/src/OLEDDisplay.cpp +++ b/src/OLEDDisplay.cpp @@ -1,55 +1,55 @@ /** - * The MIT License (MIT) - * - * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn - * Copyright (c) 2018 by Fabrice Weinberg - * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de - * - * 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. - * - * ThingPulse invests considerable time and money to develop these open source libraries. - * Please support us by buying our products (and not the clones) from - * https://thingpulse.com - * - */ - - /* - * TODO Helmut - * - test/finish dislplay.printf() on mbed-os - * - Finish _putc with drawLogBuffer when running display - */ + The MIT License (MIT) + + Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + Copyright (c) 2018 by Fabrice Weinberg + Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + + 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. + + ThingPulse invests considerable time and money to develop these open source libraries. + Please support us by buying our products (and not the clones) from + https://thingpulse.com + +*/ + +/* + TODO Helmut + - test/finish dislplay.printf() on mbed-os + - Finish _putc with drawLogBuffer when running display +*/ #include "OLEDDisplay.h" OLEDDisplay::OLEDDisplay() { - displayWidth = 128; - displayHeight = 64; - displayBufferSize = displayWidth * displayHeight / 8; - color = WHITE; - geometry = GEOMETRY_128_64; - textAlignment = TEXT_ALIGN_LEFT; - fontData = ArialMT_Plain_10; - fontTableLookupFunction = DefaultFontTableLookup; - buffer = NULL; + displayWidth = 128; + displayHeight = 64; + displayBufferSize = displayWidth * displayHeight / 8; + color = WHITE; + geometry = GEOMETRY_128_64; + textAlignment = TEXT_ALIGN_LEFT; + fontData = ArialMT_Plain_10; + fontTableLookupFunction = DefaultFontTableLookup; + buffer = NULL; #ifdef OLEDDISPLAY_DOUBLE_BUFFER - buffer_back = NULL; + buffer_back = NULL; #endif } @@ -64,41 +64,41 @@ bool OLEDDisplay::allocateBuffer() { logBufferLine = 0; logBufferMaxLines = 0; logBuffer = NULL; - + if (!this->connect()) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n"); return false; } - if(this->buffer==NULL) { + if (this->buffer == NULL) { this->buffer = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); this->buffer += getBufferOffset(); - if(!this->buffer) { + if (!this->buffer) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n"); return false; } } - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - if(this->buffer_back==NULL) { +#ifdef OLEDDISPLAY_DOUBLE_BUFFER + if (this->buffer_back == NULL) { this->buffer_back = (uint8_t*) malloc((sizeof(uint8_t) * displayBufferSize) + getBufferOffset()); this->buffer_back += getBufferOffset(); - - if(!this->buffer_back) { + + if (!this->buffer_back) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n"); free(this->buffer - getBufferOffset()); return false; } } - #endif +#endif return true; } bool OLEDDisplay::init() { - if(!allocateBuffer()) { + if (!allocateBuffer()) { return false; } @@ -109,18 +109,27 @@ bool OLEDDisplay::init() { } void OLEDDisplay::end() { - if (this->buffer) { free(this->buffer - getBufferOffset()); this->buffer = NULL; } - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - if (this->buffer_back) { free(this->buffer_back - getBufferOffset()); this->buffer_back = NULL; } - #endif - if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; } + if (this->buffer) { + free(this->buffer - getBufferOffset()); + this->buffer = NULL; + } +#ifdef OLEDDISPLAY_DOUBLE_BUFFER + if (this->buffer_back) { + free(this->buffer_back - getBufferOffset()); + this->buffer_back = NULL; + } +#endif + if (this->logBuffer != NULL) { + free(this->logBuffer); + this->logBuffer = NULL; + } } void OLEDDisplay::resetDisplay(void) { clear(); - #ifdef OLEDDISPLAY_DOUBLE_BUFFER +#ifdef OLEDDISPLAY_DOUBLE_BUFFER memset(buffer_back, 1, displayBufferSize); - #endif +#endif display(); } @@ -189,7 +198,7 @@ void OLEDDisplay::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1) { ystep = -1; } - for (; x0<=x1; x0++) { + for (; x0 <= x1; x0++) { if (steep) { setPixel(y0, x0); } else { @@ -216,94 +225,234 @@ void OLEDDisplay::fillRect(int16_t xMove, int16_t yMove, int16_t width, int16_t } } -void OLEDDisplay::drawCircle(int16_t x0, int16_t y0, int16_t radius) { - int16_t x = 0, y = radius; - int16_t dp = 1 - radius; - do { - if (dp < 0) - dp = dp + (x++) * 2 + 3; - else - dp = dp + (x++) * 2 - (y--) * 2 + 5; - - setPixel(x0 + x, y0 + y); //For the 8 octants - setPixel(x0 - x, y0 + y); - setPixel(x0 + x, y0 - y); - setPixel(x0 - x, y0 - y); - setPixel(x0 + y, y0 + x); - setPixel(x0 - y, y0 + x); - setPixel(x0 + y, y0 - x); - setPixel(x0 - y, y0 - x); - - } while (x < y); - - setPixel(x0 + radius, y0); - setPixel(x0, y0 + radius); - setPixel(x0 - radius, y0); - setPixel(x0, y0 - radius); -} - -void OLEDDisplay::drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads) { +uint8_t OLEDDisplay::drawCircle(int16_t x0, int16_t y0, uint16_t radius, uint8_t quads, bool improve) { + quads &= B1111; + if (improve) { + if ((((x0 + radius) < 0) || ((y0 + radius) < 0)) || ((x0 > 127) || (y0 > 63))) { + quads &= B0111; + } + if ((((x0 - radius) > 127) || ((y0 + radius) < 0)) || ((x0 < 0) || (y0 > 63))) { + quads &= B1011; + } + if ((((x0 + radius) < 0) || ((y0 - radius) > 63)) || ((x0 > 127) || (y0 < 0))) { + quads &= B1110; + } + if ((((x0 - radius) > 127) || ((y0 - radius) > 63)) || ((x0 < 0) || (y0 < 0))) { + quads &= B1101; + } + if (!quads) { + return false; + } + } int16_t x = 0, y = radius; int16_t dp = 1 - radius; - while (x < y) { - if (dp < 0) - dp = dp + (x++) * 2 + 3; - else - dp = dp + (x++) * 2 - (y--) * 2 + 5; - if (quads & 0x1) { + if (quads == B1111) { + while (x < y) { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; setPixel(x0 + x, y0 - y); setPixel(x0 + y, y0 - x); - } - if (quads & 0x2) { setPixel(x0 - y, y0 - x); setPixel(x0 - x, y0 - y); - } - if (quads & 0x4) { setPixel(x0 - y, y0 + x); setPixel(x0 - x, y0 + y); - } - if (quads & 0x8) { setPixel(x0 + x, y0 + y); setPixel(x0 + y, y0 + x); } - } - if (quads & 0x1 && quads & 0x8) { setPixel(x0 + radius, y0); - } - if (quads & 0x4 && quads & 0x8) { setPixel(x0, y0 + radius); - } - if (quads & 0x2 && quads & 0x4) { setPixel(x0 - radius, y0); - } - if (quads & 0x1 && quads & 0x2) { setPixel(x0, y0 - radius); } + else { + while (x < y) { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + if (quads & 0x1) { + setPixel(x0 + x, y0 - y); + setPixel(x0 + y, y0 - x); + } + if (quads & 0x2) { + setPixel(x0 - y, y0 - x); + setPixel(x0 - x, y0 - y); + } + if (quads & 0x4) { + setPixel(x0 - y, y0 + x); + setPixel(x0 - x, y0 + y); + } + if (quads & 0x8) { + setPixel(x0 + x, y0 + y); + setPixel(x0 + y, y0 + x); + } + } + + if (quads & B1001) { + setPixel(x0 + radius, y0); + } + if (quads & B1100) { + setPixel(x0, y0 + radius); + } + if (quads & B0110) { + setPixel(x0 - radius, y0); + } + if (quads & B0011) { + setPixel(x0, y0 - radius); + } + } + return quads; } +void OLEDDisplay::drawCircleQuads(int16_t x0, int16_t y0, uint16_t radius, uint8_t quads) { //Only for backward compatibility + drawCircle(x0, y0, radius, quads); +} -void OLEDDisplay::fillCircle(int16_t x0, int16_t y0, int16_t radius) { +uint8_t OLEDDisplay::fillCircle(int16_t x0, int16_t y0, uint16_t radius, bool improve) { + if (improve) { + return fillRing(x0, y0, radius, 0, B1111, true); + } int16_t x = 0, y = radius; - int16_t dp = 1 - radius; - do { - if (dp < 0) + int16_t dp = 1 - radius; + do { + if (dp < 0) dp = dp + (x++) * 2 + 3; else dp = dp + (x++) * 2 - (y--) * 2 + 5; - drawHorizontalLine(x0 - x, y0 - y, 2*x + 1); - drawHorizontalLine(x0 - x, y0 + y, 2*x + 1); - drawHorizontalLine(x0 - y, y0 - x, 2*y + 1); - drawHorizontalLine(x0 - y, y0 + x, 2*y + 1); + drawHorizontalLine(x0 - x, y0 - y, 2 * x + 1); + drawHorizontalLine(x0 - x, y0 + y, 2 * x + 1); + drawHorizontalLine(x0 - y, y0 - x, 2 * y + 1); + drawHorizontalLine(x0 - y, y0 + x, 2 * y + 1); - } while (x < y); - drawHorizontalLine(x0 - radius, y0, (2 * radius) + 1); + } while (x < y); + drawHorizontalLine(x0 - radius, y0, (2 * radius) + 1); drawVerticalLine(x0, y0 - radius, 2 * radius); + return B1111; +} + +uint8_t OLEDDisplay::fillRing(int16_t x0, int16_t y0, uint16_t radius, uint16_t radius2, byte quads, bool improve) { + quads &= B1111; + if (radius < radius2) { + _swap_int16_t(radius, radius2); + } + if (improve) { + if ((((x0 + radius) < 0) || ((y0 + radius) < 0)) || ((x0 > 127) || (y0 > 63))) { + quads &= B0111; + } + if ((((x0 - radius) > 127) || ((y0 + radius) < 0)) || ((x0 < 0) || (y0 > 63))) { + quads &= B1011; + } + if ((((x0 + radius) < 0) || ((y0 - radius) > 63)) || ((x0 > 127) || (y0 < 0))) { + quads &= B1110; + } + if ((((x0 - radius) > 127) || ((y0 - radius) > 63)) || ((x0 < 0) || (y0 < 0))) { + quads &= B1101; + } + } + if (quads == 0) { + return false; + } + if(radius2 == 0){ + setPixel(x0, y0); + radius2 = 1; + } + int16_t x = 0, y = radius; + int16_t dp = 1 - radius; + + int16_t x2 = 0, y2 = radius2; + int16_t dp2 = 1 - radius2; + if (quads == B1111) { + do { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + if (x2 < y2 + 1) { + if (dp2 < 0) + dp2 = dp2 + (x2++) * 2 + 3; + else + dp2 = dp2 + (x2++) * 2 - (y2--) * 2 + 5; + } + else { + x2++; + y2++; + } + + drawVerticalLine(x0 - x, y0 - y, y - y2 + 1); + drawHorizontalLine(x0 - y, y0 - x, y - y2 + 1); + drawVerticalLine(x0 + x, y0 - y, y - y2 + 1); + drawHorizontalLine(x0 + y2, y0 - x, y - y2 + 1); + drawVerticalLine(x0 - x, y0 + y2, y - y2 + 1); + drawHorizontalLine(x0 - y, y0 + x, y - y2 + 1); + drawVerticalLine(x0 + x, y0 + y2, y - y2 + 1); + drawHorizontalLine(x0 + y2, y0 + x, y - y2 + 1); + } while (x < y); + drawVerticalLine(x0, y0 - radius, radius - radius2 + 1); + drawVerticalLine(x0, y0 + radius2, radius - radius2 + 1); + drawHorizontalLine(x0 - radius, y0, radius - radius2 + 1); + drawHorizontalLine(x0 + radius2, y0, radius - radius2 + 1); + } + else { + do { + if (dp < 0) + dp = dp + (x++) * 2 + 3; + else + dp = dp + (x++) * 2 - (y--) * 2 + 5; + + if (x2 < y2 + 1) { + if (dp2 < 0) + dp2 = dp2 + (x2++) * 2 + 3; + else + dp2 = dp2 + (x2++) * 2 - (y2--) * 2 + 5; + } + else { + x2++; + y2++; + } + + if (quads & B0010) { + drawVerticalLine(x0 - x, y0 - y, y - y2 + 1); + drawHorizontalLine(x0 - y, y0 - x, y - y2 + 1); + } + if (quads & B0001) { + drawVerticalLine(x0 + x, y0 - y, y - y2 + 1); + drawHorizontalLine(x0 + y2, y0 - x, y - y2 + 1); + } + if (quads & B0100) { + drawVerticalLine(x0 - x, y0 + y2, y - y2 + 1); + drawHorizontalLine(x0 - y, y0 + x, y - y2 + 1); + } + if (quads & B1000) { + drawVerticalLine(x0 + x, y0 + y2, y - y2 + 1); + drawHorizontalLine(x0 + y2, y0 + x, y - y2 + 1); + } + } while (x < y); + if (quads & B0011) { + drawVerticalLine(x0, y0 - radius, radius - radius2 + 1); + } + if (quads & B1100) { + drawVerticalLine(x0, y0 + radius2, radius - radius2 + 1); + } + if (quads & B0110) { + drawHorizontalLine(x0 - radius, y0, radius - radius2 + 1); + } + if (quads & B1001) { + drawHorizontalLine(x0 + radius2, y0, radius - radius2 + 1); + } + } + + return quads; } void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { - if (y < 0 || y >= this->height()) { return; } + if (y < 0 || y >= this->height()) { + return; + } if (x < 0) { length += x; @@ -314,7 +463,9 @@ void OLEDDisplay::drawHorizontalLine(int16_t x, int16_t y, int16_t length) { length = (this->width() - x); } - if (length <= 0) { return; } + if (length <= 0) { + return; + } uint8_t * bufferPtr = buffer; bufferPtr += (y >> 3) * this->width(); @@ -436,8 +587,8 @@ void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t h int16_t widthInXbm = (width + 7) / 8; uint8_t data = 0; - for(int16_t y = 0; y < height; y++) { - for(int16_t x = 0; x < width; x++ ) { + for (int16_t y = 0; y < height; y++) { + for (int16_t x = 0; x < width; x++ ) { if (x & 7) { data >>= 1; // Move a bit } else { // Read new data every 8 bit @@ -454,9 +605,9 @@ void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t h void OLEDDisplay::drawIco16x16(int16_t xMove, int16_t yMove, const char *ico, bool inverse) { uint16_t data; - for(int16_t y = 0; y < 16; y++) { + for (int16_t y = 0; y < 16; y++) { data = pgm_read_byte(ico + (y << 1)) + (pgm_read_byte(ico + (y << 1) + 1) << 8); - for(int16_t x = 0; x < 16; x++ ) { + for (int16_t x = 0; x < 16; x++ ) { if ((data & 0x01) ^ inverse) { setPixelColor(xMove + x, yMove + y, WHITE); } else { @@ -490,8 +641,12 @@ void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, u } // Don't draw anything if it is not on the screen. - if (xMove + textWidth < 0 || xMove > this->width() ) {return;} - if (yMove + textHeight < 0 || yMove > this->width() ) {return;} + if (xMove + textWidth < 0 || xMove > this->width() ) { + return; + } + if (yMove + textHeight < 0 || yMove > this->width() ) { + return; + } for (uint16_t j = 0; j < textLength; j++) { int16_t xPos = xMove + cursorX; @@ -532,7 +687,7 @@ void OLEDDisplay::drawString(int16_t xMove, int16_t yMove, String strUser) { if (textAlignment == TEXT_ALIGN_CENTER_BOTH) { uint16_t lb = 0; // Find number of linebreaks in text - for (uint16_t i=0;text[i] != 0; i++) { + for (uint16_t i = 0; text[i] != 0; i++) { lb += (text[i] == 10); } // Calculate center @@ -540,7 +695,7 @@ void OLEDDisplay::drawString(int16_t xMove, int16_t yMove, String strUser) { } uint16_t line = 0; - char* textPart = strtok(text,"\n"); + char* textPart = strtok(text, "\n"); while (textPart != NULL) { uint16_t length = strlen(textPart); drawStringInternal(xMove, yMove - yOffset + (line++) * lineHeight, textPart, length, getStringWidth(textPart, length)); @@ -567,7 +722,7 @@ void OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxL strWidth += pgm_read_byte(fontData + JUMPTABLE_START + (text[i] - firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); // Always try to break on a space or dash - if (text[i] == ' ' || text[i]== '-') { + if (text[i] == ' ' || text[i] == '-') { preferredBreakpoint = i; widthAtBreakpoint = strWidth; } @@ -703,7 +858,7 @@ void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { uint16_t line = 0; uint16_t lastPos = 0; - for (uint16_t i=0;ilogBufferFilled;i++){ + for (uint16_t i = 0; i < this->logBufferFilled; i++) { // Everytime we have a \n print if (this->logBuffer[i] == 10) { length++; @@ -733,7 +888,7 @@ uint16_t OLEDDisplay::getHeight(void) { return displayHeight; } -bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){ +bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars) { if (logBuffer != NULL) free(logBuffer); uint16_t size = lines * chars; if (size > 0) { @@ -742,7 +897,7 @@ bool OLEDDisplay::setLogBuffer(uint16_t lines, uint16_t chars){ this->logBufferMaxLines = lines; // Lines max printable this->logBufferSize = size; // Total number of characters the buffer can hold this->logBuffer = (char *) malloc(size * sizeof(uint8_t)); - if(!this->logBuffer) { + if (!this->logBuffer) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][setLogBuffer] Not enough memory to create log buffer\n"); return false; } @@ -775,8 +930,8 @@ size_t OLEDDisplay::write(uint8_t c) { // Find the end of the first line uint16_t firstLineEnd = 0; - for (uint16_t i=0;ilogBufferFilled;i++) { - if (this->logBuffer[i] == 10){ + for (uint16_t i = 0; i < this->logBufferFilled; i++) { + if (this->logBuffer[i] == 10) { // Include last char too firstLineEnd = i + 1; break; @@ -815,21 +970,21 @@ size_t OLEDDisplay::write(const char* str) { #ifdef __MBED__ int OLEDDisplay::_putc(int c) { - if (!fontData) - return 1; - if (!logBufferSize) { - uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); - uint16_t lines = this->displayHeight / textHeight; - uint16_t chars = 2 * (this->displayWidth / textHeight); - - if (this->displayHeight % textHeight) - lines++; - if (this->displayWidth % textHeight) - chars++; - setLogBuffer(lines, chars); - } - - return this->write((uint8_t)c); + if (!fontData) + return 1; + if (!logBufferSize) { + uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); + uint16_t lines = this->displayHeight / textHeight; + uint16_t chars = 2 * (this->displayWidth / textHeight); + + if (this->displayHeight % textHeight) + lines++; + if (this->displayWidth % textHeight) + chars++; + setLogBuffer(lines, chars); + } + + return this->write((uint8_t)c); } #endif @@ -837,25 +992,25 @@ int OLEDDisplay::_putc(int c) { void OLEDDisplay::setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { this->geometry = g; switch (g) { - case GEOMETRY_128_64: - this->displayWidth = 128; - this->displayHeight = 64; - break; - case GEOMETRY_128_32: - this->displayWidth = 128; - this->displayHeight = 32; - break; - case GEOMETRY_RAWMODE: - this->displayWidth = width > 0 ? width : 128; - this->displayHeight = height > 0 ? height : 64; - break; - } - this->displayBufferSize = displayWidth * displayHeight /8; + case GEOMETRY_128_64: + this->displayWidth = 128; + this->displayHeight = 64; + break; + case GEOMETRY_128_32: + this->displayWidth = 128; + this->displayHeight = 32; + break; + case GEOMETRY_RAWMODE: + this->displayWidth = width > 0 ? width : 128; + this->displayHeight = height > 0 ? height : 64; + break; + } + this->displayBufferSize = displayWidth * displayHeight / 8; } void OLEDDisplay::sendInitCommands(void) { if (geometry == GEOMETRY_RAWMODE) - return; + return; sendCommand(DISPLAYOFF); sendCommand(SETDISPLAYCLOCKDIV); sendCommand(0xF0); // Increase speed of the display max ~96Hz @@ -923,7 +1078,7 @@ void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t widt int16_t xPos = xMove + (i / rasterHeight); int16_t yPos = ((yMove >> 3) + (i % rasterHeight)) * this->width(); -// int16_t yScreenPos = yMove + yOffset; + // int16_t yScreenPos = yMove + yOffset; int16_t dataPos = xPos + yPos; if (dataPos >= 0 && dataPos < displayBufferSize && @@ -973,7 +1128,7 @@ char* OLEDDisplay::utf8ascii(String str) { // Copy the string into a char array char* s = (char*) malloc(length * sizeof(char)); - if(!s) { + if (!s) { DEBUG_OLEDDISPLAY("[OLEDDISPLAY][utf8ascii] Can't allocate another char array. Drop support for UTF-8.\n"); return (char*) str.c_str(); } @@ -981,14 +1136,14 @@ char* OLEDDisplay::utf8ascii(String str) { length--; - for (uint16_t i=0; i < length; i++) { + for (uint16_t i = 0; i < length; i++) { char c = (this->fontTableLookupFunction)(s[i]); - if (c!=0) { - s[k++]=c; + if (c != 0) { + s[k++] = c; } } - s[k]=0; + s[k] = 0; // This will leak 's' be sure to free it in the calling function. return s; @@ -1000,23 +1155,23 @@ void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) { char DefaultFontTableLookup(const uint8_t ch) { - // UTF-8 to font table index converter - // Code form http://playground.arduino.cc/Main/Utf8ascii - static uint8_t LASTCHAR; + // UTF-8 to font table index converter + // Code form http://playground.arduino.cc/Main/Utf8ascii + static uint8_t LASTCHAR; - if (ch < 128) { // Standard ASCII-set 0..0x7F handling - LASTCHAR = 0; - return ch; - } + if (ch < 128) { // Standard ASCII-set 0..0x7F handling + LASTCHAR = 0; + return ch; + } - uint8_t last = LASTCHAR; // get last char - LASTCHAR = ch; + uint8_t last = LASTCHAR; // get last char + LASTCHAR = ch; - switch (last) { // conversion depnding on first UTF8-character - case 0xC2: return (uint8_t) ch; - case 0xC3: return (uint8_t) (ch | 0xC0); - case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol - } + switch (last) { // conversion depnding on first UTF8-character + case 0xC2: return (uint8_t) ch; + case 0xC3: return (uint8_t) (ch | 0xC0); + case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol + } - return (uint8_t) 0; // otherwise: return zero, if character has to be ignored + return (uint8_t) 0; // otherwise: return zero, if character has to be ignored } From 512cb9f748040935e2d6b603f5246355695de6f7 Mon Sep 17 00:00:00 2001 From: NoobTracker <63962365+NoobTracker@users.noreply.github.com> Date: Thu, 23 Apr 2020 15:44:29 +0200 Subject: [PATCH 5/6] Improved Circle() functions, added fillRing() Improved Circle() functions, added fillRing() --- src/OLEDDisplay.h | 749 +++++++++++++++++++++++----------------------- 1 file changed, 376 insertions(+), 373 deletions(-) diff --git a/src/OLEDDisplay.h b/src/OLEDDisplay.h index 205dffc..61693bf 100644 --- a/src/OLEDDisplay.h +++ b/src/OLEDDisplay.h @@ -1,373 +1,376 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn - * Copyright (c) 2018 by Fabrice Weinberg - * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de - * - * 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. - * - * ThingPulse invests considerable time and money to develop these open source libraries. - * Please support us by buying our products (and not the clones) from - * https://thingpulse.com - * - */ - -#ifndef OLEDDISPLAY_h -#define OLEDDISPLAY_h - -#ifdef ARDUINO -#include -#elif __MBED__ -#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) - -#include -#define delay(x) wait_ms(x) -#define yield() void() - -/* - * This is a little Arduino String emulation to keep the OLEDDisplay - * library code in common between Arduino and mbed-os - */ -class String { -public: - String(const char *s) { _str = s; }; - int length() { return strlen(_str); }; - const char *c_str() { return _str; }; - void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const { - memcpy(buf, _str + index, std::min(bufsize, strlen(_str))); - }; -private: - const char *_str; -}; - -#else -#error "Unkown operating system" -#endif - -#include "OLEDDisplayFonts.h" - -//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ ) -//#define DEBUG_OLEDDISPLAY(...) dprintf("%s", __VA_ARGS__ ) - -#ifndef DEBUG_OLEDDISPLAY -#define DEBUG_OLEDDISPLAY(...) -#endif - -// Use DOUBLE BUFFERING by default -#ifndef OLEDDISPLAY_REDUCE_MEMORY -#define OLEDDISPLAY_DOUBLE_BUFFER -#endif - -// 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 -}; - - -enum OLEDDISPLAY_GEOMETRY { - GEOMETRY_128_64 = 0, - GEOMETRY_128_32, - GEOMETRY_RAWMODE, -}; - -typedef char (*FontTableLookupFunction)(const uint8_t ch); -char DefaultFontTableLookup(const uint8_t ch); - - -#ifdef ARDUINO -class OLEDDisplay : public Print { -#elif __MBED__ -class OLEDDisplay : public Stream { -#else -#error "Unkown operating system" -#endif - - public: - OLEDDisplay(); - virtual ~OLEDDisplay(); - - uint16_t width(void) const { return displayWidth; }; - uint16_t height(void) const { return displayHeight; }; - - // Use this to resume after a deep sleep without resetting the display (what init() would do). - // Returns true if connection to the display was established and the buffer allocated, false otherwise. - bool allocateBuffer(); - - // Allocates the buffer and initializes the driver & display. Resets the display! - // Returns false if buffer allocation failed, true otherwise. - 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); - - // Returns the current color. - OLEDDISPLAY_COLOR getColor(); - - // Draw a pixel at given position - void setPixel(int16_t x, int16_t y); - - // Draw a pixel at given position and color - void setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color); - - // Clear a pixel at given position FIXME: INVERSE is untested with this function - void clearPixel(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); - - // Draw all Quadrants specified in the quads bit mask - void drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads); - - // 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 line 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. Progress is - // a unsigned byte value between 0 and 100 - 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 uint8_t *image); - - // Draw a XBM - void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm); - - // Draw icon 16x16 xbm format - void drawIco16x16(int16_t x, int16_t y, const char *ico, bool inverse = false); - - /* 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 uint8_t *fontData); - - // Set the function that will convert utf-8 to font table index - void setFontTableLookupFunction(FontTableLookupFunction function); - - /* 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 - // really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0 - // normal brightness & contrast: contrast = 100 - void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64); - - // Convenience method to access - void setBrightness(uint8_t); - - // Reset display rotation or mirroring - void resetOrientation(); - - // Turn the display upside down - void flipScreenVertically(); - - // Mirror the display (to be used in a mirror or as a projector) - void mirrorScreen(); - - // Write the buffer to the display memory - virtual void display(void) = 0; - - // Clear the local pixel buffer - void clear(void); - - // Log buffer implementation - - // This will define the lines and characters you can - // print to the screen. When you exeed the buffer size (lines * chars) - // the output may be truncated due to the size constraint. - bool setLogBuffer(uint16_t lines, uint16_t chars); - - // Draw the log buffer at position (x, y) - void drawLogBuffer(uint16_t x, uint16_t y); - - // Get screen geometry - uint16_t getWidth(void); - uint16_t getHeight(void); - - // Implement needed function to be compatible with Print class - size_t write(uint8_t c); - size_t write(const char* s); - - // Implement needed function to be compatible with Stream class -#ifdef __MBED__ - int _putc(int c); - int _getc() { return -1; }; -#endif - - - uint8_t *buffer; - - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - uint8_t *buffer_back; - #endif - - protected: - - OLEDDISPLAY_GEOMETRY geometry; - - uint16_t displayWidth; - uint16_t displayHeight; - uint16_t displayBufferSize; - - // Set the correct height, width and buffer for the geometry - void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width = 0, uint16_t height = 0); - - OLEDDISPLAY_TEXT_ALIGNMENT textAlignment; - OLEDDISPLAY_COLOR color; - - const uint8_t *fontData; - - // State values for logBuffer - uint16_t logBufferSize; - uint16_t logBufferFilled; - uint16_t logBufferLine; - uint16_t logBufferMaxLines; - char *logBuffer; - - - // the header size of the buffer used, e.g. for the SPI command header - virtual int getBufferOffset(void) = 0; - - // Send a command to the display (low level function) - virtual void sendCommand(uint8_t com) {(void)com;}; - - // Connect to the display - virtual bool connect() { return false; }; - - // Send all the init commands - void sendInitCommands(); - - // converts utf8 characters to extended ascii - char* utf8ascii(String s); - - void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *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); - - FontTableLookupFunction fontTableLookupFunction; -}; - -#endif +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de + * + * 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. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef OLEDDISPLAY_h +#define OLEDDISPLAY_h + +#ifdef ARDUINO +#include +#elif __MBED__ +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) + +#include +#define delay(x) wait_ms(x) +#define yield() void() + +/* + * This is a little Arduino String emulation to keep the OLEDDisplay + * library code in common between Arduino and mbed-os + */ +class String { +public: + String(const char *s) { _str = s; }; + int length() { return strlen(_str); }; + const char *c_str() { return _str; }; + void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const { + memcpy(buf, _str + index, std::min(bufsize, strlen(_str))); + }; +private: + const char *_str; +}; + +#else +#error "Unkown operating system" +#endif + +#include "OLEDDisplayFonts.h" + +//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ ) +//#define DEBUG_OLEDDISPLAY(...) dprintf("%s", __VA_ARGS__ ) + +#ifndef DEBUG_OLEDDISPLAY +#define DEBUG_OLEDDISPLAY(...) +#endif + +// Use DOUBLE BUFFERING by default +#ifndef OLEDDISPLAY_REDUCE_MEMORY +#define OLEDDISPLAY_DOUBLE_BUFFER +#endif + +// 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 +}; + + +enum OLEDDISPLAY_GEOMETRY { + GEOMETRY_128_64 = 0, + GEOMETRY_128_32, + GEOMETRY_RAWMODE, +}; + +typedef char (*FontTableLookupFunction)(const uint8_t ch); +char DefaultFontTableLookup(const uint8_t ch); + + +#ifdef ARDUINO +class OLEDDisplay : public Print { +#elif __MBED__ +class OLEDDisplay : public Stream { +#else +#error "Unkown operating system" +#endif + + public: + OLEDDisplay(); + virtual ~OLEDDisplay(); + + uint16_t width(void) const { return displayWidth; }; + uint16_t height(void) const { return displayHeight; }; + + // Use this to resume after a deep sleep without resetting the display (what init() would do). + // Returns true if connection to the display was established and the buffer allocated, false otherwise. + bool allocateBuffer(); + + // Allocates the buffer and initializes the driver & display. Resets the display! + // Returns false if buffer allocation failed, true otherwise. + 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); + + // Returns the current color. + OLEDDISPLAY_COLOR getColor(); + + // Draw a pixel at given position + void setPixel(int16_t x, int16_t y); + + // Draw a pixel at given position and color + void setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color); + + // Clear a pixel at given position FIXME: INVERSE is untested with this function + void clearPixel(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 + uint8_t drawCircle(int16_t x, int16_t y, uint16_t radius, uint8_t quads = B1111, bool improve = false); + + // Draw all Quadrants specified in the quads bit mask (backward compatibility) + void drawCircleQuads(int16_t x0, int16_t y0, uint16_t radius, uint8_t quads); + + // Fill circle + uint8_t fillCircle(int16_t x, int16_t y, uint16_t radius, bool improve = false); + + // Fill ring (added by NoobTracker) + uint8_t fillRing(int16_t x0, int16_t y0, uint16_t radius, uint16_t radius2, uint8_t quads = B1111, bool improve = false); + + // Draw a line horizontally + void drawHorizontalLine(int16_t x, int16_t y, int16_t length); + + // Draw a line 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. Progress is + // a unsigned byte value between 0 and 100 + 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 uint8_t *image); + + // Draw a XBM + void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm); + + // Draw icon 16x16 xbm format + void drawIco16x16(int16_t x, int16_t y, const char *ico, bool inverse = false); + + /* 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 uint8_t *fontData); + + // Set the function that will convert utf-8 to font table index + void setFontTableLookupFunction(FontTableLookupFunction function); + + /* 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 + // really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0 + // normal brightness & contrast: contrast = 100 + void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64); + + // Convenience method to access + void setBrightness(uint8_t); + + // Reset display rotation or mirroring + void resetOrientation(); + + // Turn the display upside down + void flipScreenVertically(); + + // Mirror the display (to be used in a mirror or as a projector) + void mirrorScreen(); + + // Write the buffer to the display memory + virtual void display(void) = 0; + + // Clear the local pixel buffer + void clear(void); + + // Log buffer implementation + + // This will define the lines and characters you can + // print to the screen. When you exeed the buffer size (lines * chars) + // the output may be truncated due to the size constraint. + bool setLogBuffer(uint16_t lines, uint16_t chars); + + // Draw the log buffer at position (x, y) + void drawLogBuffer(uint16_t x, uint16_t y); + + // Get screen geometry + uint16_t getWidth(void); + uint16_t getHeight(void); + + // Implement needed function to be compatible with Print class + size_t write(uint8_t c); + size_t write(const char* s); + + // Implement needed function to be compatible with Stream class +#ifdef __MBED__ + int _putc(int c); + int _getc() { return -1; }; +#endif + + + uint8_t *buffer; + + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t *buffer_back; + #endif + + protected: + + OLEDDISPLAY_GEOMETRY geometry; + + uint16_t displayWidth; + uint16_t displayHeight; + uint16_t displayBufferSize; + + // Set the correct height, width and buffer for the geometry + void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width = 0, uint16_t height = 0); + + OLEDDISPLAY_TEXT_ALIGNMENT textAlignment; + OLEDDISPLAY_COLOR color; + + const uint8_t *fontData; + + // State values for logBuffer + uint16_t logBufferSize; + uint16_t logBufferFilled; + uint16_t logBufferLine; + uint16_t logBufferMaxLines; + char *logBuffer; + + + // the header size of the buffer used, e.g. for the SPI command header + virtual int getBufferOffset(void) = 0; + + // Send a command to the display (low level function) + virtual void sendCommand(uint8_t com) {(void)com;}; + + // Connect to the display + virtual bool connect() { return false; }; + + // Send all the init commands + void sendInitCommands(); + + // converts utf8 characters to extended ascii + char* utf8ascii(String s); + + void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *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); + + FontTableLookupFunction fontTableLookupFunction; +}; + +#endif From c3bfd6f0992217546f5e8200e99ccb12b462d126 Mon Sep 17 00:00:00 2001 From: NoobTracker <63962365+NoobTracker@users.noreply.github.com> Date: Thu, 23 Apr 2020 15:46:45 +0200 Subject: [PATCH 6/6] Added fillRing() demo --- examples/SSD1306RingDemo/SSD1306RingDemo.ino | 85 ++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 examples/SSD1306RingDemo/SSD1306RingDemo.ino diff --git a/examples/SSD1306RingDemo/SSD1306RingDemo.ino b/examples/SSD1306RingDemo/SSD1306RingDemo.ino new file mode 100644 index 0000000..e57fa29 --- /dev/null +++ b/examples/SSD1306RingDemo/SSD1306RingDemo.ino @@ -0,0 +1,85 @@ + /** + * The MIT License (MIT) + * + * Copyright (c) 2020 by NoobTracker + * + * 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. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + + // Include the correct display library + // For a connection via I2C using Wire include + #include // Only needed for Arduino 1.6.5 and earlier + #include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"` + // or #include "SH1106Wire.h", legacy include: `#include "SH1106.h"` + // For a connection via I2C using brzo_i2c (must be installed) include + // #include // Only needed for Arduino 1.6.5 and earlier + // #include "SSD1306Brzo.h" + // #include "SH1106Brzo.h" + // For a connection via SPI include + // #include // Only needed for Arduino 1.6.5 and earlier + // #include "SSD1306Spi.h" + // #include "SH1106SPi.h" + + // Use the corresponding display class: + + // Initialize the OLED display using SPI + // D5 -> CLK + // D7 -> MOSI (DOUT) + // D0 -> RES + // D2 -> DC + // D8 -> CS + // SSD1306Spi display(D0, D2, D8); + // or + // SH1106Spi display(D0, D2); + + // Initialize the OLED display using brzo_i2c + // D3 -> SDA + // D5 -> SCL + // SSD1306Brzo display(0x3c, SDA, SCL); + // or + // SH1106Brzo display(0x3c, SDA, SCL); + + // Initialize the OLED display using Wire library + SSD1306Wire display(0x3c, SDA, SCL); + // SH1106 display(0x3c, SDA, SCL); + +void ring(byte quads, byte pos){ + float angle = (pos * PI * 2) / 256; + float angle2 = ((pos + 0x20) * PI * 2) / 256; + display.fillRing(display.getWidth() / 2, display.getHeight() / 2, abs(sin(angle) * 30), abs(sin(angle2) * 25), quads); +} + +void setup() { + display.init(); + display.flipScreenVertically(); + display.setColor(WHITE); +} +uint16_t pos = 0x100; +void loop() { + display.clear(); + ring(pos >> 8, pos & 0xFF); + pos++; + display.display(); + delay(30); +}