cimdit/cimditssd1306.cpp

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;
}