/**
Bare minimum i2c driver for OLED display
bkucenski@gmail.com 2/22/2019
based on https://github.com/adafruit/Adafruit_SSD1306
*/
#ifndef DISPLAY128X64_H
#define DISPLAY128X64_H
#include <Wire.h>
#define CLOCK_DISPLAY 400000 // this seems to work. Faster is better
#define SSD1306_MEMORYMODE 0x20 ///< See datasheet
#define SSD1306_COLUMNADDR 0x21 ///< See datasheet
#define SSD1306_PAGEADDR 0x22 ///< See datasheet
#define SSD1306_SETCONTRAST 0x81 ///< See datasheet
#define SSD1306_CHARGEPUMP 0x8D ///< See datasheet
#define SSD1306_SEGREMAP 0xA0 ///< See datasheet
#define SSD1306_DISPLAYALLON_RESUME 0xA4 ///< See datasheet
#define SSD1306_DISPLAYALLON 0xA5 ///< Not currently used
#define SSD1306_NORMALDISPLAY 0xA6 ///< See datasheet
#define SSD1306_INVERTDISPLAY 0xA7 ///< See datasheet
#define SSD1306_SETMULTIPLEX 0xA8 ///< See datasheet
#define SSD1306_DISPLAYOFF 0xAE ///< See datasheet
#define SSD1306_DISPLAYON 0xAF ///< See datasheet
#define SSD1306_COMSCANINC 0xC0 ///< Not currently used
#define SSD1306_COMSCANDEC 0xC8 ///< See datasheet
#define SSD1306_SETDISPLAYOFFSET 0xD3 ///< See datasheet
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5 ///< See datasheet
#define SSD1306_SETPRECHARGE 0xD9 ///< See datasheet
#define SSD1306_SETCOMPINS 0xDA ///< See datasheet
#define SSD1306_SETVCOMDETECT 0xDB ///< See datasheet
#define SSD1306_SETLOWCOLUMN 0x00 ///< Not currently used
#define SSD1306_SETHIGHCOLUMN 0x10 ///< Not currently used
#define SSD1306_SETSTARTLINE 0x40 ///< See datasheet
#define SSD1306_EXTERNALVCC 0x01 ///< External display voltage source
#define SSD1306_SWITCHCAPVCC 0x02 ///< Gen. display voltage from 3.3V
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26 ///< Init rt scroll
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27 ///< Init left scroll
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 ///< Init diag scroll
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A ///< Init diag scroll
#define SSD1306_DEACTIVATE_SCROLL 0x2E ///< Stop scroll
#define SSD1306_ACTIVATE_SCROLL 0x2F ///< Start scroll
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3 ///< Set scroll range
#define SCREEN_ADDR 0x3C
#define SCREEN_WIDTH_BYTES 16
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SCREEN_BYTES 1024
#if defined(BUFFER_LENGTH)
#define WIRE_MAX BUFFER_LENGTH ///< AVR or similar Wire lib
#elif defined(SERIAL_BUFFER_SIZE)
#define WIRE_MAX (SERIAL_BUFFER_SIZE-1) ///< Newer Wire uses RingBuffer
#else
#define WIRE_MAX 32 ///< Use common Arduino core default
#endif
byte screen[SCREEN_BYTES];
void PlotPixel(int x, int y, byte c)
{
if (y < 0) {
return;
}
if (y >= SCREEN_HEIGHT) {
return;
}
if (x < 0) {
return;
}
if (x >= SCREEN_WIDTH) {
return;
}
if (c) {
screen[x + (y / 8) * SCREEN_WIDTH] |= (1 << (y & 7));
} else {
screen[x + (y / 8) * SCREEN_WIDTH] &= ~(1 << (y & 7));
}
}
int ix, iy;
byte b;
// 16x16 sprites
void Blit(int x, int y, byte * img)
{
for (iy = 0; iy < 16; iy++) {
b = pgm_read_byte(img + iy * 2);
for (ix = 0; ix < 8; ix++) {
PlotPixel(x + ix, y + iy, (byte)((b >> (7 - (ix % 8))) & 1));
}
b = pgm_read_byte(img + iy * 2 + 1);
for (ix = 8; ix < 16; ix++) {
PlotPixel(x + ix, y + iy, (byte)((b >> (7 - (ix % 8))) & 1));
}
}
}
// 8x8 sprites
void Blit8(int x, int y, byte * img)
{
for (iy = 0; iy < 8; iy++) {
b = pgm_read_byte(img + iy);
for (ix = 0; ix < 8; ix++) {
PlotPixel(x + ix, y + iy, (byte)((b >> (7 - (ix % 8))) & 1));
}
}
}
// 8x16 sprites
void Blit816(int x, int y, byte * img)
{
for (iy = 0; iy < 16; iy++) {
b = pgm_read_byte(img + iy);
for (ix = 0; ix < 8; ix++) {
PlotPixel(x + ix, y + iy, (byte)((b >> (7 - (ix % 8))) & 1));
}
}
}
// 5x8 sprites
void Blit58(int x, int y, byte * img)
{
for (ix = 0; ix < 5; ix++) {
b = pgm_read_byte(img + ix);
for (iy = 0; iy < 8; iy++) {
PlotPixel(x + ix, y + iy, (byte)((b >> ((iy % 8))) & 1));
}
}
}
void BlitText58(int x, int y, byte * font, byte * str)
{
while (str[0] && x < 128) {
Blit58(x, y, font + 5 * str[0]);
str++;
x += 5;
}
}
void BlitText816(int x, int y, byte * font, byte * str)
{
while (str[0] && x < 128) {
Blit816(x, y, font + 16 * str[0]);
str++;
x += 8;
}
}
void ClearDisplay()
{
memset(screen, 0, SCREEN_BYTES);
}
void SendCommand(uint8_t c)
{
Wire.beginTransmission(SCREEN_ADDR);
Wire.write((uint8_t)0x00); // Co = 0, D/C = 0
Wire.write(c);
Wire.endTransmission();
}
void SendCommands(const uint8_t *c, uint8_t n)
{
Wire.beginTransmission(SCREEN_ADDR);
Wire.write((uint8_t)0x00); // Co = 0, D/C = 0
uint8_t bytesOut = 1;
while (n--) {
if (bytesOut >= WIRE_MAX) {
Wire.endTransmission();
Wire.beginTransmission(SCREEN_ADDR);
Wire.write((uint8_t)0x00); // Co = 0, D/C = 0
bytesOut = 1;
}
Wire.write(pgm_read_byte(c++));
bytesOut++;
}
Wire.endTransmission();
}
bool InitDisplay(uint8_t vccstate)
{
ClearDisplay();
// Init sequence
static const uint8_t PROGMEM init1[] = {
SSD1306_DISPLAYOFF, // 0xAE
SSD1306_SETDISPLAYCLOCKDIV, // 0xD5
0x80, // the suggested ratio 0x80
SSD1306_SETMULTIPLEX
}; // 0xA8
SendCommands(init1, sizeof(init1));
SendCommand(SCREEN_HEIGHT - 1);
static const uint8_t PROGMEM init2[] = {
SSD1306_SETDISPLAYOFFSET, // 0xD3
0x0, // no offset
SSD1306_SETSTARTLINE | 0x0, // line #0
SSD1306_CHARGEPUMP
}; // 0x8D
SendCommands(init2, sizeof(init2));
SendCommand((vccstate == SSD1306_EXTERNALVCC) ? 0x10 : 0x14);
static const uint8_t PROGMEM init3[] = {
SSD1306_MEMORYMODE, // 0x20
0x00, // 0x0 act like ks0108
SSD1306_SEGREMAP | 0x1,
SSD1306_COMSCANDEC
};
SendCommands(init3, sizeof(init3));
if ((SCREEN_WIDTH == 128) && (SCREEN_HEIGHT == 32)) {
static const uint8_t PROGMEM init4a[] = {
SSD1306_SETCOMPINS, // 0xDA
0x02,
SSD1306_SETCONTRAST, // 0x81
0x8F
};
SendCommands(init4a, sizeof(init4a));
} else if ((SCREEN_WIDTH == 128) && (SCREEN_HEIGHT == 64)) {
static const uint8_t PROGMEM init4b[] = {
SSD1306_SETCOMPINS, // 0xDA
0x12,
SSD1306_SETCONTRAST
}; // 0x81
SendCommands(init4b, sizeof(init4b));
SendCommand((vccstate == SSD1306_EXTERNALVCC) ? 0x9F : 0xCF);
} else if ((SCREEN_WIDTH == 96) && (SCREEN_HEIGHT == 16)) {
static const uint8_t PROGMEM init4c[] = {
SSD1306_SETCOMPINS, // 0xDA
0x2, // ada x12
SSD1306_SETCONTRAST
}; // 0x81
SendCommands(init4c, sizeof(init4c));
SendCommand((vccstate == SSD1306_EXTERNALVCC) ? 0x10 : 0xAF);
} else {
// Other screen varieties -- TBD
}
SendCommand(SSD1306_SETPRECHARGE); // 0xd9
SendCommand((vccstate == SSD1306_EXTERNALVCC) ? 0x22 : 0xF1);
static const uint8_t PROGMEM init5[] = {
SSD1306_SETVCOMDETECT, // 0xDB
0x40,
SSD1306_DISPLAYALLON_RESUME, // 0xA4
SSD1306_NORMALDISPLAY, // 0xA6
SSD1306_DEACTIVATE_SCROLL,
SSD1306_DISPLAYON
}; // Main screen turn on
SendCommands(init5, sizeof(init5));
return true; // Success
}
void PushToDisplay()
{
static const uint8_t PROGMEM dlist1[] = {
SSD1306_PAGEADDR,
0, // Page start address
0xFF, // Page end (not really, but works here)
SSD1306_COLUMNADDR,
0
}; // Column start address
SendCommands(dlist1, sizeof(dlist1));
SendCommand(SCREEN_WIDTH - 1); // Column end address
uint8_t *ptr = screen;
uint16_t count = SCREEN_BYTES;
Wire.beginTransmission(SCREEN_ADDR);
Wire.write((uint8_t)0x40);
uint8_t bytesOut = 1;
while (count--) {
if (bytesOut >= WIRE_MAX) {
Wire.endTransmission();
Wire.beginTransmission(SCREEN_ADDR);
Wire.write((uint8_t)0x40);
bytesOut = 1;
}
Wire.write(*ptr++);
bytesOut++;
}
Wire.endTransmission();
}
#endif
#endif