/** * \file cimditssd1306.h * \brief Declaration of the CimditSSD1306 class * \author GrumpyDeveloper (Sascha Nitsch) * \copyright 2022 Sascha Nitsch * Licensed under MIT license * */ // system includes #include #include // 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(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; }