Last active
October 6, 2023 13:02
-
-
Save tobozo/6d1ad419823148a638832eaefa79fc63 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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