/*
* displayclass.cpp
*
* Created on: 31 Oct 2020
* Author: mike
*/
#include "libOLED/displayclass.H"
#include <cstring>
#include <cstdlib>
namespace
{
uint8_t const SSD1306_SETCONTRAST = 0x81;
uint8_t const SSD1306_DISPLAYALLON_RESUME = 0xA4;
uint8_t const SSD1306_DISPLAYALLON = 0xA5;
uint8_t const SSD1306_NORMALDISPLAY = 0xA6;
uint8_t const SSD1306_INVERTDISPLAY = 0xA7;
uint8_t const SSD1306_DISPLAYOFF = 0xAE;
uint8_t const SSD1306_DISPLAYON = 0xAF;
uint8_t const SSD1306_SETDISPLAYOFFSET = 0xD3;
uint8_t const SSD1306_SETCOMPINS = 0xDA;
uint8_t const SSD1306_SETVCOMDETECT = 0xDB;
uint8_t const SSD1306_SETDISPLAYCLOCKDIV = 0xD5;
uint8_t const SSD1306_SETPRECHARGE = 0xD9;
uint8_t const SSD1306_SETMULTIPLEX = 0xA8;
uint8_t const SSD1306_SETLOWCOLUMN = 0x00;
uint8_t const SSD1306_SETHIGHCOLUMN = 0x10;
uint8_t const SSD1306_SETSTARTLINE = 0x40;
uint8_t const SSD1306_MEMORYMODE = 0x20;
uint8_t const SSD1306_COLUMNADDR = 0x21;
uint8_t const SSD1306_PAGEADDR = 0x22;
uint8_t const SSD1306_COMSCANINC = 0xC0;
uint8_t const SSD1306_COMSCANDEC = 0xC8;
uint8_t const SSD1306_SEGREMAP = 0xA0;
uint8_t const SSD1306_CHARGEPUMP = 0x8D;
uint8_t const SSD1306_EXTERNALVCC = 0x1;
uint8_t const SSD1306_SWITCHCAPVCC = 0x2;
// Scrolling #defines
uint8_t const SSD1306_ACTIVATE_SCROLL = 0x2F;
uint8_t const SSD1306_DEACTIVATE_SCROLL = 0x2E;
uint8_t const SSD1306_SET_VERTICAL_SCROLL_AREA = 0xA3;
uint8_t const SSD1306_RIGHT_HORIZONTAL_SCROLL = 0x26;
uint8_t const SSD1306_LEFT_HORIZONTAL_SCROLL = 0x27;
uint8_t const SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29;
uint8_t const SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A;
template<class T>
void
swap (T &x, T &y)
{
T temp = x;
x = y;
y = temp;
}
template<class T>
T
abs (T x)
{
return x < 0 ? -x : x;
}
}
// provided to allow a destructor to destroy something not deleted
// this is only OK because destructors shouldnt be needed
void
operator delete (void *data, unsigned int f)
{
(void) data;
(void) f;
}
// provided to implement an "error handler" on pure virtual
extern "C" void
__cxa_pure_virtual ()
{
while (1)
;
}
display_t::display_t (int const width, int const height, int const ramwidth,
uint8_t *const data) :
m_width (width), m_height (height), m_ramwidth (ramwidth), m_cursor_x (0), m_cursor_y (
0), m_rotation (0), m_colour (WHITE), m_data (data)
{
}
display_t::~display_t ()
{
}
void display_t::reset()
{
oledReset();
}
void
display_t::init ()
{
uint8_t const vccstate = SSD1306_EXTERNALVCC;
oledSetCD (0);
// Init sequence for 128x32 or 128x64 OLED module
oledWrite (SSD1306_DISPLAYOFF); // 0xAE
oledWrite (SSD1306_SETDISPLAYCLOCKDIV); // 0xD5
oledWrite (0x80); // the suggested ratio 0x80
oledWrite (SSD1306_SETMULTIPLEX); // 0xA8
oledWrite (m_height - 1);
oledWrite (SSD1306_SETDISPLAYOFFSET); // 0xD3
oledWrite (0x0); // no offset
oledWrite (SSD1306_SETSTARTLINE | 0x0); // line #0
oledWrite (SSD1306_CHARGEPUMP); // 0x8D
oledWrite (vccstate == SSD1306_EXTERNALVCC ? 0x10 : 0x14);
oledWrite (SSD1306_MEMORYMODE); // 0x20
oledWrite (0x00); // 0x0 act like ks0108
oledWrite (SSD1306_SEGREMAP | 0x1);
oledWrite (SSD1306_COMSCANDEC);
oledWrite (SSD1306_SETCOMPINS); // 0xDA
oledWrite (m_height == 32 ? 0x02 : 0x12);
oledWrite (SSD1306_SETCONTRAST); // 0x81
oledWrite (vccstate == SSD1306_EXTERNALVCC ? 0x9F : 0xCF);
oledWrite (SSD1306_SETPRECHARGE); // 0xd9
oledWrite (vccstate == SSD1306_EXTERNALVCC ? 0x22 : 0xF1);
oledWrite (SSD1306_SETVCOMDETECT); // 0xDB
oledWrite (0x40);
oledWrite (SSD1306_DISPLAYALLON_RESUME); // 0xA4
oledWrite (SSD1306_NORMALDISPLAY); // 0xA6
oledWrite (SSD1306_DISPLAYON); //--turn on oled panel
clearDisplay ();
}
uint8_t
display_t::getRotation (void)
{
return m_rotation;
}
int16_t
display_t::width (void)
{
switch (m_rotation)
{
case 0:
return m_width;
break;
case 1:
return m_width;
break;
case 2:
return m_height;
break;
case 3:
return -m_width;
break;
}
return 0;
}
int16_t
display_t::height (void)
{
switch (m_rotation)
{
case 0:
return m_height;
break;
case 1:
return m_height;
break;
case 2:
return m_width;
break;
case 3:
return -m_height;
break;
}
return 0;
}
// the most basic function, set a single pixel
void
display_t::drawPixel (int16_t x, int16_t y, bool pixel)
{
if ((x < 0) || (x >= m_width) || (y < 0) || (y >= m_height))
return;
// check rotation, move pixel around if necessary
switch (m_rotation)
{
case 1:
swap (x, y);
x = m_width - x - 1;
break;
case 2:
x = m_width - x - 1;
y = m_height - y - 1;
break;
case 3:
swap (x, y);
y = m_height - y - 1;
break;
}
// x is which column
// BLACK, and 0, invert 0
// WHITE, and 0, invert 1
// OVERLAY, and 1 (preserve) , invert 0/
// INVERT, and 1, (preserve) , invert 1
switch (m_colour)
{
case BLACK:
case WHITE:
m_data[x + (y / 8) * m_width] &= ~(1 << (y & 7));
break;
default:
break;
}
uint8_t pixData = 0;
switch (m_colour)
{
case BLACK:
case INVERT:
pixData = pixel ? 0 : 1;
break;
default:
pixData = pixel ? 1 : 0;
break;
}
m_data[x + (y / 8) * m_width] ^= (pixData << (y & 7));
}
void
display_t::invertDisplay (uint8_t i)
{
oledSetCD (0);
oledWrite (i ? SSD1306_INVERTDISPLAY : SSD1306_NORMALDISPLAY);
}
// startscrollright
// Activate a right handed scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void
display_t::startscrollright (uint8_t start, uint8_t stop)
{
oledSetCD (0);
oledWrite (SSD1306_RIGHT_HORIZONTAL_SCROLL);
oledWrite (0X00);
oledWrite (start);
oledWrite (0X00);
oledWrite (stop);
oledWrite (0X00);
oledWrite (0XFF);
oledWrite (SSD1306_ACTIVATE_SCROLL);
}
// startscrollleft
// Activate a right handed scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void
display_t::startscrollleft (uint8_t start, uint8_t stop)
{
oledSetCD (0);
oledWrite (SSD1306_LEFT_HORIZONTAL_SCROLL);
oledWrite (0X00);
oledWrite (start);
oledWrite (0X00);
oledWrite (stop);
oledWrite (0X00);
oledWrite (0XFF);
oledWrite (SSD1306_ACTIVATE_SCROLL);
}
// startscrolldiagright
// Activate a diagonal scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void
display_t::startscrolldiagright (uint8_t start, uint8_t stop)
{
oledSetCD (0);
oledWrite (SSD1306_SET_VERTICAL_SCROLL_AREA);
oledWrite (0X00);
oledWrite (m_height);
oledWrite (SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL);
oledWrite (0X00);
oledWrite (start);
oledWrite (0X00);
oledWrite (stop);
oledWrite (0X01);
oledWrite (SSD1306_ACTIVATE_SCROLL);
}
// startscrolldiagleft
// Activate a diagonal scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void
display_t::startscrolldiagleft (uint8_t start, uint8_t stop)
{
oledSetCD (0);
oledWrite (SSD1306_SET_VERTICAL_SCROLL_AREA);
oledWrite (0X00);
oledWrite (m_height);
oledWrite (SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL);
oledWrite (0X00);
oledWrite (start);
oledWrite (0X00);
oledWrite (stop);
oledWrite (0X01);
oledWrite (SSD1306_ACTIVATE_SCROLL);
}
void
display_t::stopscroll (void)
{
oledSetCD (0);
oledWrite (SSD1306_DEACTIVATE_SCROLL);
}
// Dim the display
// dim = true: display is dimmed
// dim = false: display is normal
void
display_t::dim (uint8_t contrast)
{
// the range of contrast to too small to be really useful
// it is useful to dim the display
oledSetCD (0);
oledWrite (SSD1306_SETCONTRAST);
oledWrite (contrast);
}
void
display_t::display (void)
{
oledSetCD (0);
// select entire display as window to write into
oledWrite (SSD1306_COLUMNADDR);
oledWrite (0); // Column start address (0 = reset)
oledWrite (m_ramwidth - 1); // Column end address (127 = reset)
oledWrite (SSD1306_PAGEADDR);
oledWrite (0); // Page start address (0 = reset)
oledWrite ((m_height == 64) ? 7 : 3); // Page end address
int row;
int col = m_ramwidth == 132 ? 2 : 0;
for (row = 0; row < m_height / 8; row++)
{
oledSetCD (0);
// set the cursor to
oledWrite (0xB0 + row); //set page address
oledWrite (col & 0xf); //set lower column address
oledWrite (0x10 | (col >> 4)); //set higher column address
oledSetCD (1);
oledWrite (m_data + row * m_width, m_width);
}
}
// clear everything
void
display_t::clearDisplay (colour_t colour)
{
switch (colour)
{
case WHITE:
case OVERLAY:
memset (m_data, 255, dataSize (m_width, m_height));
break;
case BLACK:
memset (m_data, 0, dataSize (m_width, m_height));
break;
case INVERT:
for (size_t i = 0; i < dataSize (m_width, m_height); i++)
m_data[i] ^= 255;
break;
}
}
void
display_t::drawRectangle (int16_t x1, int16_t y1, int16_t x2, int16_t y2,
colour_t color)
{
for (int16_t x = x1; x <= x2; x++)
for (int16_t y = y1; y < y2; y++)
{
switch (color)
{
case BLACK:
m_data[x + (y / 8) * m_width] &= ~(1 << (y & 7));
break;
default:
case WHITE:
case OVERLAY:
m_data[x + (y / 8) * m_width] |= (1 << (y & 7));
break;
case INVERT:
m_data[x + (y / 8) * m_width] ^= (1 << (y & 7));
break;
}
}
}
/* using Bresenham draw algorithm */
void
display_t::drawLine (int16_t x1, int16_t y1, int16_t x2, int16_t y2,
colour_t color)
{
int16_t x, y, dx, dy, //deltas
dx2, dy2, //scaled deltas
ix, iy, //increase rate on the x and y axis
err; //the error term
uint16_t i; //looping variable
// identify the first pixel
x = x1;
y = y1;
// difference between starting and ending points
dx = x2 - x1;
dy = y2 - y1;
// calculate direction of the vector and store in ix and iy
if (dx >= 0)
ix = 1;
if (dx < 0)
{
ix = -1;
dx = abs (dx);
}
if (dy >= 0)
iy = 1;
if (dy < 0)
{
iy = -1;
dy = abs (dy);
}
// scale deltas and store in dx2 and dy2
dx2 = dx * 2;
dy2 = dy * 2;
// all variables are set and it's time to enter the main loop.
if (dx > dy) // dx is the major axis
{
// initialize the error term
err = dy2 - dx;
for (i = 0; i <= dx; i++)
{
drawPixel (x, y, color);
if (err >= 0)
{
err -= dx2;
y += iy;
}
err += dy2;
x += ix;
}
}
else // dy is the major axis
{
// initialize the error term
err = dx2 - dy;
for (i = 0; i <= dy; i++)
{
drawPixel (x, y, color);
if (err >= 0)
{
err -= dy2;
x += ix;
}
err += dx2;
y += iy;
}
}
}