285 lines
7.9 KiB
C++
285 lines
7.9 KiB
C++
/**
|
|
* \file cimditssd1306.h
|
|
* \brief Declaration of the CimditSSD1306 class
|
|
* \author GrumpyDeveloper (Sascha Nitsch)
|
|
* \copyright 2022 Sascha Nitsch
|
|
* Licensed under MIT license
|
|
*
|
|
*/
|
|
|
|
// system includes
|
|
#include <avr/pgmspace.h>
|
|
#include <util/delay.h>
|
|
|
|
// our includes
|
|
#include "cimditssd1306.h"
|
|
#include "glcdfont.c"
|
|
|
|
CimditSSD1306::CimditSSD1306(uint8_t w, uint8_t h, uint8_t i2cAddr) {
|
|
m_width = w;
|
|
m_height = h;
|
|
m_buffer = nullptr;
|
|
m_cursorX = m_cursorY = 0;
|
|
m_textsizeX = m_textsizeY = 1;
|
|
m_state = true;
|
|
m_i2cAddr = i2cAddr;
|
|
}
|
|
|
|
CimditSSD1306::~CimditSSD1306() {
|
|
if (m_buffer) {
|
|
free(m_buffer);
|
|
m_buffer = nullptr;
|
|
}
|
|
}
|
|
|
|
void CimditSSD1306::commandStart() {
|
|
Wire.beginTransmission(m_i2cAddr);
|
|
Wire.write((uint8_t)0x00);
|
|
}
|
|
|
|
void CimditSSD1306::commandEnd() {
|
|
Wire.endTransmission();
|
|
}
|
|
|
|
void CimditSSD1306::commandListProgMem(const uint8_t *c, uint8_t n) {
|
|
uint16_t bytesOut = 1;
|
|
while (n--) {
|
|
if (bytesOut >= BUFFER_LENGTH) {
|
|
commandEnd();
|
|
commandStart();
|
|
bytesOut = 1;
|
|
}
|
|
Wire.write(pgm_read_byte(c++));
|
|
bytesOut++;
|
|
}
|
|
}
|
|
|
|
/// static intialization part 1
|
|
static const uint8_t PROGMEM init1[] = {
|
|
SSD1306_SET_DISPLAY_OFF,
|
|
SSD1306_SET_DISPLAY_CLOCK_DIVIDE_RATIO, 0x80,
|
|
SSD1306_SET_MULTIPLEX_RATIO
|
|
};
|
|
|
|
/// static initialization part 2
|
|
static const uint8_t PROGMEM init2[] = {
|
|
SSD1306_SET_DISPLAY_OFFSET, 0x0,
|
|
SSD1306_SET_DISPLAY_START_LINE_0,
|
|
SSD1306_CHARGE_PUMP_SETTING, 0x14,
|
|
SSD1306_SET_MEMORY_ADDRESSING_MODE, 0x00,
|
|
SSD1306_SET_SEGMENT_RE_MAP_127,
|
|
SSD1306_SET_COM_OUTPUT_SCAN_DIRECTION_REMAPPED,
|
|
SSD1306_SET_COM_PINS_HARDWARE_CONFIGURATION, 0x02,
|
|
SSD1306_SET_CONTRAST_CONTROL, 0x8F,
|
|
SSD1306_SET_PRE_CHARGE_PERIOD, 0xF1,
|
|
SSD1306_SET_VCOM_DESELECT_LEVEL, 0x20,
|
|
SSD1306_ENTIRE_DISPLAY_ON,
|
|
SSD1306_SET_NORMAL_DISPLAY,
|
|
SSD1306_DEACTIVATE_SCROLL,
|
|
SSD1306_SET_DISPLAY_ON
|
|
};
|
|
|
|
bool CimditSSD1306::begin() {
|
|
if ((!m_buffer) && !(m_buffer = reinterpret_cast<uint8_t *>(malloc(m_width * (m_height / 8)))))
|
|
return false;
|
|
clearDisplay();
|
|
// Init sequence
|
|
commandStart();
|
|
commandListProgMem(init1, sizeof(init1));
|
|
Wire.write(m_height - 1);
|
|
commandListProgMem(init2, sizeof(init2));
|
|
commandEnd();
|
|
return true; // Success
|
|
}
|
|
|
|
void CimditSSD1306::drawPixel(uint8_t x, uint8_t y, bool state) {
|
|
if (x >= m_width || y >= m_height) return;
|
|
if (state) {
|
|
m_buffer[x + (y >> 3) * m_width] |= (1 << (y & 7));
|
|
} else {
|
|
m_buffer[x + (y >> 3) * m_width] &= ~(1 << (y & 7));
|
|
}
|
|
}
|
|
|
|
void CimditSSD1306::clearDisplay(void) {
|
|
memset(m_buffer, 0, m_width * ((m_height + 7) / 8));
|
|
}
|
|
|
|
void CimditSSD1306::drawFastHLine(uint8_t x, uint8_t y, uint8_t w, bool state) {
|
|
if ((x + w) >= m_width || (y >= m_height)) return;
|
|
uint8_t *pBuf = m_buffer + (y / 8) * m_width + x;
|
|
uint8_t mask = 1 << (y & 7);
|
|
if (state) {
|
|
while (w--) {
|
|
*pBuf++ |= mask;
|
|
}
|
|
} else {
|
|
mask = ~mask;
|
|
while (w--) {
|
|
*pBuf++ &= mask;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CimditSSD1306::drawFastVLine(uint8_t x, uint8_t y, uint8_t h, bool state) {
|
|
uint8_t *pBuf = m_buffer + (y / 8) * m_width + x;
|
|
// do the first partial byte, if necessary - this requires some masking
|
|
uint8_t mod = (y & 7);
|
|
if (mod) {
|
|
// mask off the high n bits we want to set
|
|
mod = 8 - mod;
|
|
// note - lookup table results in a nearly 10% performance
|
|
// improvement in fill* functions
|
|
// uint8_t mask = ~(0xFF >> mod);
|
|
static const uint8_t PROGMEM premask[8] = {0x00, 0x80, 0xC0, 0xE0,
|
|
0xF0, 0xF8, 0xFC, 0xFE};
|
|
uint8_t mask = pgm_read_byte(&premask[mod]);
|
|
// adjust the mask if we're not going to reach the end of this byte
|
|
if (h < mod) {
|
|
mask &= (0XFF >> (mod - h));
|
|
}
|
|
if (state) {
|
|
*pBuf |= mask;
|
|
} else {
|
|
*pBuf &= ~mask;
|
|
}
|
|
pBuf += m_width;
|
|
}
|
|
|
|
if (h >= mod) { // More to go?
|
|
h -= mod;
|
|
// Write solid bytes while we can - effectively 8 rows at a time
|
|
if (h >= 8) {
|
|
// store a local value to work with
|
|
uint8_t val = state ? 255 : 0;
|
|
do {
|
|
*pBuf = val; // Set byte
|
|
pBuf += m_width; // Advance pointer 8 rows
|
|
h -= 8; // Subtract 8 rows from height
|
|
} while (h >= 8);
|
|
}
|
|
if (h) { // Do the final partial byte, if necessary
|
|
mod = h & 7;
|
|
// this time we want to mask the low bits of the byte,
|
|
// vs the high bits we did above
|
|
// uint8_t mask = (1 << mod) - 1;
|
|
// note - lookup table results in a nearly 10% performance
|
|
// improvement in fill* functions
|
|
static const uint8_t PROGMEM postmask[8] = {0x00, 0x01, 0x03, 0x07,
|
|
0x0F, 0x1F, 0x3F, 0x7F};
|
|
uint8_t mask = pgm_read_byte(&postmask[mod]);
|
|
if (state) {
|
|
*pBuf |= mask;
|
|
} else {
|
|
*pBuf &= ~mask;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// statric start command for transmitting screen content
|
|
static const uint8_t PROGMEM dlist1[] = {
|
|
SSD1306_SET_PAGE_ADDRESS, 0, 0x3,
|
|
SSD1306_SET_COLUMN_ADDRESS, 0
|
|
};
|
|
|
|
void CimditSSD1306::display(void) {
|
|
commandStart();
|
|
commandListProgMem(dlist1, sizeof(dlist1));
|
|
Wire.write(m_width - 1); // Column end address
|
|
commandEnd();
|
|
uint16_t count = m_width * (m_height >> 3);
|
|
uint8_t *ptr = m_buffer;
|
|
Wire.beginTransmission(m_i2cAddr);
|
|
Wire.write((uint8_t)0x40);
|
|
uint16_t bytesOut = 1;
|
|
while (count--) {
|
|
if (bytesOut >= BUFFER_LENGTH) {
|
|
Wire.endTransmission();
|
|
Wire.beginTransmission(m_i2cAddr);
|
|
Wire.write((uint8_t)0x40);
|
|
bytesOut = 1;
|
|
}
|
|
Wire.write(*ptr++);
|
|
bytesOut++;
|
|
}
|
|
Wire.endTransmission();
|
|
}
|
|
|
|
void CimditSSD1306::displayPartial(uint8_t part) {
|
|
commandStart();
|
|
Wire.write(SSD1306_SET_PAGE_ADDRESS);
|
|
Wire.write(part); // Page start address
|
|
Wire.write(part); // Page end address
|
|
Wire.write(SSD1306_SET_COLUMN_ADDRESS);
|
|
Wire.write(0);
|
|
Wire.write(m_width - 1); // Column end address
|
|
Wire.endTransmission();
|
|
uint16_t count = m_width * (m_height >> 5);
|
|
uint8_t *ptr = m_buffer + m_width * part;
|
|
Wire.beginTransmission(m_i2cAddr);
|
|
Wire.write((uint8_t)0x40);
|
|
uint16_t bytesOut = 1;
|
|
while (count--) {
|
|
if (bytesOut >= BUFFER_LENGTH) {
|
|
Wire.endTransmission();
|
|
Wire.beginTransmission(m_i2cAddr);
|
|
Wire.write((uint8_t)0x40);
|
|
bytesOut = 1;
|
|
}
|
|
Wire.write(*ptr++);
|
|
bytesOut++;
|
|
}
|
|
Wire.endTransmission();
|
|
}
|
|
|
|
void CimditSSD1306::setTextSize(uint8_t sX, uint8_t sY) {
|
|
m_textsizeX = sX;
|
|
m_textsizeY = sY;
|
|
}
|
|
|
|
size_t CimditSSD1306::write(uint8_t c) {
|
|
if (c == '\n' || (m_cursorX + m_textsizeX * 6 > m_width)) { // a newline or text oveflow
|
|
m_cursorX = 0; // set X to zero
|
|
m_cursorY += m_textsizeY * 8; // go down one line
|
|
} else if (c == '\r') { // a carriage return
|
|
m_cursorX = 0; // set X to zero
|
|
} else { // something to print
|
|
drawChar(m_cursorX, m_cursorY, c, m_textsizeX, m_textsizeY);
|
|
m_cursorX += m_textsizeX * 6; // Advance x one char
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void CimditSSD1306::drawChar(uint8_t x, uint8_t y, unsigned char c, uint8_t sizeX, uint8_t sizeY) {
|
|
for (int8_t i = 0; i < 5; i++) { // one char is 5 pixel wide
|
|
uint8_t line = pgm_read_byte(&font[c * 5 + i]); // get pixel from flash/program memory
|
|
for (int8_t j = 0; j < 8; j++, line >>= 1) { // for each 8 lines
|
|
if (line & 1) {
|
|
if (sizeX == 1 && sizeY == 1) { // one pixel size, draw pixel direct
|
|
drawPixel(x + i, y + j, m_state);
|
|
} else {
|
|
fillRect(x + i * sizeX, y + j * sizeY, sizeX, sizeY, m_state); // draw rectangles with given size multiplicator
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CimditSSD1306::fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, bool state) {
|
|
if (y + h >= m_height) h = m_height - y;
|
|
if (x + w >= m_width) w = m_width - x;
|
|
for (int16_t i = x; i < x + w; i++) {
|
|
drawFastVLine(i, y, h, state);
|
|
}
|
|
}
|
|
|
|
void CimditSSD1306::setTextState(bool state) {
|
|
m_state = state;
|
|
}
|
|
|
|
void CimditSSD1306::setCursor(uint8_t x, uint8_t y) {
|
|
m_cursorX = x;
|
|
m_cursorY = y;
|
|
}
|