Skip to content

Instantly share code, notes, and snippets.

@tobozo
Last active October 6, 2023 13:02
Show Gist options
  • Save tobozo/6d1ad419823148a638832eaefa79fc63 to your computer and use it in GitHub Desktop.
Save tobozo/6d1ad419823148a638832eaefa79fc63 to your computer and use it in GitHub Desktop.
/*
* Multiplexed SSD1306 Demo
* Copyright © 2018-01-02 tobozo
*
* https://github.com/tobozo
* https://twitter.com/tobozotagada
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the “Software”), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* The Software is provided “as is”, without warranty of any kind, express or
* implied, including but not limited to the warranties of merchantability,
* fitness for a particular purpose and noninfringement. In no event shall the
* authors or copyright holders tobozo be liable for any claim, damages or other
* liability, whether in an action of contract, tort or otherwise, arising from
* out of or in connection with the software or the use or other dealings in the
* Software.
*
* Except as contained in this notice, the name of the copyright holder shall
* not be used in advertising or otherwise to promote the sale, use or other
* dealings in this Software without prior written authorization from tobozo.
*
*
*/
#include <SPI.h>
#include <Wire.h>
#include <U8g2lib.h>
#define TCAADDR 0x70 // I2C Multiplexer ADDR
#define NUMSPRITES 16
#define XPOS 0
#define YPOS 1
#define DELTAY 2
#define DIR 3
#define SPRITE 4
#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH 16
// pixels matrix, this will be squared and zoomed, keep it a power of 2 or adjust zoomfactor accordingly
#define MATRIX_SIZE 128
bool matrix[MATRIX_SIZE][MATRIX_SIZE];
static int matrixtype = 0;
float zoom = 8;
float zoomfactor = 4;
uint8_t dnum; // screen id
// some variables for the demos
int16_t icons[NUMSPRITES][5];
uint16_t hspritepos = 0;
uint16_t maxhpos = 0;
uint16_t maxvpos = 0;
int center;
float angle = 0;
float anglecos;
float anglesin;
time_t last = millis();
long demoduration = 5000;
// binary logos (Adafruit 01010101 format, for projection in the matrix)
/* adafruit logo */
const uint8_t logo16_glcd_bmp[] = {
B11000000, B00000000,
B11000000, B00000001,
B11000000, B00000001,
B11100000, B00000011,
B11100000, B11110011,
B11111000, B11111110,
B11111111, B01111110,
B10011111, B00110011,
B11111100, B00011111,
B01110000, B00001101,
B10100000, B00011011,
B11100000, B00111111,
B11110000, B00111111,
B11110000, B01111100,
B01110000, B01110000,
B00110000, B00000000,
};
/* smiley */
const uint8_t logo16_glcd2_bmp[] = {
B11100000, B00000111,
B00011000, B00011000,
B00000100, B00100000,
B00000010, B01000000,
B01100010, B01000110,
B01100001, B10000110,
B00000001, B10000000,
B00000001, B10000000,
B00000001, B10000000,
B00001001, B10010000,
B00010001, B10001000,
B11100010, B01000111,
B00000010, B01000000,
B00000100, B00100000,
B00011000, B00011000,
B11100000, B00000111,
};
/* smiley bored */
const uint8_t logo_smiley_bored[] = {
B11100000, B00000111,
B00011000, B00011000,
B00000100, B00100000,
B00000010, B01000000,
B00000010, B01000000,
B01111001, B10011110,
B00000001, B10000000,
B00000001, B10000000,
B00000001, B10000000,
B00000001, B10000000,
B00000001, B10000000,
B11100010, B01000111,
B00000010, B01000000,
B00000100, B00100000,
B00011000, B00011000,
B11100000, B00000111,
};
// xbm logos (exported form Kolourpaint/The Gimp in 1 bit XBM format)
/* skull logo */
const uint8_t hzv_logo[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0xff, 0x03, 0x40, 0x00, 0x00, 0x02,
0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x78, 0x00, 0x00, 0x1e,
0x08, 0x00, 0xfc, 0x11, 0x08, 0x00, 0xfc, 0x11, 0x08, 0x00, 0xfc, 0x11,
0x88, 0x3f, 0xfc, 0x11, 0x88, 0x3f, 0xfc, 0x11, 0x88, 0x3f, 0xfc, 0x11,
0x88, 0x3f, 0xfc, 0x11, 0x08, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x10,
0x08, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x10, 0x78, 0x00, 0x00, 0x1e,
0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x3c, 0x3c, 0x02,
0x40, 0x24, 0x24, 0x02, 0x40, 0x24, 0x24, 0x02, 0x40, 0x24, 0x24, 0x02,
0xc0, 0xe7, 0xe7, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/* -pumpkin */
const uint8_t halloween_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xff, 0xff, 0x01,
0xc0, 0xff, 0xff, 0x07, 0xe0, 0xff, 0xff, 0x0f, 0xf0, 0xff, 0xff, 0x1f,
0xf8, 0xff, 0xff, 0x3f, 0xf8, 0xff, 0xff, 0x3f, 0xfc, 0xff, 0xcf, 0x3f,
0xfc, 0xff, 0xc7, 0x7f, 0xfc, 0xc7, 0xc3, 0x73, 0xfc, 0x87, 0xc1, 0x73,
0xfc, 0x8f, 0xf1, 0x71, 0xfc, 0xff, 0xff, 0x70, 0xfc, 0xff, 0x7f, 0x60,
0x3c, 0xff, 0x3f, 0x60, 0x3c, 0xfe, 0x0f, 0x60, 0x3c, 0x30, 0x00, 0x70,
0x3c, 0x00, 0x00, 0x30, 0x78, 0xf8, 0xff, 0x39, 0x70, 0xfe, 0xff, 0x1f,
0xf0, 0xff, 0xff, 0x1f, 0xe0, 0xff, 0xff, 0x0f, 0x80, 0xff, 0xff, 0x03,
0x00, 0xf8, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Scrolling #defines (from https://github.com/adafruit/Adafruit_SSD1306/blob/master/Adafruit_SSD1306.h#L135)
#define SSD1306_ACTIVATE_SCROLL 0x2F
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR 0x22
enum ssd1306_scroll_frequency {
SSD1306_SCROLLFREQ_5 = 0b000,
SSD1306_SCROLLFREQ_64 = 0b001,
SSD1306_SCROLLFREQ_128 = 0b010,
SSD1306_SCROLLFREQ_256 = 0b011,
SSD1306_SCROLLFREQ_3 = 0b100,
SSD1306_SCROLLFREQ_4 = 0b101,
SSD1306_SCROLLFREQ_25 = 0b110,
SSD1306_SCROLLFREQ_2 = 0b111
};
#define DIR_UP 1
#define DIR_DOWN 2
#define DIR_LEFT 3
#define DIR_RIGHT 4
struct logo {
const uint8_t *binary;
uint8_t width;
uint8_t height;
};
logo logoArray[5] = {
{ logo16_glcd_bmp, 16, 16 },
{ logo16_glcd2_bmp, 16, 16 },
{ logo_smiley_bored, 16, 16 },
{ hzv_logo, 32, 32 },
{ halloween_bits, 32, 32 },
};
struct display {
U8G2 *display;
uint8_t i2cnum;
const u8g2_cb_t *rotation;
uint16_t position;
uint8_t width;
uint8_t height;
};
// all display types, add more if required, give them explicit names, then add them to the OLEDS 'display' array
U8G2_SSD1306_64X48_ER_F_HW_I2C display64x48 (U8G2_R0, U8X8_PIN_NONE);
U8G2_SSD1306_128X64_NONAME_F_HW_I2C display128x64(U8G2_R0, U8X8_PIN_NONE);
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C display128x32(U8G2_R0, U8X8_PIN_NONE);
// how many screens are connected to the TCA I2C Multiplexer, min 2, max 8
#define NUMSCREENS 2
// display array, order matters, NUMSCREENS value matters too :-)
// put them in the same order as on the TCA
// (+) it's okay to mix different screen types as long as they're
// supported by u8g2 and their width/height don't exceed MATRIX_SIZE
display OLEDS[NUMSCREENS] = {
/*{ &type, TCA I2C index, orientation, shared },*/
{ &display128x64, 1, U8G2_R3},
{ &display128x64, 2, U8G2_R3}
/*
{ &display128x32, 3, U8G2_R3 },
{ &display128x32, 4, U8G2_R3 },
{ &display128x32, 5, U8G2_R3 },
{ &display128x32, 6, U8G2_R3 },
{ &display128x32, 7, U8G2_R3 },
{ &display128x32, 0, U8G2_R3 }
*/
};
/* I2C multiplexer controls */
void tcaselect(uint8_t i) {
if (i > 7) return;
Wire.beginTransmission(TCAADDR);
Wire.write(1 << i);
Wire.endTransmission();
}
/* hack hack, because u8g2 does not implement native scrolling on SSD1306 */
void screenScroll(uint8_t dnum, uint8_t scrollDirection, uint8_t start, uint8_t stop, enum ssd1306_scroll_frequency freq) {
switch(scrollDirection) {
case SSD1306_RIGHT_HORIZONTAL_SCROLL:
case SSD1306_LEFT_HORIZONTAL_SCROLL:
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), scrollDirection);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), 0X00);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), start);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), freq);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), stop);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), 0X00);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), 0XFF);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), SSD1306_ACTIVATE_SCROLL);
break;
case SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL:
case SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL:
//u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), SSD1306_SET_VERTICAL_SCROLL_AREA);
//u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), 0X00);
//u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), OLEDS[dnum].height);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), scrollDirection);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), 0X00);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), start);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), freq);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), stop);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), 0X01);
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), SSD1306_ACTIVATE_SCROLL);
break;
default:
// duh
break;
}
}
void screenStopScroll(uint8_t dnum) {
u8x8_cad_SendCmd( OLEDS[dnum].display->getU8x8(), SSD1306_DEACTIVATE_SCROLL);
}
void setMatrixTile() {
logo icon = logoArray[1];
uint16_t iconposx = 0;
uint16_t iconposy = 0;
for(uint8_t ty=0;ty<MATRIX_SIZE;ty+=icon.height) {
for(uint8_t tx=0;tx<MATRIX_SIZE;tx+=icon.width) {
for(uint8_t y=0;y<icon.height*2;y=y+2) {
uint8_t msb = icon.binary[y+1];
uint8_t lsb = icon.binary[y];
for(uint8_t x=0;x<8;x++) {
matrix[x+tx][y/2+ty] = (bool)((msb >> x) & 1);
matrix[x+8+tx][y/2+ty] = (bool)((lsb >> x) & 1);
}
}
}
}
}
void setMatrixGrid(uint8_t depth) {
float matrixwidth = MATRIX_SIZE;
float matrixheight = MATRIX_SIZE;
//Serial.println("Setting Matrix");
for(uint8_t y=0;y<matrixheight;y++) {
bool ystate = y%depth == 1;
for(uint8_t x=0;x<matrixwidth;x++) {
bool xstate = x%depth == 1;
matrix[x][y] = !(ystate ? xstate : !xstate);
}
}
}
void projectMatrix(uint8_t dnum, float zoom, float angle, float width, float height, float offsetx, float offsety) {
float ratio = 1/zoom;
float virtualwidth = (float)MATRIX_SIZE*zoom;
float virtualheight = virtualwidth;//(float)MATRIX_SIZE*zoom;
float virtualcenterx = virtualwidth/2 - offsetx;
float virtualcentery = virtualheight/2 - offsety;
//float vectorx = cos(angle);
//float vectory = sin(angle);
float marginx = (virtualwidth - width) / 2;
float marginy = (virtualheight - height) / 2;
tcaselect(OLEDS[dnum].i2cnum);
for( uint8_t py=0; py<height; py++ ) {
int16_t x1 = marginx;
int16_t y1 = marginy+py;
int16_t x2 = virtualwidth-marginx;
int16_t y2 = marginy+py;
for( uint8_t px=0; px<width; px++ ) {
uint16_t x = round( x1+(x2-x1)*px/width );
uint16_t y = round( y1+(y2-y1)*px/width );
uint16_t nx = (anglecos * (x - virtualcenterx)) + (anglesin * (y - virtualcentery)) + virtualcenterx + offsetx;
uint16_t ny = (anglecos * (y - virtualcentery)) - (anglesin * (x - virtualcenterx)) + virtualcentery + offsety;
uint8_t mx = nx*ratio;
uint8_t my = ny*ratio;
OLEDS[dnum].display->setDrawColor(matrix[mx][my]);
OLEDS[dnum].display->drawPixel(px, py);
}
}
OLEDS[dnum].display->sendBuffer();
}
// native scroll demo
void scrollXBM() {
int16_t scrollIndex = 64;
logo curLogo = logoArray[3];
do {
for(uint8_t i=0;i<NUMSCREENS;i++) {
tcaselect(OLEDS[i].i2cnum);
OLEDS[i].display->clearBuffer();
OLEDS[i].display->setDrawColor(1);
OLEDS[i].display->drawXBM((-OLEDS[i].position % curLogo.width) - scrollIndex , scrollIndex, curLogo.width, curLogo.height, curLogo.binary);
OLEDS[i].display->sendBuffer();
}
scrollIndex-=2;
} while(scrollIndex>1);
scrollIndex = 0;
do {
for(uint8_t i=0;i<NUMSCREENS;i++) {
tcaselect(OLEDS[i].i2cnum);
screenScroll(i, SSD1306_RIGHT_HORIZONTAL_SCROLL, 0x00, 0x0f, SSD1306_SCROLLFREQ_2);
}
delay(50);
for(uint8_t i=0;i<NUMSCREENS;i++) {
tcaselect(OLEDS[i].i2cnum);
screenStopScroll(i);
}
scrollIndex++;
} while(scrollIndex<500);
}
void drawSprites(uint8_t dnum) {
display curDisplay = OLEDS[dnum];
U8G2 *display = curDisplay.display;
tcaselect(curDisplay.i2cnum);
display->clearBuffer();
float w = display->getDisplayWidth();
float h = display->getDisplayHeight();
display->setDrawColor(1);
for (uint8_t f=0; f<NUMSPRITES; f++) {
logo curLogo = logoArray[icons[f][SPRITE]];
if( icons[f][XPOS]+(curLogo.width/2) > curDisplay.position
&& icons[f][XPOS]-(curLogo.width/2) < curDisplay.position + curDisplay.width ) {
display->drawXBM(icons[f][XPOS] - curDisplay.position - (curLogo.width/2), icons[f][YPOS], curLogo.width, curLogo.height, curLogo.binary);
}
}
display->sendBuffer();
}
void drawFloatingSprites() {
// initialize sprites
for (uint8_t f=0; f< NUMSPRITES; f++) {
icons[f][XPOS] = random(maxhpos) - (LOGO16_GLCD_WIDTH/2);
icons[f][YPOS] = random(maxvpos) - (LOGO16_GLCD_HEIGHT/2);
icons[f][DELTAY] = random(5) + 1;
icons[f][DIR] = random(8) - 4;
icons[f][SPRITE] = random(5);
}
long then = millis() + demoduration;
while (millis() < then) {
// draw displays
for (uint8_t f=0; f< NUMSCREENS; f++) {
drawSprites(f);
}
// move sprites
for (uint8_t f=0; f< NUMSPRITES; f++) {
icons[f][YPOS] += icons[f][DELTAY];
icons[f][XPOS] += icons[f][DIR];
if(icons[f][XPOS]>maxhpos) {
icons[f][XPOS] = 0;
}
if(icons[f][XPOS]<0) {
icons[f][XPOS] = maxhpos;
}
// if its gone, reinit
if (icons[f][YPOS] > maxvpos) {
icons[f][XPOS] = random(maxhpos) - (LOGO16_GLCD_WIDTH/2);
icons[f][YPOS] = -15;
icons[f][DELTAY] = random(5) + 1;
}
}
}
}
void setup() {
Serial.begin(115200);
while (!Serial); // Leonardo: wait for serial monitor
Wire.begin();
uint16_t pos = 0;
for(uint8_t i=0;i<NUMSCREENS;i++) {
tcaselect(OLEDS[i].i2cnum);
OLEDS[i].display->begin();
OLEDS[i].display->setDisplayRotation( OLEDS[i].rotation );
OLEDS[i].width = OLEDS[i].display->getDisplayWidth();
OLEDS[i].height = OLEDS[i].display->getDisplayHeight();
OLEDS[i].position = pos;
pos += OLEDS[i].width;
if(OLEDS[i].height > maxvpos) maxvpos = OLEDS[i].height;
/*
for(uint8_t yy=0;yy<OLEDS[i].height;yy++) {
for(uint8_t xx=0;xx<OLEDS[i].width;xx++) {
OLEDS[i].matrix[xx][yy] = false;
}
}*/
Serial.print(i);
Serial.print("\t");
Serial.print(OLEDS[i].width);
Serial.print("\t");
Serial.print(OLEDS[i].height);
Serial.print("\t");
Serial.print(OLEDS[i].position);
Serial.println();
}
maxhpos = pos;
center = maxhpos / 2;
Serial.println("Display Setup Complete");
anglecos = cos(angle);
anglesin = sin(angle);
setMatrixTile();
}
void loop() {
dnum++;
if(dnum>=NUMSCREENS) {
dnum = 0;
angle = angle + 0.1;
if(angle>=2*PI) angle=-2*PI;
anglecos = cos(angle);
anglesin = sin(angle);
}
if(last+demoduration<millis()) {
last = millis();
switch(matrixtype) {
case 0: // matrix rotating square grid demo
setMatrixGrid(2);
matrixtype = 1;
zoom = 24;// + anglesin*16;
zoomfactor = 16;
break;
case 1: // matrix rotating bmp tile demo
setMatrixTile();
matrixtype = 2;
zoom = 4;// + anglesin*2;
zoomfactor = 2;
break;
case 2: // scrolling demo
scrollXBM();
matrixtype = 3;
break;
case 3: // floating sprites demo
drawFloatingSprites();
matrixtype = 0;
break;
}
}
projectMatrix(dnum, zoom + anglesin*zoomfactor, angle, OLEDS[dnum].width, OLEDS[dnum].height, OLEDS[dnum].position - center + OLEDS[dnum].width/2, 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment