initial commit
This commit is contained in:
964
OLEDDisplay.cpp
Normal file
964
OLEDDisplay.cpp
Normal file
@@ -0,0 +1,964 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
|
||||
* Copyright (c) 2018 by Fabrice Weinberg
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* 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 "OLEDDisplay.h"
|
||||
|
||||
OLEDDisplay::~OLEDDisplay() {
|
||||
end();
|
||||
}
|
||||
|
||||
bool OLEDDisplay::init() {
|
||||
if (!this->connect()) {
|
||||
//DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Can't establish connection to display\n");
|
||||
Serial.print("Can't establish connection to display\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(this->buffer==NULL) {
|
||||
this->buffer = (uint8_t*) malloc(sizeof(uint8_t) * displayBufferSize);
|
||||
|
||||
if(!this->buffer) {
|
||||
//DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create display\n");
|
||||
Serial.print("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);
|
||||
|
||||
if(!this->buffer_back) {
|
||||
//DEBUG_OLEDDISPLAY("[OLEDDISPLAY][init] Not enough memory to create back buffer\n");
|
||||
Serial.print("Not enough memory to create back buffer\n");
|
||||
free(this->buffer);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
resetDisplay(16);
|
||||
sendInitCommands();
|
||||
|
||||
clear();
|
||||
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
|
||||
memset(buffer_back, 1, displayBufferSize);
|
||||
#endif
|
||||
display();
|
||||
return true;
|
||||
}
|
||||
|
||||
void OLEDDisplay::end() {
|
||||
if (this->buffer) { free(this->buffer); this->buffer = NULL; }
|
||||
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
|
||||
if (this->buffer_back) { free(this->buffer_back); this->buffer_back = NULL; }
|
||||
#endif
|
||||
if (this->logBuffer != NULL) { free(this->logBuffer); this->logBuffer = NULL; }
|
||||
}
|
||||
|
||||
void OLEDDisplay::sleep() {
|
||||
sendCommand(0x8D);
|
||||
sendCommand(0x10);
|
||||
sendCommand(0xAE);
|
||||
}
|
||||
|
||||
void OLEDDisplay::wakeup() {
|
||||
sendCommand(0x8D);
|
||||
sendCommand(0x14);
|
||||
sendCommand(0xAF);
|
||||
}
|
||||
|
||||
void OLEDDisplay::resetDisplay(uint8_t rstPin) {
|
||||
pinMode(rstPin, OUTPUT);
|
||||
digitalWrite(rstPin,LOW);
|
||||
delay(100);
|
||||
digitalWrite(rstPin,HIGH);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 + 2 * (++x) + 3;
|
||||
else
|
||||
dp = dp + 2 * (++x) - 2 * (--y) + 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 + 2 * (++x) + 3;
|
||||
else
|
||||
dp = dp + 2 * (++x) - 2 * (--y) + 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 + 2 * (++x) + 3;
|
||||
else
|
||||
dp = dp + 2 * (++x) - 2 * (--y) + 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::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;
|
||||
|
||||
uint8_t cursorX = 0;
|
||||
uint8_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;
|
||||
|
||||
byte code = text[j];
|
||||
if (code >= firstChar) {
|
||||
byte charCode = code - firstChar;
|
||||
|
||||
// 4 Bytes per char code
|
||||
byte msbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES ); // MSB \ JumpAddress
|
||||
byte lsbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_LSB); // LSB /
|
||||
byte charByteSize = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_SIZE); // Size
|
||||
byte 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::drawdata(int16_t xMove, int16_t yMove, int16_t Num) {
|
||||
// uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS);
|
||||
// unsigned char c = 0,i = 0,j = 0,ch[3];
|
||||
// String strUser;
|
||||
//
|
||||
// ch[0] = Num/100 + 48;//锟斤拷锟斤拷十锟斤拷锟狡碉拷48锟斤拷为锟剿革拷Num锟斤拷锟斤拷ASCLL锟斤拷母锟<E6AF8D>4位0011 0000锟斤拷
|
||||
// ch[1] = Num%100/10 + 48;
|
||||
// ch[2] = Num%10 + 48;
|
||||
//
|
||||
// if(ch[0] == 48) //锟斤拷锟节帮拷锟斤拷锟斤拷每位为"0"时锟斤拷锟斤拷煽崭瘢锟斤拷锟斤拷锟绞撅拷锟<E68BB7>
|
||||
// {
|
||||
// ch[0] = 32;
|
||||
// if(ch[1] == 48)
|
||||
// {
|
||||
// ch[1] = 32;
|
||||
// if(ch[2] == 48)
|
||||
// {
|
||||
// ch[2] = 32;
|
||||
// }
|
||||
// else{ch[2] = Num%10 + 48;}
|
||||
// }
|
||||
// else{ch[1] = Num%100/10 + 48;}
|
||||
// }
|
||||
// else {ch[0] = Num/100 + 48;}
|
||||
//
|
||||
// // 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;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;
|
||||
}
|
||||
|
||||
// Private functions
|
||||
void OLEDDisplay::setGeometry(OLEDDISPLAY_GEOMETRY g) {
|
||||
this->geometry = g;
|
||||
if (g == GEOMETRY_128_64) {
|
||||
this->displayWidth = 128;
|
||||
this->displayHeight = 64;
|
||||
} else if (g == GEOMETRY_128_32) {
|
||||
this->displayWidth = 128;
|
||||
this->displayHeight = 32;
|
||||
} else if (g == GEOMETRY_64_32) {
|
||||
this->displayWidth = 64;
|
||||
this->displayHeight = 32;
|
||||
}
|
||||
this->displayBufferSize = displayWidth*displayHeight/8;
|
||||
}
|
||||
|
||||
void OLEDDisplay::sendInitCommands(void) {
|
||||
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) || (geometry == GEOMETRY_64_32)) {
|
||||
sendCommand(0x12);
|
||||
} else if (geometry == GEOMETRY_128_32) {
|
||||
sendCommand(0x02);
|
||||
}
|
||||
|
||||
sendCommand(SETCONTRAST);
|
||||
|
||||
if ((geometry == GEOMETRY_128_64) || (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 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;
|
||||
}
|
||||
|
||||
byte 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;
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
335
OLEDDisplay.h
Normal file
335
OLEDDisplay.h
Normal file
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
|
||||
* Copyright (c) 2018 by Fabrice Weinberg
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* 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
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "OLEDDisplayFonts.h"
|
||||
|
||||
//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ )
|
||||
|
||||
#ifndef DEBUG_OLEDDISPLAY
|
||||
#define DEBUG_OLEDDISPLAY(...)
|
||||
#endif
|
||||
|
||||
// Use DOUBLE BUFFERING by default
|
||||
#ifndef OLEDDISPLAY_REDUCE_MEMORY
|
||||
#define OLEDDISPLAY_DOUBLE_BUFFER
|
||||
#endif
|
||||
|
||||
// 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 = 1,
|
||||
GEOMETRY_64_32 = 2 //Wireless Stick
|
||||
};
|
||||
|
||||
typedef byte (*FontTableLookupFunction)(const byte ch);
|
||||
|
||||
|
||||
class OLEDDisplay : public Print {
|
||||
|
||||
public:
|
||||
virtual ~OLEDDisplay();
|
||||
|
||||
uint16_t width(void) const { return displayWidth; };
|
||||
uint16_t height(void) const { return displayHeight; };
|
||||
|
||||
// Initialize the display
|
||||
bool init();
|
||||
|
||||
// Free the memory used by the display
|
||||
void end();
|
||||
|
||||
void sleep();
|
||||
|
||||
void wakeup();
|
||||
|
||||
// Cycle through the initialization
|
||||
void resetDisplay(uint8_t rstPin);
|
||||
|
||||
/* 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 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);
|
||||
|
||||
/* 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);
|
||||
|
||||
uint8_t *buffer = NULL;
|
||||
|
||||
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
|
||||
uint8_t *buffer_back = NULL;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
OLEDDISPLAY_GEOMETRY geometry = GEOMETRY_128_64;
|
||||
|
||||
uint16_t displayWidth = 128;
|
||||
uint16_t displayHeight = 64;
|
||||
uint16_t displayBufferSize = 1024;
|
||||
|
||||
// Set the correct height, width and buffer for the geometry
|
||||
void setGeometry(OLEDDISPLAY_GEOMETRY g);
|
||||
|
||||
OLEDDISPLAY_TEXT_ALIGNMENT textAlignment = TEXT_ALIGN_LEFT;
|
||||
OLEDDISPLAY_COLOR color = WHITE;
|
||||
|
||||
const uint8_t *fontData = ArialMT_Plain_10;
|
||||
|
||||
// State values for logBuffer
|
||||
uint16_t logBufferSize = 0;
|
||||
uint16_t logBufferFilled = 0;
|
||||
uint16_t logBufferLine = 0;
|
||||
uint16_t logBufferMaxLines = 0;
|
||||
char *logBuffer = NULL;
|
||||
|
||||
// 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);
|
||||
|
||||
// UTF-8 to font table index converter
|
||||
// Code form http://playground.arduino.cc/Main/Utf8ascii
|
||||
FontTableLookupFunction fontTableLookupFunction = [](const byte ch) {
|
||||
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
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
1274
OLEDDisplayFonts.h
Normal file
1274
OLEDDisplayFonts.h
Normal file
File diff suppressed because it is too large
Load Diff
422
OLEDDisplayUi.cpp
Normal file
422
OLEDDisplayUi.cpp
Normal file
@@ -0,0 +1,422 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
|
||||
* Copyright (c) 2018 by Fabrice Weinberg
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* 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 "OLEDDisplayUi.h"
|
||||
|
||||
OLEDDisplayUi::OLEDDisplayUi(OLEDDisplay *display) {
|
||||
this->display = display;
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::init() {
|
||||
this->display->init();
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::setTargetFPS(uint8_t fps){
|
||||
float oldInterval = this->updateInterval;
|
||||
this->updateInterval = ((float) 1.0 / (float) fps) * 1000;
|
||||
|
||||
// Calculate new ticksPerFrame
|
||||
float changeRatio = oldInterval / (float) this->updateInterval;
|
||||
this->ticksPerFrame *= changeRatio;
|
||||
this->ticksPerTransition *= changeRatio;
|
||||
}
|
||||
|
||||
// -/------ Automatic controll ------\-
|
||||
|
||||
void OLEDDisplayUi::enableAutoTransition(){
|
||||
this->autoTransition = true;
|
||||
}
|
||||
void OLEDDisplayUi::disableAutoTransition(){
|
||||
this->autoTransition = false;
|
||||
}
|
||||
void OLEDDisplayUi::setAutoTransitionForwards(){
|
||||
this->state.frameTransitionDirection = 1;
|
||||
this->lastTransitionDirection = 1;
|
||||
}
|
||||
void OLEDDisplayUi::setAutoTransitionBackwards(){
|
||||
this->state.frameTransitionDirection = -1;
|
||||
this->lastTransitionDirection = -1;
|
||||
}
|
||||
void OLEDDisplayUi::setTimePerFrame(uint16_t time){
|
||||
this->ticksPerFrame = (uint16_t) ( (float) time / (float) updateInterval);
|
||||
}
|
||||
void OLEDDisplayUi::setTimePerTransition(uint16_t time){
|
||||
this->ticksPerTransition = (uint16_t) ( (float) time / (float) updateInterval);
|
||||
}
|
||||
|
||||
// -/------ Customize indicator position and style -------\-
|
||||
void OLEDDisplayUi::enableIndicator(){
|
||||
this->state.isIndicatorDrawen = true;
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::disableIndicator(){
|
||||
this->state.isIndicatorDrawen = false;
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::enableAllIndicators(){
|
||||
this->shouldDrawIndicators = true;
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::disableAllIndicators(){
|
||||
this->shouldDrawIndicators = false;
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::setIndicatorPosition(IndicatorPosition pos) {
|
||||
this->indicatorPosition = pos;
|
||||
}
|
||||
void OLEDDisplayUi::setIndicatorDirection(IndicatorDirection dir) {
|
||||
this->indicatorDirection = dir;
|
||||
}
|
||||
void OLEDDisplayUi::setActiveSymbol(const uint8_t* symbol) {
|
||||
this->activeSymbol = symbol;
|
||||
}
|
||||
void OLEDDisplayUi::setInactiveSymbol(const uint8_t* symbol) {
|
||||
this->inactiveSymbol = symbol;
|
||||
}
|
||||
|
||||
|
||||
// -/----- Frame settings -----\-
|
||||
void OLEDDisplayUi::setFrameAnimation(AnimationDirection dir) {
|
||||
this->frameAnimationDirection = dir;
|
||||
}
|
||||
void OLEDDisplayUi::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) {
|
||||
this->frameFunctions = frameFunctions;
|
||||
this->frameCount = frameCount;
|
||||
this->resetState();
|
||||
}
|
||||
|
||||
// -/----- Overlays ------\-
|
||||
void OLEDDisplayUi::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){
|
||||
this->overlayFunctions = overlayFunctions;
|
||||
this->overlayCount = overlayCount;
|
||||
}
|
||||
|
||||
// -/----- Loading Process -----\-
|
||||
|
||||
void OLEDDisplayUi::setLoadingDrawFunction(LoadingDrawFunction loadingDrawFunction) {
|
||||
this->loadingDrawFunction = loadingDrawFunction;
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) {
|
||||
uint8_t progress = 0;
|
||||
uint8_t increment = 100 / stagesCount;
|
||||
|
||||
for (uint8_t i = 0; i < stagesCount; i++) {
|
||||
display->clear();
|
||||
this->loadingDrawFunction(this->display, &stages[i], progress);
|
||||
display->display();
|
||||
|
||||
stages[i].callback();
|
||||
|
||||
progress += increment;
|
||||
yield();
|
||||
}
|
||||
|
||||
display->clear();
|
||||
this->loadingDrawFunction(this->display, &stages[stagesCount-1], progress);
|
||||
display->display();
|
||||
|
||||
delay(150);
|
||||
}
|
||||
|
||||
// -/----- Manuel control -----\-
|
||||
void OLEDDisplayUi::nextFrame() {
|
||||
if (this->state.frameState != IN_TRANSITION) {
|
||||
this->state.manuelControll = true;
|
||||
this->state.frameState = IN_TRANSITION;
|
||||
this->state.ticksSinceLastStateSwitch = 0;
|
||||
this->lastTransitionDirection = this->state.frameTransitionDirection;
|
||||
this->state.frameTransitionDirection = 1;
|
||||
}
|
||||
}
|
||||
void OLEDDisplayUi::previousFrame() {
|
||||
if (this->state.frameState != IN_TRANSITION) {
|
||||
this->state.manuelControll = true;
|
||||
this->state.frameState = IN_TRANSITION;
|
||||
this->state.ticksSinceLastStateSwitch = 0;
|
||||
this->lastTransitionDirection = this->state.frameTransitionDirection;
|
||||
this->state.frameTransitionDirection = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::switchToFrame(uint8_t frame) {
|
||||
if (frame >= this->frameCount) return;
|
||||
this->state.ticksSinceLastStateSwitch = 0;
|
||||
if (frame == this->state.currentFrame) return;
|
||||
this->state.frameState = FIXED;
|
||||
this->state.currentFrame = frame;
|
||||
this->state.isIndicatorDrawen = true;
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::transitionToFrame(uint8_t frame) {
|
||||
if (frame >= this->frameCount) return;
|
||||
this->state.ticksSinceLastStateSwitch = 0;
|
||||
if (frame == this->state.currentFrame) return;
|
||||
this->nextFrameNumber = frame;
|
||||
this->lastTransitionDirection = this->state.frameTransitionDirection;
|
||||
this->state.manuelControll = true;
|
||||
this->state.frameState = IN_TRANSITION;
|
||||
this->state.frameTransitionDirection = frame < this->state.currentFrame ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
// -/----- State information -----\-
|
||||
OLEDDisplayUiState* OLEDDisplayUi::getUiState(){
|
||||
return &this->state;
|
||||
}
|
||||
|
||||
|
||||
int8_t OLEDDisplayUi::update(){
|
||||
unsigned long frameStart = millis();
|
||||
int8_t timeBudget = this->updateInterval - (frameStart - this->state.lastUpdate);
|
||||
if ( timeBudget <= 0) {
|
||||
// Implement frame skipping to ensure time budget is keept
|
||||
if (this->autoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil(-timeBudget / this->updateInterval);
|
||||
|
||||
this->state.lastUpdate = frameStart;
|
||||
this->tick();
|
||||
}
|
||||
return this->updateInterval - (millis() - frameStart);
|
||||
}
|
||||
|
||||
|
||||
void OLEDDisplayUi::tick() {
|
||||
this->state.ticksSinceLastStateSwitch++;
|
||||
|
||||
switch (this->state.frameState) {
|
||||
case IN_TRANSITION:
|
||||
if (this->state.ticksSinceLastStateSwitch >= this->ticksPerTransition){
|
||||
this->state.frameState = FIXED;
|
||||
this->state.currentFrame = getNextFrameNumber();
|
||||
this->state.ticksSinceLastStateSwitch = 0;
|
||||
this->nextFrameNumber = -1;
|
||||
}
|
||||
break;
|
||||
case FIXED:
|
||||
// Revert manuelControll
|
||||
if (this->state.manuelControll) {
|
||||
this->state.frameTransitionDirection = this->lastTransitionDirection;
|
||||
this->state.manuelControll = false;
|
||||
}
|
||||
if (this->state.ticksSinceLastStateSwitch >= this->ticksPerFrame){
|
||||
if (this->autoTransition){
|
||||
this->state.frameState = IN_TRANSITION;
|
||||
}
|
||||
this->state.ticksSinceLastStateSwitch = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this->display->clear();
|
||||
this->drawFrame();
|
||||
if (shouldDrawIndicators) {
|
||||
this->drawIndicator();
|
||||
}
|
||||
this->drawOverlays();
|
||||
this->display->display();
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::resetState() {
|
||||
this->state.lastUpdate = 0;
|
||||
this->state.ticksSinceLastStateSwitch = 0;
|
||||
this->state.frameState = FIXED;
|
||||
this->state.currentFrame = 0;
|
||||
this->state.isIndicatorDrawen = true;
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::drawFrame(){
|
||||
switch (this->state.frameState){
|
||||
case IN_TRANSITION: {
|
||||
float progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition;
|
||||
int16_t x = 0, y = 0, x1 = 0, y1 = 0;
|
||||
switch(this->frameAnimationDirection){
|
||||
case SLIDE_LEFT:
|
||||
x = -this->display->width() * progress;
|
||||
y = 0;
|
||||
x1 = x + this->display->width();
|
||||
y1 = 0;
|
||||
break;
|
||||
case SLIDE_RIGHT:
|
||||
x = this->display->width() * progress;
|
||||
y = 0;
|
||||
x1 = x - this->display->width();
|
||||
y1 = 0;
|
||||
break;
|
||||
case SLIDE_UP:
|
||||
x = 0;
|
||||
y = -this->display->height() * progress;
|
||||
x1 = 0;
|
||||
y1 = y + this->display->height();
|
||||
break;
|
||||
case SLIDE_DOWN:
|
||||
default:
|
||||
x = 0;
|
||||
y = this->display->height() * progress;
|
||||
x1 = 0;
|
||||
y1 = y - this->display->height();
|
||||
break;
|
||||
}
|
||||
|
||||
// Invert animation if direction is reversed.
|
||||
int8_t dir = this->state.frameTransitionDirection >= 0 ? 1 : -1;
|
||||
x *= dir; y *= dir; x1 *= dir; y1 *= dir;
|
||||
|
||||
bool drawenCurrentFrame;
|
||||
|
||||
|
||||
// Prope each frameFunction for the indicator Drawen state
|
||||
this->enableIndicator();
|
||||
(this->frameFunctions[this->state.currentFrame])(this->display, &this->state, x, y);
|
||||
drawenCurrentFrame = this->state.isIndicatorDrawen;
|
||||
|
||||
this->enableIndicator();
|
||||
(this->frameFunctions[this->getNextFrameNumber()])(this->display, &this->state, x1, y1);
|
||||
|
||||
// Build up the indicatorDrawState
|
||||
if (drawenCurrentFrame && !this->state.isIndicatorDrawen) {
|
||||
// Drawen now but not next
|
||||
this->indicatorDrawState = 2;
|
||||
} else if (!drawenCurrentFrame && this->state.isIndicatorDrawen) {
|
||||
// Not drawen now but next
|
||||
this->indicatorDrawState = 1;
|
||||
} else if (!drawenCurrentFrame && !this->state.isIndicatorDrawen) {
|
||||
// Not drawen in both frames
|
||||
this->indicatorDrawState = 3;
|
||||
}
|
||||
|
||||
// If the indicator isn't draw in the current frame
|
||||
// reflect it in state.isIndicatorDrawen
|
||||
if (!drawenCurrentFrame) this->state.isIndicatorDrawen = false;
|
||||
|
||||
break;
|
||||
}
|
||||
case FIXED:
|
||||
// Always assume that the indicator is drawn!
|
||||
// And set indicatorDrawState to "not known yet"
|
||||
this->indicatorDrawState = 0;
|
||||
this->enableIndicator();
|
||||
(this->frameFunctions[this->state.currentFrame])(this->display, &this->state, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::drawIndicator() {
|
||||
|
||||
// Only draw if the indicator is invisible
|
||||
// for both frames or
|
||||
// the indiactor is shown and we are IN_TRANSITION
|
||||
if (this->indicatorDrawState == 3 || (!this->state.isIndicatorDrawen && this->state.frameState != IN_TRANSITION)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t posOfHighlightFrame = 0;
|
||||
float indicatorFadeProgress = 0;
|
||||
|
||||
// if the indicator needs to be slided in we want to
|
||||
// highlight the next frame in the transition
|
||||
uint8_t frameToHighlight = this->indicatorDrawState == 1 ? this->getNextFrameNumber() : this->state.currentFrame;
|
||||
|
||||
// Calculate the frame that needs to be highlighted
|
||||
// based on the Direction the indiactor is drawn
|
||||
switch (this->indicatorDirection){
|
||||
case LEFT_RIGHT:
|
||||
posOfHighlightFrame = frameToHighlight;
|
||||
break;
|
||||
case RIGHT_LEFT:
|
||||
default:
|
||||
posOfHighlightFrame = this->frameCount - frameToHighlight;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (this->indicatorDrawState) {
|
||||
case 1: // Indicator was not drawn in this frame but will be in next
|
||||
// Slide IN
|
||||
indicatorFadeProgress = 1 - ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
|
||||
break;
|
||||
case 2: // Indicator was drawn in this frame but not in next
|
||||
// Slide OUT
|
||||
indicatorFadeProgress = ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
|
||||
break;
|
||||
}
|
||||
|
||||
//Space between indicators - reduce for small screen sizes
|
||||
uint16_t indicatorSpacing = 12;
|
||||
if (this->display->getHeight() < 64 && (this->indicatorPosition == RIGHT || this->indicatorPosition == LEFT)) {
|
||||
indicatorSpacing = 6;
|
||||
}
|
||||
|
||||
uint16_t frameStartPos = (indicatorSpacing * frameCount / 2);
|
||||
const uint8_t *image;
|
||||
|
||||
uint16_t x = 0,y = 0;
|
||||
|
||||
|
||||
for (byte i = 0; i < this->frameCount; i++) {
|
||||
|
||||
switch (this->indicatorPosition){
|
||||
case TOP:
|
||||
y = 0 - (8 * indicatorFadeProgress);
|
||||
x = (this->display->width() / 2) - frameStartPos + 12 * i;
|
||||
break;
|
||||
case BOTTOM:
|
||||
y = (this->display->height() - 8) + (8 * indicatorFadeProgress);
|
||||
x = (this->display->width() / 2) - frameStartPos + 12 * i;
|
||||
break;
|
||||
case RIGHT:
|
||||
x = (this->display->width() - 8) + (8 * indicatorFadeProgress);
|
||||
y = (this->display->height() / 2) - frameStartPos + 2 + 12 * i;
|
||||
break;
|
||||
case LEFT:
|
||||
default:
|
||||
x = 0 - (8 * indicatorFadeProgress);
|
||||
y = (this->display->height() / 2) - frameStartPos + 2 + indicatorSpacing * i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (posOfHighlightFrame == i) {
|
||||
image = this->activeSymbol;
|
||||
} else {
|
||||
image = this->inactiveSymbol;
|
||||
}
|
||||
|
||||
this->display->drawFastImage(x, y, 8, 8, image);
|
||||
}
|
||||
}
|
||||
|
||||
void OLEDDisplayUi::drawOverlays() {
|
||||
for (uint8_t i=0;i<this->overlayCount;i++){
|
||||
(this->overlayFunctions[i])(this->display, &this->state);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t OLEDDisplayUi::getNextFrameNumber(){
|
||||
if (this->nextFrameNumber != -1) return this->nextFrameNumber;
|
||||
return (this->state.currentFrame + this->frameCount + this->state.frameTransitionDirection) % this->frameCount;
|
||||
}
|
||||
309
OLEDDisplayUi.h
Normal file
309
OLEDDisplayUi.h
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
|
||||
* Copyright (c) 2018 by Fabrice Weinberg
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* 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 OLEDDISPLAYUI_h
|
||||
#define OLEDDISPLAYUI_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "OLEDDisplay.h"
|
||||
|
||||
//#define DEBUG_OLEDDISPLAYUI(...) Serial.printf( __VA_ARGS__ )
|
||||
|
||||
#ifndef DEBUG_OLEDDISPLAYUI
|
||||
#define DEBUG_OLEDDISPLAYUI(...)
|
||||
#endif
|
||||
|
||||
enum AnimationDirection {
|
||||
SLIDE_UP,
|
||||
SLIDE_DOWN,
|
||||
SLIDE_LEFT,
|
||||
SLIDE_RIGHT
|
||||
};
|
||||
|
||||
enum IndicatorPosition {
|
||||
TOP,
|
||||
RIGHT,
|
||||
BOTTOM,
|
||||
LEFT
|
||||
};
|
||||
|
||||
enum IndicatorDirection {
|
||||
LEFT_RIGHT,
|
||||
RIGHT_LEFT
|
||||
};
|
||||
|
||||
enum FrameState {
|
||||
IN_TRANSITION,
|
||||
FIXED
|
||||
};
|
||||
|
||||
|
||||
const uint8_t ANIMATION_activeSymbol[] PROGMEM = {
|
||||
0x00, 0x18, 0x3c, 0x7e, 0x7e, 0x3c, 0x18, 0x00
|
||||
};
|
||||
|
||||
const uint8_t ANIMATION_inactiveSymbol[] PROGMEM = {
|
||||
0x00, 0x0, 0x0, 0x18, 0x18, 0x0, 0x0, 0x00
|
||||
};
|
||||
|
||||
|
||||
// Structure of the UiState
|
||||
struct OLEDDisplayUiState {
|
||||
uint64_t lastUpdate = 0;
|
||||
uint16_t ticksSinceLastStateSwitch = 0;
|
||||
|
||||
FrameState frameState = FIXED;
|
||||
uint8_t currentFrame = 0;
|
||||
|
||||
bool isIndicatorDrawen = true;
|
||||
|
||||
// Normal = 1, Inverse = -1;
|
||||
int8_t frameTransitionDirection = 1;
|
||||
|
||||
bool manuelControll = false;
|
||||
|
||||
// Custom data that can be used by the user
|
||||
void* userData = NULL;
|
||||
};
|
||||
|
||||
struct LoadingStage {
|
||||
const char* process;
|
||||
void (*callback)();
|
||||
};
|
||||
|
||||
typedef void (*FrameCallback)(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
|
||||
typedef void (*OverlayCallback)(OLEDDisplay *display, OLEDDisplayUiState* state);
|
||||
typedef void (*LoadingDrawFunction)(OLEDDisplay *display, LoadingStage* stage, uint8_t progress);
|
||||
|
||||
class OLEDDisplayUi {
|
||||
private:
|
||||
OLEDDisplay *display;
|
||||
|
||||
// Symbols for the Indicator
|
||||
IndicatorPosition indicatorPosition = BOTTOM;
|
||||
IndicatorDirection indicatorDirection = LEFT_RIGHT;
|
||||
|
||||
const uint8_t* activeSymbol = ANIMATION_activeSymbol;
|
||||
const uint8_t* inactiveSymbol = ANIMATION_inactiveSymbol;
|
||||
|
||||
bool shouldDrawIndicators = true;
|
||||
|
||||
// Values for the Frames
|
||||
AnimationDirection frameAnimationDirection = SLIDE_RIGHT;
|
||||
|
||||
int8_t lastTransitionDirection = 1;
|
||||
|
||||
uint16_t ticksPerFrame = 151; // ~ 5000ms at 30 FPS
|
||||
uint16_t ticksPerTransition = 15; // ~ 500ms at 30 FPS
|
||||
|
||||
bool autoTransition = true;
|
||||
|
||||
FrameCallback* frameFunctions;
|
||||
uint8_t frameCount = 0;
|
||||
|
||||
// Internally used to transition to a specific frame
|
||||
int8_t nextFrameNumber = -1;
|
||||
|
||||
// Values for Overlays
|
||||
OverlayCallback* overlayFunctions;
|
||||
uint8_t overlayCount = 0;
|
||||
|
||||
// Will the Indicator be drawen
|
||||
// 3 Not drawn in both frames
|
||||
// 2 Drawn this frame but not next
|
||||
// 1 Not drown this frame but next
|
||||
// 0 Not known yet
|
||||
uint8_t indicatorDrawState = 1;
|
||||
|
||||
// Loading screen
|
||||
LoadingDrawFunction loadingDrawFunction = [](OLEDDisplay *display, LoadingStage* stage, uint8_t progress) {
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(ArialMT_Plain_10);
|
||||
display->drawString(64, 18, stage->process);
|
||||
display->drawProgressBar(4, 32, 120, 8, progress);
|
||||
};
|
||||
|
||||
// UI State
|
||||
OLEDDisplayUiState state;
|
||||
|
||||
// Bookeeping for update
|
||||
uint8_t updateInterval = 33;
|
||||
|
||||
uint8_t getNextFrameNumber();
|
||||
void drawIndicator();
|
||||
void drawFrame();
|
||||
void drawOverlays();
|
||||
void tick();
|
||||
void resetState();
|
||||
|
||||
public:
|
||||
|
||||
OLEDDisplayUi(OLEDDisplay *display);
|
||||
|
||||
/**
|
||||
* Initialise the display
|
||||
*/
|
||||
void init();
|
||||
|
||||
/**
|
||||
* Configure the internal used target FPS
|
||||
*/
|
||||
void setTargetFPS(uint8_t fps);
|
||||
|
||||
// Automatic Controll
|
||||
/**
|
||||
* Enable automatic transition to next frame after the some time can be configured with `setTimePerFrame` and `setTimePerTransition`.
|
||||
*/
|
||||
void enableAutoTransition();
|
||||
|
||||
/**
|
||||
* Disable automatic transition to next frame.
|
||||
*/
|
||||
void disableAutoTransition();
|
||||
|
||||
/**
|
||||
* Set the direction if the automatic transitioning
|
||||
*/
|
||||
void setAutoTransitionForwards();
|
||||
void setAutoTransitionBackwards();
|
||||
|
||||
/**
|
||||
* Set the approx. time a frame is displayed
|
||||
*/
|
||||
void setTimePerFrame(uint16_t time);
|
||||
|
||||
/**
|
||||
* Set the approx. time a transition will take
|
||||
*/
|
||||
void setTimePerTransition(uint16_t time);
|
||||
|
||||
// Customize indicator position and style
|
||||
|
||||
/**
|
||||
* Draw the indicator.
|
||||
* This is the defaut state for all frames if
|
||||
* the indicator was hidden on the previous frame
|
||||
* it will be slided in.
|
||||
*/
|
||||
void enableIndicator();
|
||||
|
||||
/**
|
||||
* Don't draw the indicator.
|
||||
* This will slide out the indicator
|
||||
* when transitioning to the next frame.
|
||||
*/
|
||||
void disableIndicator();
|
||||
|
||||
/**
|
||||
* Enable drawing of indicators
|
||||
*/
|
||||
void enableAllIndicators();
|
||||
|
||||
/**
|
||||
* Disable draw of indicators.
|
||||
*/
|
||||
void disableAllIndicators();
|
||||
|
||||
/**
|
||||
* Set the position of the indicator bar.
|
||||
*/
|
||||
void setIndicatorPosition(IndicatorPosition pos);
|
||||
|
||||
/**
|
||||
* Set the direction of the indicator bar. Defining the order of frames ASCENDING / DESCENDING
|
||||
*/
|
||||
void setIndicatorDirection(IndicatorDirection dir);
|
||||
|
||||
/**
|
||||
* Set the symbol to indicate an active frame in the indicator bar.
|
||||
*/
|
||||
void setActiveSymbol(const uint8_t* symbol);
|
||||
|
||||
/**
|
||||
* Set the symbol to indicate an inactive frame in the indicator bar.
|
||||
*/
|
||||
void setInactiveSymbol(const uint8_t* symbol);
|
||||
|
||||
|
||||
// Frame settings
|
||||
|
||||
/**
|
||||
* Configure what animation is used to transition from one frame to another
|
||||
*/
|
||||
void setFrameAnimation(AnimationDirection dir);
|
||||
|
||||
/**
|
||||
* Add frame drawing functions
|
||||
*/
|
||||
void setFrames(FrameCallback* frameFunctions, uint8_t frameCount);
|
||||
|
||||
// Overlay
|
||||
|
||||
/**
|
||||
* Add overlays drawing functions that are draw independent of the Frames
|
||||
*/
|
||||
void setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount);
|
||||
|
||||
|
||||
// Loading animation
|
||||
/**
|
||||
* Set the function that will draw each step
|
||||
* in the loading animation
|
||||
*/
|
||||
void setLoadingDrawFunction(LoadingDrawFunction loadingFunction);
|
||||
|
||||
|
||||
/**
|
||||
* Run the loading process
|
||||
*/
|
||||
void runLoadingProcess(LoadingStage* stages, uint8_t stagesCount);
|
||||
|
||||
|
||||
// Manual Control
|
||||
void nextFrame();
|
||||
void previousFrame();
|
||||
|
||||
/**
|
||||
* Switch without transition to frame `frame`.
|
||||
*/
|
||||
void switchToFrame(uint8_t frame);
|
||||
|
||||
/**
|
||||
* Transition to frame `frame`, when the `frame` number is bigger than the current
|
||||
* frame the forward animation will be used, otherwise the backwards animation is used.
|
||||
*/
|
||||
void transitionToFrame(uint8_t frame);
|
||||
|
||||
// State Info
|
||||
OLEDDisplayUiState* getUiState();
|
||||
|
||||
int8_t update();
|
||||
};
|
||||
#endif
|
||||
39
SSD1306.h
Normal file
39
SSD1306.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
|
||||
* Copyright (c) 2018 by Fabrice Weinberg
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* 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 SSD1306_h
|
||||
#define SSD1306_h
|
||||
#include "SSD1306Wire.h"
|
||||
|
||||
// For legacy support make SSD1306 an alias for SSD1306
|
||||
typedef SSD1306Wire SSD1306;
|
||||
|
||||
|
||||
#endif
|
||||
184
SSD1306Wire.h
Normal file
184
SSD1306Wire.h
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
|
||||
* Copyright (c) 2018 by Fabrice Weinberg
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* 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 SSD1306Wire_h
|
||||
#define SSD1306Wire_h
|
||||
|
||||
#include "OLEDDisplay.h"
|
||||
#include <Wire.h>
|
||||
|
||||
|
||||
class SSD1306Wire : public OLEDDisplay {
|
||||
private:
|
||||
uint8_t _address;
|
||||
uint8_t _sda;
|
||||
uint8_t _scl;
|
||||
uint8_t _rst;
|
||||
bool _doI2cAutoInit = false;
|
||||
|
||||
public:
|
||||
SSD1306Wire(uint8_t _address, uint8_t _sda, uint8_t _scl, uint8_t _rst, OLEDDISPLAY_GEOMETRY g = GEOMETRY_128_64) {
|
||||
setGeometry(g);
|
||||
|
||||
this->_address = _address;
|
||||
this->_sda = _sda;
|
||||
this->_scl = _scl;
|
||||
this->_rst = _rst;
|
||||
}
|
||||
|
||||
|
||||
bool connect() {
|
||||
pinMode(_rst,OUTPUT);
|
||||
digitalWrite(_rst, LOW);
|
||||
delay(50);
|
||||
digitalWrite(_rst, HIGH);
|
||||
|
||||
Wire.begin(this->_sda, this->_scl);
|
||||
// Let's use ~700khz if ESP8266 is in 160Mhz mode
|
||||
// this will be limited to ~400khz if the ESP8266 in 80Mhz mode.
|
||||
Wire.setClock(700000);
|
||||
return true;
|
||||
}
|
||||
|
||||
void display(void) {
|
||||
initI2cIfNeccesary();
|
||||
const int x_offset = (128 - this->width()) / 2;
|
||||
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
|
||||
uint8_t minBoundY = UINT8_MAX;
|
||||
uint8_t maxBoundY = 0;
|
||||
|
||||
uint8_t minBoundX = UINT8_MAX;
|
||||
uint8_t maxBoundX = 0;
|
||||
uint8_t x, y;
|
||||
|
||||
// Calculate the Y bounding box of changes
|
||||
// and copy buffer[pos] to buffer_back[pos];
|
||||
for (y = 0; y < (this->height() / 8); y++) {
|
||||
for (x = 0; x < this->width(); x++) {
|
||||
uint16_t pos = x + y * this->width();
|
||||
if (buffer[pos] != buffer_back[pos]) {
|
||||
minBoundY = _min(minBoundY, y);
|
||||
maxBoundY = _max(maxBoundY, y);
|
||||
minBoundX = _min(minBoundX, x);
|
||||
maxBoundX = _max(maxBoundX, x);
|
||||
}
|
||||
buffer_back[pos] = buffer[pos];
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
// If the minBoundY wasn't updated
|
||||
// we can savely assume that buffer_back[pos] == buffer[pos]
|
||||
// holdes true for all values of pos
|
||||
|
||||
if (minBoundY == UINT8_MAX) return;
|
||||
|
||||
sendCommand(COLUMNADDR);
|
||||
sendCommand(x_offset + minBoundX);
|
||||
sendCommand(x_offset + maxBoundX);
|
||||
|
||||
sendCommand(PAGEADDR);
|
||||
sendCommand(minBoundY);
|
||||
sendCommand(maxBoundY);
|
||||
|
||||
byte k = 0;
|
||||
for (y = minBoundY; y <= maxBoundY; y++) {
|
||||
for (x = minBoundX; x <= maxBoundX; x++) {
|
||||
if (k == 0) {
|
||||
Wire.beginTransmission(_address);
|
||||
Wire.write(0x40);
|
||||
}
|
||||
|
||||
Wire.write(buffer[x + y * this->width()]);
|
||||
k++;
|
||||
if (k == 16) {
|
||||
Wire.endTransmission();
|
||||
k = 0;
|
||||
}
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
if (k != 0) {
|
||||
Wire.endTransmission();
|
||||
}
|
||||
#else
|
||||
|
||||
sendCommand(COLUMNADDR);
|
||||
sendCommand(x_offset);
|
||||
sendCommand(x_offset + (this->width() - 1));
|
||||
|
||||
sendCommand(PAGEADDR);
|
||||
sendCommand(0x0);
|
||||
sendCommand((this->height() / 8) - 1);
|
||||
|
||||
if (geometry == GEOMETRY_128_64) {
|
||||
sendCommand(0x7);
|
||||
} else if (geometry == GEOMETRY_128_32) {
|
||||
sendCommand(0x3);
|
||||
}
|
||||
|
||||
for (uint16_t i=0; i < displayBufferSize; i++) {
|
||||
Wire.beginTransmission(this->_address);
|
||||
Wire.write(0x40);
|
||||
for (uint8_t x = 0; x < 16; x++) {
|
||||
Wire.write(buffer[i]);
|
||||
i++;
|
||||
}
|
||||
i--;
|
||||
Wire.endTransmission();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void setI2cAutoInit(bool doI2cAutoInit) {
|
||||
_doI2cAutoInit = doI2cAutoInit;
|
||||
}
|
||||
|
||||
private:
|
||||
inline void sendCommand(uint8_t command) __attribute__((always_inline)){
|
||||
initI2cIfNeccesary();
|
||||
Wire.beginTransmission(_address);
|
||||
Wire.write(0x80);
|
||||
Wire.write(command);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
void initI2cIfNeccesary() {
|
||||
if (_doI2cAutoInit) {
|
||||
Wire.begin(this->_sda, this->_scl);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//SSD1306Wire display;
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user