#ifndef OLEDI2C_h #define OLEDI2C_h // ===== константы ===== #define SSD1306_128x32 0 #define SSD1306_128x64 1 #define SSH1106_128x64 2 #define OLED_I2C 0 #define OLED_SPI 1 #define OLED_NO_BUFFER 0 #define OLED_BUFFER 1 #define OLED_CLEAR 0 #define OLED_FILL 1 #define OLED_STROKE 2 #define BUF_ADD 0 #define BUF_SUBTRACT 1 #define BUF_REPLACE 2 #define BITMAP_NORMAL 0 #define BITMAP_INVERT 1 // =========================================================================== #include "I2C.h" // лёгкая библиотека Wire (для atmega328) #include "charMap.h" #ifndef OLED_NO_PRINT #include #endif // ============================ БЭКЭНД КОНСТАНТЫ ============================== // внутренние константы #define OLED_WIDTH 128 #define OLED_HEIGHT_32 0x02 #define OLED_HEIGHT_64 0x12 #define OLED_64 0x3F #define OLED_32 0x1F #define OLED_DISPLAY_OFF 0xAE #define OLED_DISPLAY_ON 0xAF #define OLED_COMMAND_MODE 0x00 #define OLED_ONE_COMMAND_MODE 0x80 #define OLED_DATA_MODE 0x40 #define OLED_ONE_DATA_MODE 0xC0 #define OLED_ADDRESSING_MODE 0x20 #define OLED_HORIZONTAL 0x00 #define OLED_VERTICAL 0x01 #define OLED_NORMAL_V 0xC8 #define OLED_FLIP_V 0xC0 #define OLED_NORMAL_H 0xA1 #define OLED_FLIP_H 0xA0 #define OLED_CONTRAST 0x81 #define OLED_SETCOMPINS 0xDA #define OLED_SETVCOMDETECT 0xDB #define OLED_CLOCKDIV 0xD5 #define OLED_SETMULTIPLEX 0xA8 #define OLED_COLUMNADDR 0x21 #define OLED_PAGEADDR 0x22 #define OLED_CHARGEPUMP 0x8D #define OLED_NORMALDISPLAY 0xA6 #define OLED_INVERTDISPLAY 0xA7 #define BUFSIZE_128x64 (128 * 64 / 8) #define BUFSIZE_128x32 (128 * 32 / 8) #ifndef OLED_SPI_SPEED #define OLED_SPI_SPEED 1000000ul #endif //static SPISettings OLED_SPI_SETT(OLED_SPI_SPEED, MSBFIRST, SPI_MODE0); // список инициализации static const uint8_t _oled_init[] PROGMEM = { OLED_DISPLAY_OFF, OLED_CLOCKDIV, 0x80, // value OLED_CHARGEPUMP, 0x14, // value OLED_ADDRESSING_MODE, OLED_VERTICAL, OLED_NORMAL_H, OLED_NORMAL_V, OLED_CONTRAST, 0x7F, // value OLED_SETVCOMDETECT, 0x40, // value OLED_NORMALDISPLAY, OLED_DISPLAY_ON, }; // ========================== КЛАСС КЛАСС КЛАСС ============================= template #ifndef OLED_NO_PRINT class OLEDI2C : public Print { #else class OLEDI2C { #endif public: // ========================== КОНСТРУКТОР ============================= OLEDI2C(uint8_t address = 0x3C) : _address(address) {} // ============================= СЕРВИС =============================== // инициализация void init(int __attribute__((unused)) sda = 0, int __attribute__((unused)) scl = 0) { #if defined(ESP32) || defined(ESP8266) if (sda || scl) Wire.begin(sda, scl); else Wire.begin(); #else Wire.begin(); #endif beginCommand(); for (uint8_t i = 0; i < 15; i++) sendByte(pgm_read_byte(&_oled_init[i])); endTransm(); beginCommand(); sendByte(OLED_SETCOMPINS); sendByte(_TYPE ? OLED_HEIGHT_64 : OLED_HEIGHT_32); sendByte(OLED_SETMULTIPLEX); sendByte(_TYPE ? OLED_64 : OLED_32); endTransm(); setCursorXY(0, 0); if (_BUFF) setWindow(0, 0, _maxX, _maxRow); // для буфера включаем всю область } // очистить дисплей void clear() { fill(0); } // очистить область void clear(int x0, int y0, int x1, int y1) { if (_TYPE < 2) { // для SSD1306 if (_BUFF) rect(x0, y0, x1, y1, OLED_CLEAR); else { x1++; y1++; y0 >>= 3; y1 = (y1 - 1) >> 3; y0 = constrain(y0, 0, _maxRow); y1 = constrain(y1, 0, _maxRow); x0 = constrain(x0, 0, _maxX); x1 = constrain(x1, 0, _maxX); setWindow(x0, y0, x1, y1); beginData(); for (int x = x0; x < x1; x++) for (int y = y0; y < y1 + 1; y++) writeData(0, y, x, 2); endTransm(); } } else { // для SSH1108 } setCursorXY(_x, _y); } // яркость 0-255 void setContrast(uint8_t value) { sendCommand(OLED_CONTRAST, value); } // вкл/выкл void setPower(bool mode) { sendCommand(mode ? OLED_DISPLAY_ON : OLED_DISPLAY_OFF); } // отразить по горизонтали void flipH(bool mode) { sendCommand(mode ? OLED_FLIP_H : OLED_NORMAL_H); } // инвертировать дисплей void invertDisplay(bool mode) { sendCommand(mode ? OLED_INVERTDISPLAY : OLED_NORMALDISPLAY); } // отразить по вертикали void flipV(bool mode) { sendCommand(mode ? OLED_FLIP_V : OLED_NORMAL_V); } // ============================= ПЕЧАТЬ ================================== virtual size_t write(uint8_t data) { #ifndef OLED_NO_PRINT // переносы и пределы bool newPos = false; if (data == '\r') { _x = 0; newPos = true; data = 0; } // получен возврат каретки if (data == '\n') { _y += _scaleY; newPos = true; data = 0; _getn = 1; } // получен перевод строки if (_println && (_x + 6 * _scaleX) >= _maxX) { _x = 0; _y += _scaleY; newPos = true; } // строка переполненена, перевод и возврат if (newPos) setCursorXY(_x, _y); // переставляем курсор if (_y + _scaleY > _maxY + 1) data = 0; // дисплей переполнен if (_getn && _println && data == ' ' && _x == 0) { _getn = 0; data = 0; } // убираем первый пробел в строке // фикс русских букв и некоторых символов if (data > 127) { uint8_t thisData = data; // data = 0 - флаг на пропуск if (data > 191) data = 0; else if (_lastChar == 209 && data == 145) data = 192; // ё кастомная else if (_lastChar == 208 && data == 129) data = 149; // Е вместо Ё else if (_lastChar == 226 && data == 128) data = 0; // тире вместо длинного тире (начало) else if (_lastChar == 128 && data == 148) data = 45; // тире вместо длинного тире _lastChar = thisData; } if (data == 0) return 1; // если тут не вылетели - печатаем символ if (_TYPE < 2 || 1) { // для SSD1306 int newX = _x + _scaleX * 6; if (newX < 0 || _x > _maxX) _x = newX; // пропускаем вывод "за экраном" else { if (!_BUFF) beginData(); for (uint8_t col = 0; col < 6; col++) { // 6 стобиков буквы uint8_t bits = getFont(data, col); // получаем байт if (_invState) bits = ~bits; // инверсия if (_scaleX == 1) { // если масштаб 1 if (_x >= 0 && _x <= _maxX) { // внутри дисплея if (_shift == 0) { // если вывод без сдвига на строку writeData(bits, 0, 0, _mode); // выводим } else { // со сдвигом writeData(bits << _shift, 0, 0, _mode); // верхняя часть writeData(bits >> (8 - _shift), 1, 0, _mode); // нижняя часть } } } else { // масштаб 2, 3 или 4 - растягиваем шрифт uint32_t newData = 0; // буфер for (uint8_t i = 0, count = 0; i < 8; i++) for (uint8_t j = 0; j < _scaleX; j++, count++) bitWrite(newData, count, bitRead(bits, i)); // пакуем растянутый шрифт for (uint8_t i = 0; i < _scaleX; i++) { // выводим. По Х byte prevData = 0; if (_x + i >= 0 && _x + i <= _maxX) // внутри дисплея for (uint8_t j = 0; j < _scaleX; j++) { // выводим. По Y byte data = newData >> (j * 8); // получаем кусок буфера if (_shift == 0) { // если вывод без сдвига на строку writeData(data, j, i, _mode); // выводим } else { // со сдвигом writeData((prevData >> (8 - _shift)) | (data << _shift), j, i, _mode); // склеиваем и выводим prevData = data; // запоминаем предыдущий } } if (_shift != 0) writeData(prevData >> (8 - _shift), _scaleX, i, _mode); // выводим нижний кусочек, если со сдвигом } } _x += _scaleX; // двигаемся на ширину пикселя (1-4) } if (!_BUFF) endTransm(); } } else { // для SSH1106 } #endif return 1; } // автоматически переносить текст void autoPrintln(bool mode) { _println = mode; } // отправить курсор в 0,0 void home() { setCursorXY(0, 0); } // поставить курсор для символа 0-127, 0-8(4) void setCursor(int x, int y) { setCursorXY(x, y << 3); } // поставить курсор для символа 0-127, 0-63(31) void setCursorXY(int x, int y) { _x = x; _y = y; setWindowShift(x, y, _maxX, _scaleY); } // масштаб шрифта (1-4) void setScale(uint8_t scale) { scale = constrain(scale, 1, 4); // защита от нечитающих доку _scaleX = scale; _scaleY = scale * 8; setCursorXY(_x, _y); } // инвертировать текст (0-1) void invertText(bool inv) { _invState = inv; } void textMode(byte mode) { _mode = mode; } // возвращает true, если дисплей "кончился" - при побуквенном выводе bool isEnd() { return (_y > _maxRow); } // ================================== ГРАФИКА ================================== // точка (заливка 1/0) void dot(int x, int y, byte fill = 1) { if (x < 0 || x > _maxX || y < 0 || y > _maxY) return; _x = 0; _y = 0; if (_BUFF) { bitWrite(_oled_buffer[_bufIndex(x, y)], y & 0b111, fill); } else { if (!_buf_flag) { setWindow(x, y >> 3, _maxX, _maxRow); beginData(); sendByte(1 << (y & 0b111)); // задвигаем 1 на высоту y endTransm(); } else { x -= _bufX; y -= _bufY; if (x < 0 || x > _bufsizeX || y < 0 || y > (_bufsizeY << 3)) return; bitWrite(_buf_ptr[(y >> 3) + x * _bufsizeY], y & 0b111, fill); } } } // линия void line(int x0, int y0, int x1, int y1, byte fill = 1) { _x = 0; _y = 0; if (x0 == x1) fastLineV(x0, y0, y1, fill); else if (y0 == y1) fastLineH(y0, x0, x1, fill); else { int sx, sy, e2, err; int dx = abs(x1 - x0); int dy = abs(y1 - y0); sx = (x0 < x1) ? 1 : -1; sy = (y0 < y1) ? 1 : -1; err = dx - dy; for (;;) { dot(x0, y0, fill); if (x0 == x1 && y0 == y1) return; e2 = err << 1; if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; } } } } // горизонтальная линия void fastLineH(int y, int x0, int x1, byte fill = 1) { _x = 0; _y = 0; if (x0 > x1) _swap(x0, x1); if (y < 0 || y > _maxY) return; if (x0 == x1) { dot(x0, y, fill); return; } x1++; x0 = constrain(x0, 0, _maxX); x1 = constrain(x1, 0, _maxX); if (_BUFF) { for (int x = x0; x < x1; x++) dot(x, y, fill); } else { if (_buf_flag) { for (int x = x0; x < x1; x++) dot(x, y, fill); return; } byte data = 0b1 << (y & 0b111); y >>= 3; setWindow(x0, y, x1, y); beginData(); for (int x = x0; x < x1; x++) writeData(data, y, x); endTransm(); } } // вертикальная линия void fastLineV(int x, int y0, int y1, byte fill = 1) { _x = 0; _y = 0; if (y0 > y1) _swap(y0, y1); if (x < 0 || x > _maxX) return; if (y0 == y1) { dot(x, y0, fill); return; } y1++; if (_BUFF) { for (int y = y0; y < y1; y++) dot(x, y, fill); } else { if (_buf_flag) { for (int y = y0; y < y1; y++) dot(x, y, fill); return; } if (fill) fill = 255; byte shift = y0 & 0b111; byte shift2 = 8 - (y1 & 0b111); if (shift2 == 8) shift2 = 0; int height = y1 - y0; y0 >>= 3; y1 = (y1 - 1) >> 3; byte numBytes = y1 - y0; setWindow(x, y0, x, y1); beginData(); if (numBytes == 0) { if (_inRange(y0, 0, _maxRow)) writeData((fill >> (8 - height)) << shift, y0, x); } else { if (_inRange(y0, 0, _maxRow)) writeData(fill << shift, y0, x); // начальный кусок y0++; for (byte i = 0; i < numBytes - 1; i++, y0++) if (_inRange(y0, 0, _maxRow)) writeData(fill, y0, x); // столбик if (_inRange(y0, 0, _maxRow)) writeData(fill >> shift2, y0, x); // нижний кусок } endTransm(); } } // прямоугольник (лев. верхн, прав. нижн) void rect(int x0, int y0, int x1, int y1, byte fill = 1) { _x = 0; _y = 0; if (x0 > x1) _swap(x0, x1); if (y0 > y1) _swap(y0, y1); if (fill == OLED_STROKE) { fastLineH(y0, x0 + 1, x1 - 1); fastLineH(y1, x0 + 1, x1 - 1); fastLineV(x0, y0, y1); fastLineV(x1, y0, y1); } else { if (x0 == x1 && y0 == y1) { dot(x0, y0, fill); return; } if (x0 == x1) { fastLineV(x0, y0, y1); return; } if (y0 == y1) { fastLineH(y0, x0, x1); return; } if (!_BUFF && fill == OLED_CLEAR) { clear(x0, y0, x1, y1); return; } // если рисуем в мини-буфер if (_buf_flag) { x0 = constrain(x0, 0, _maxX); x1 = constrain(x1, 0, _maxX); for (byte x = x0; x <= x1; x++) fastLineV(x, y0, y1, fill == OLED_FILL ? 1 : 0); return; } byte thisFill = (fill == OLED_FILL ? 0 : 1); // рисуем в олед и в большой буфер x1++; y1++; byte shift = y0 & 0b111; byte shift2 = 8 - (y1 & 0b111); if (shift2 == 8) shift2 = 0; int height = y1 - y0; y0 >>= 3; y1 = (y1 - 1) >> 3; byte numBytes = y1 - y0; x0 = constrain(x0, 0, _maxX); x1 = constrain(x1, 0, _maxX); if (!_BUFF) setWindow(x0, y0, x1, y1); if (!_BUFF) beginData(); for (byte x = x0; x < x1; x++) { int y = y0; if (numBytes == 0) { if (_inRange(y, 0, _maxRow)) writeData((255 >> (8 - height)) << shift, y, x, thisFill); } else { if (_inRange(y, 0, _maxRow)) writeData(255 << shift, y, x, thisFill); // начальный кусок y++; for (byte i = 0; i < numBytes - 1; i++, y++) if (_inRange(y, 0, _maxRow)) writeData(255, y, x, thisFill); // столбик if (_inRange(y, 0, _maxRow)) writeData(255 >> shift2, y, x, thisFill); // нижний кусок } } if (!_BUFF) endTransm(); } } // прямоугольник скруглённый (лев. верхн, прав. нижн) void roundRect(int x0, int y0, int x1, int y1, byte fill = OLED_FILL) { if (fill == OLED_STROKE) { fastLineV(x0, y0 + 2, y1 - 2); fastLineV(x1, y0 + 2, y1 - 2); fastLineH(y0, x0 + 2, x1 - 2); fastLineH(y1, x0 + 2, x1 - 2); dot(x0 + 1, y0 + 1); dot(x1 - 1, y0 + 1); dot(x1 - 1, y1 - 1); dot(x0 + 1, y1 - 1); } else { fastLineV(x0, y0 + 2, y1 - 2, fill); fastLineV(x0 + 1, y0 + 1, y1 - 1, fill); fastLineV(x1 - 1, y0 + 1, y1 - 1, fill); fastLineV(x1, y0 + 2, y1 - 2, fill); rect(x0 + 2, y0, x1 - 2, y1, fill); } } // окружность (центр х, центр у, радиус, заливка) void circle(int x, int y, int radius, byte fill = OLED_FILL) { //if (!_BUFF) createBuffer(x - radius, y - radius, x + radius + 1, y + radius); int f = 1 - radius; int ddF_x = 1; int ddF_y = -2 * radius; int x1 = 0; int y1 = radius; byte fillLine = (fill == OLED_CLEAR) ? 0 : 1; dot(x, y + radius, fillLine); dot(x, y - radius, fillLine); dot(x + radius, y, fillLine); dot(x - radius, y, fillLine); //if (fill != OLED_STROKE) fastLineH(y, x - radius, x + radius-1, fillLine); if (fill != OLED_STROKE) fastLineV(x, y - radius, y + radius - 1, fillLine); while (x1 < y1) { if (f >= 0) { y1--; ddF_y += 2; f += ddF_y; } x1++; ddF_x += 2; f += ddF_x; if (fill == OLED_STROKE) { dot(x + x1, y + y1); dot(x - x1, y + y1); dot(x + x1, y - y1); dot(x - x1, y - y1); dot(x + y1, y + x1); dot(x - y1, y + x1); dot(x + y1, y - x1); dot(x - y1, y - x1); } else { // FILL / CLEAR fastLineV(x + x1, y - y1, y + y1, fillLine); fastLineV(x - x1, y - y1, y + y1, fillLine); fastLineV(x + y1, y - x1, y + x1, fillLine); fastLineV(x - y1, y - x1, y + x1, fillLine); /* fastLineH(y + y1, x - x1, x + x1-1, fillLine); fastLineH(y - y1, x - x1, x + x1-1, fillLine); fastLineH(y + x1, x - y1, x + y1-1, fillLine); fastLineH(y - x1, x - y1, x + y1-1, fillLine); */ } } //if (!_BUFF) sendBuffer(); } void bezier(int* arr, uint8_t size, uint8_t dense, uint8_t fill = 1) { int a[size * 2]; for (int i = 0; i < (1 << dense); i++) { for (int j = 0; j < size * 2; j++) a[j] = arr[j]; for (int j = (size - 1) * 2 - 1; j > 0; j -= 2) for (int k = 0; k <= j; k++) a[k] = a[k] + (((a[k + 2] - a[k]) * i) >> dense); dot(a[0], a[1], fill); } } // вывести битмап void drawBitmap(int x, int y, const uint8_t* frame, int width, int height, uint8_t invert = 0, byte mode = 0) { _x = 0; _y = 0; if (invert) invert = 255; byte left = height & 0b111; if (left != 0) height += (8 - left); // округляем до ближайшего кратного степени 2 int shiftY = (y >> 3) + (height >> 3); // строка (row) крайнего байта битмапа setWindowShift(x, y, width, height); // выделяем окно bool bottom = (_shift != 0 && shiftY >= 0 && shiftY <= _maxRow); // рисовать ли нижний сдвинутый байт if (!_BUFF) beginData(); for (int X = x, countX = 0; X < x + width; X++, countX++) { // в пикселях byte prevData = 0; if (_inRange(X, 0, _maxX)) { // мы внутри дисплея по X for (int Y = y >> 3, countY = 0; Y < shiftY; Y++, countY++) { // в строках (пикс/8) byte data = pgm_read_word(&(frame[countY * width + countX])) ^ invert; // достаём байт if (_shift == 0) { // без сдвига по Y if (_inRange(Y, 0, _maxRow)) writeData(data, Y, X, mode); // мы внутри дисплея по Y } else { // со сдвигом по Y if (_inRange(Y, 0, _maxRow)) writeData((prevData >> (8 - _shift)) | (data << _shift), Y, X, mode); // задвигаем prevData = data; } } if (bottom) writeData(prevData >> (8 - _shift), shiftY, X, mode); // нижний байт } } if (!_BUFF) endTransm(); } // залить весь дисплей указанным байтом void fill(uint8_t data) { if (_BUFF) memset(_oled_buffer, data, _bufSize); else { if (_TYPE < 2 || 1) { // для SSD1306 setWindow(0, 0, _maxX, _maxRow); beginData(); for (int i = 0; i < (_TYPE ? 1024 : 512); i++) sendByte(data); endTransm(); } else { // для SSH1108 } } setCursorXY(_x, _y); } // шлёт байт в "столбик" setCursor() и setCursorXY() void drawByte(uint8_t data) { if (++_x > _maxX) return; if (_TYPE < 2 || 1) { // для SSD1306 if (!_BUFF) beginData(); if (_shift == 0) { // если вывод без сдвига на строку writeData(data); // выводим } else { // со сдвигом writeData(data << _shift); // верхняя часть writeData(data >> (8 - _shift), 1); // нижняя часть со сдвигом на 1 } if (!_BUFF) endTransm(); } else { // для SSH1106 /* int h = y & 0x07; if (_BUFF) { for (int p = 0; p < 2; p++) { Wire.beginTransmission(_address); continueCmd(0xB0 + (y >> 3) + p); // Page continueCmd(0x00 + ((x + 2) & 0x0F)); // Column low nibble continueCmd(0x10 + ((x + 2) >> 4)); // Column high nibble continueCmd(0xE0); // Read modify write Wire.write(OLED_ONE_DATA_MODE); Wire.endTransmission(); Wire.requestFrom((int)_address, 2); Wire.read(); // Dummy read int j = Wire.read(); Wire.beginTransmission(_address); Wire.write(OLED_ONE_DATA_MODE); Wire.write((data << h) >> (p << 3) | j); continueCmd(0xEE); // Cancel read modify write Wire.endTransmission(); } } else { for (int p = 0; p < 2; p++) { Wire.beginTransmission(_address); continueCmd(0xB0 + (y >> 3) + p); // Page continueCmd(0x00 + ((x + 2) & 0x0F)); // Column low nibble continueCmd(0x10 + ((x + 2) >> 4)); // Column high nibble Wire.write(OLED_ONE_DATA_MODE); Wire.write((data << h) >> (p << 3)); Wire.endTransmission(); } }*/ } } // вывести одномерный байтовый массив (линейный битмап высотой 8) void drawBytes(uint8_t* data, byte size) { if (!_BUFF) beginData(); for (byte i = 0; i < size; i++) { if (++_x > _maxX) return; if (_shift == 0) { // если вывод без сдвига на строку writeData(data[i]); // выводим } else { // со сдвигом writeData(data[i] << _shift); // верхняя часть writeData(data[i] >> (8 - _shift), 1); // нижняя часть со сдвигом на 1 } } if (!_BUFF) endTransm(); } // ================================== СИСТЕМНОЕ =================================== // полностью обновить дисплей из буфера void update() { if (_BUFF) { if (_TYPE < 2) { // для 1306 setWindow(0, 0, _maxX, _maxRow); beginData(); for (int i = 0; i < (_TYPE ? 1024 : 512); i++) sendByte(_oled_buffer[i]); endTransm(); } else { // для 1106 sendCommand(0x00); sendCommand(0x10); sendCommand(0x40); uint16_t ptr = 0; for (uint8_t i = 0; i < (64 >> 3); i++) { sendCommand(0xB0 + i + 0); //set page address sendCommand(2 & 0xf); //set lower column address sendCommand(0x10); //set higher column address for (uint8_t a = 0; a < 8; a++) { beginData(); for (uint8_t b = 0; b < (OLED_WIDTH >> 3); b++) { sendByteRaw(_oled_buffer[((ptr & 0x7F) << 3) + (ptr >> 7)]); ptr++; } endTransm(); } } } } } // выборочно обновить дисплей из буфера (x0, y0, x1, y1) void update(int x0, int y0, int x1, int y1) { if (_BUFF) { y0 >>= 3; y1 >>= 3; setWindow(x0, y0, x1, y1); beginData(); for (int x = x0; x < x1; x++) for (int y = y0; y < y1 + 1; y++) if (x >= 0 && x <= _maxX && y >= 0 && y <= _maxRow) sendByte(_oled_buffer[y + x * (_TYPE ? 8 : 4)]); endTransm(); } } // отправить байт по i2с или в буфер void writeData(byte data, byte offsetY = 0, byte offsetX = 0, int mode = 0) { if (_BUFF) { switch (mode) { case 0: _oled_buffer[_bufIndex(_x + offsetX, _y) + offsetY] |= data; // добавить break; case 1: _oled_buffer[_bufIndex(_x + offsetX, _y) + offsetY] &= ~data; // вычесть break; case 2: _oled_buffer[_bufIndex(_x + offsetX, _y) + offsetY] = data; // заменить break; } } else { if (_buf_flag) { int x = _x - _bufX; int y = _y - _bufY; if (x < 0 || x > _bufsizeX || y < 0 || y > (_bufsizeY << 3)) return; switch (mode) { case 0: _buf_ptr[(y >> 3) + x * _bufsizeY] |= data; // добавить break; case 1: _buf_ptr[(y >> 3) + x * _bufsizeY] &= ~data; // вычесть break; case 2: _buf_ptr[(y >> 3) + x * _bufsizeY] = data; // заменить break; } } else { sendByte(data); } } } // окно со сдвигом. x 0-127, y 0-63 (31), ширина в пикселях, высота в пикселях void setWindowShift(int x0, int y0, int sizeX, int sizeY) { _shift = y0 & 0b111; if (!_BUFF) setWindow(x0, (y0 >> 3), x0 + sizeX, (y0 + sizeY - 1) >> 3); } // ================== ДИНАМИЧЕСКИЙ БУФЕР ================ // создать bool createBuffer(int x0, int y0, int x1, int y1, byte fill = 0) { if (!_BUFF) { _bufX = x0; _bufY = y0; _bufsizeX = x1 - x0 + 1; _bufsizeY = ((y1 - y0) >> 3) + 1; int bufSize = _bufsizeX * _bufsizeY; _buf_ptr = (byte*)malloc(bufSize); if (_buf_ptr != NULL) { _buf_flag = true; memset(_buf_ptr, fill, bufSize); return true; } else { _buf_flag = false; return false; } } } // отправить void sendBuffer() { if (!_BUFF) { if (_buf_flag) { setWindow(_bufX, _bufY >> 3, _bufX + _bufsizeX, (_bufY >> 3) + _bufsizeY - 1); beginData(); for (int i = 0; i < _bufsizeX * _bufsizeY; i++) { sendByte(_buf_ptr[i]); } endTransm(); _buf_flag = false; free(_buf_ptr); } } } // ========= ЛОУ-ЛЕВЕЛ ОТПРАВКА ========= // супер-костыль для либы Wire. Привет индусам! void sendByte(uint8_t data) { sendByteRaw(data); #if !defined(microWire_h) if (!_CONN) { _writes++; if (_writes >= 16) { endTransm(); beginData(); } } #endif } void sendByteRaw(uint8_t data) { Wire.write(data); } // отправить команду void sendCommand(uint8_t cmd1) { beginOneCommand(); sendByteRaw(cmd1); endTransm(); } // отправить код команды и команду void sendCommand(uint8_t cmd1, uint8_t cmd2) { beginCommand(); sendByteRaw(cmd1); sendByteRaw(cmd2); endTransm(); } // выбрать "окно" дисплея void setWindow(int x0, int y0, int x1, int y1) { beginCommand(); sendByteRaw(OLED_COLUMNADDR); sendByteRaw(constrain(x0, 0, _maxX)); sendByteRaw(constrain(x1, 0, _maxX)); sendByteRaw(OLED_PAGEADDR); sendByteRaw(constrain(y0, 0, _maxRow)); sendByteRaw(constrain(y1, 0, _maxRow)); endTransm(); } void beginData() { startTransm(); if (_CONN) fastWrite(_DC, 1); else sendByteRaw(OLED_DATA_MODE); } void beginCommand() { startTransm(); if (_CONN) fastWrite(_DC, 0); else sendByteRaw(OLED_COMMAND_MODE); } void beginOneCommand() { startTransm(); if (_CONN) fastWrite(_DC, 0); else sendByteRaw(OLED_ONE_COMMAND_MODE); } void endTransm() { Wire.endTransmission(); _writes = 0; } void startTransm() { Wire.beginTransmission(_address); } // получить "столбик-байт" буквы uint8_t getFont(uint8_t font, uint8_t row) { #ifndef OLED_NO_PRINT if (row > 4) return 0; font = font - '0' + 16; // перевод код символа из таблицы ASCII if (font <= 95) { return pgm_read_byte(&(_charMap[font][row])); // для английских букв и символов } else if (font >= 96 && font <= 111) { // и пизд*ц для русских return pgm_read_byte(&(_charMap[font + 47][row])); } else if (font <= 159) { return pgm_read_byte(&(_charMap[font - 17][row])); } else { return pgm_read_byte(&(_charMap[font - 1][row])); // для кастомных (ё) } #endif } // ==================== ПЕРЕМЕННЫЕ И КОНСТАНТЫ ==================== const uint8_t _address = 0x3C; const uint8_t _maxRow = (_TYPE ? 8 : 4) - 1; const uint8_t _maxY = (_TYPE ? 64 : 32) - 1; const uint8_t _maxX = OLED_WIDTH - 1; // на случай добавления мелких дисплеев // софт. буфер const int _bufSize = ((_BUFF == 1) ? (_TYPE ? BUFSIZE_128x64 : BUFSIZE_128x32) : 0); uint8_t _oled_buffer[((_BUFF == 1) ? (_TYPE ? BUFSIZE_128x64 : BUFSIZE_128x32) : 0)]; private: // всякое void fastWrite(const uint8_t pin, bool val) { #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) if (pin < 8) bitWrite(PORTD, pin, val); else if (pin < 14) bitWrite(PORTB, (pin - 8), val); else if (pin < 20) bitWrite(PORTC, (pin - 14), val); #elif defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny13__) bitWrite(PORTB, pin, val); #else digitalWrite(pin, val); #endif } // индекс в буфере int _bufIndex(int x, int y) { return ((y) >> 3) + ((x) << (_TYPE ? 3 : 2)); // _y / 8 + _x * (4 или 8) } void _swap(int& x, int& y) { int z = x; x = y; y = z; } bool _inRange(int x, int mi, int ma) { return x >= mi && x <= ma; } bool _invState = 0; bool _println = false; bool _getn = false; uint8_t _scaleX = 1, _scaleY = 8; int _x = 0, _y = 0; uint8_t _shift = 0; uint8_t _lastChar; uint8_t _writes = 0; uint8_t _mode = 2; // дин. буфер int _bufsizeX, _bufsizeY; int _bufX, _bufY; uint8_t* _buf_ptr; bool _buf_flag = false; }; #endif