kopia lustrzana https://github.com/lora-aprs/LoRa_APRS_iGate
1046 wiersze
29 KiB
C++
1046 wiersze
29 KiB
C++
|
/**
|
||
|
* 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) + BufferOffset);
|
||
|
this->buffer += BufferOffset;
|
||
|
|
||
|
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) + BufferOffset);
|
||
|
this->buffer_back += BufferOffset;
|
||
|
|
||
|
if(!this->buffer_back) {
|
||
|
DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n");
|
||
|
free(this->buffer - BufferOffset);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool OLEDDisplay::init() {
|
||
|
|
||
|
BufferOffset = getBufferOffset();
|
||
|
|
||
|
if(!allocateBuffer()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
sendInitCommands();
|
||
|
resetDisplay();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void OLEDDisplay::end() {
|
||
|
if (this->buffer) { free(this->buffer - BufferOffset); this->buffer = NULL; }
|
||
|
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
|
||
|
if (this->buffer_back) { free(this->buffer_back - BufferOffset); 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::drawStringf( int16_t x, int16_t y, char* buffer, String format, ... )
|
||
|
{
|
||
|
va_list myargs;
|
||
|
va_start(myargs, format);
|
||
|
vsprintf(buffer, format.c_str(), myargs);
|
||
|
va_end(myargs);
|
||
|
drawString( x, y, buffer );
|
||
|
}
|
||
|
|
||
|
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;i<this->logBufferFilled;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;i<this->logBufferFilled;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_64_48:
|
||
|
this->displayWidth = 64;
|
||
|
this->displayHeight = 48;
|
||
|
break;
|
||
|
case GEOMETRY_64_32:
|
||
|
this->displayWidth = 64;
|
||
|
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);
|
||
|
if(geometry == GEOMETRY_64_32)
|
||
|
sendCommand(0x00);
|
||
|
else
|
||
|
sendCommand(SETSTARTLINE);
|
||
|
sendCommand(CHARGEPUMP);
|
||
|
sendCommand(0x14);
|
||
|
sendCommand(MEMORYMODE);
|
||
|
sendCommand(0x00);
|
||
|
sendCommand(SEGREMAP);
|
||
|
sendCommand(COMSCANINC);
|
||
|
sendCommand(SETCOMPINS);
|
||
|
|
||
|
if (geometry == GEOMETRY_128_64 || geometry == GEOMETRY_64_48 || geometry == GEOMETRY_64_32) {
|
||
|
sendCommand(0x12);
|
||
|
} else if (geometry == GEOMETRY_128_32) {
|
||
|
sendCommand(0x02);
|
||
|
}
|
||
|
|
||
|
sendCommand(SETCONTRAST);
|
||
|
|
||
|
if (geometry == GEOMETRY_128_64 || geometry == GEOMETRY_64_48 || geometry == GEOMETRY_64_32) {
|
||
|
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 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
|
||
|
}
|