Repository: frumperino/FrekvensPanel Branch: master Commit: 07ae202ee24b Files: 10 Total size: 20.8 KB Directory structure: gitextract_c8sobvkw/ ├── FrekvensPanel/ │ ├── FrekvensPanel.cpp │ ├── FrekvensPanel.h │ ├── examples/ │ │ ├── buttons/ │ │ │ ├── button.h │ │ │ └── buttons.ino │ │ ├── pika/ │ │ │ ├── bm_pika.h │ │ │ └── pika.ino │ │ └── sparkles/ │ │ └── sparkles.ino │ ├── library.properties │ └── readme.md └── readme.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: FrekvensPanel/FrekvensPanel.cpp ================================================ // By /u/frumperino // goodwires.org #include "FrekvensPanel.h" void init(int p_latch, int p_clock, int p_data, int p_enable); FrekvensPanel::FrekvensPanel(int p_latch, int p_clock, int p_data, int bitDepth, int numPanels) : Adafruit_GFX(16, numPanels * 16) { init(p_latch, p_clock, p_data, bitDepth, numPanels); } FrekvensPanel::FrekvensPanel(int p_latch, int p_clock, int p_data) : FrekvensPanel(p_latch, p_clock, p_data, 0, 1) { } void FrekvensPanel::init(int p_latch, int p_clock, int p_data, int bitDepth, int numPanels) { _pLatch = p_latch; _pClock = p_clock; _pData = p_data; pinMode(_pLatch, OUTPUT); pinMode(_pClock, OUTPUT); pinMode(_pData, OUTPUT); _bitDepth = bitDepth; _numPages = (1 << bitDepth); _pageMask = _numPages - 1; _numPanels = numPanels; _pageStride = 16 * numPanels; _numWords = _numPages * _pageStride; _numPixels = 16 * 16 * numPanels; _addressMask = _numPixels-1; _width = 16; _height = 16 * _numPanels; buf = (unsigned short*) malloc(_numWords * sizeof(unsigned short)); } void FrekvensPanel::clear() { for (int i=0;i<_numWords;i++) { buf[i] = 0; } } void FrekvensPanel::writeDeepPixel(unsigned short x, unsigned short y, unsigned short value) { if (x > 7) { y += 0x10; x &= 0x07; } unsigned int address = (x + (y << 3)) & _addressMask; unsigned short ba = address >> 4; unsigned short br = address & 0x0F; unsigned short ms = (1 << br); unsigned short mc = 0xFFFF ^ ms; unsigned short* wp = &buf[ba]; unsigned short ofs = (x+y) & _pageMask; for (unsigned int i=0;i<_numPages;i++) { ofs++; ofs &= _pageMask; char b = ((value >> ofs) & 0x01); if (b) { *wp |= ms; } else { *wp &= mc; } wp+=_pageStride; } } void FrekvensPanel::scan() { if (_numPages == 1) { // single bit plane unsigned short* p = &buf[0]; for (int i=0;i<_pageStride;i++) { unsigned short w = *p++; for (int j=0;j<16;j++) { digitalWrite(_pData, w & 0x01); w >>= 1; digitalWrite(_pClock, HIGH); delayMicroseconds(1); digitalWrite(_pClock, LOW); } } digitalWrite(_pLatch,HIGH); delayMicroseconds(1); digitalWrite(_pLatch,LOW); } else { // multiple bit planes unsigned short* p = &buf[_pageStride * _activePage]; for (int i=0;i<_pageStride;i++) { unsigned short w = *p++; for (int j=0;j<16;j++) { digitalWrite(_pData, w & 0x01); w >>= 1; digitalWrite(_pClock, HIGH); delayMicroseconds(1); digitalWrite(_pClock, LOW); } } digitalWrite(_pLatch,HIGH); delayMicroseconds(1); digitalWrite(_pLatch,LOW); _activePage++; _activePage %= _numPages; } } unsigned short FrekvensPanel::width() { return this->_width; } unsigned short FrekvensPanel::height() { return this->_height; } boolean FrekvensPanel::getPixel(int16_t x, int16_t y) { if (x > 7) { y += 0x10; x &= 0x07; } unsigned short address = (x + (y << 3)) & _addressMask; unsigned short ba = address >> 4; unsigned short br = address & 0x0F; unsigned short* wp = &buf[ba]; return ((*wp) >> br) & 0x01; } void FrekvensPanel::drawPixel(int16_t x, int16_t y, uint16_t color) { if ((x >= 0) && (y >= 0) && (x < _width) && ( y < _height)) { if (x > 7) { y += 0x10; x &= 0x07; } unsigned short address = (x + (y << 3)) & _addressMask; unsigned short ba = address >> 4; unsigned short br = address & 0x0F; unsigned short ms = (1 << br); unsigned short* wp = &buf[ba]; if (color & 0x01) { *wp |= ms; } else { *wp &= (0xFFFF ^ ms); } } } void FrekvensPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { for (int i=0;i>= 1; unsigned short* p = &buf[i * _pageStride]; for (int j=0;j<_pageStride;j++) { *p++ = w; } } } ================================================ FILE: FrekvensPanel/FrekvensPanel.h ================================================ // by /u/frumperino // goodwires.org #ifndef __FREKVENSPANEL_H #define __FREKVENSPANEL_H #include #include class FrekvensPanel : public Adafruit_GFX { private: unsigned short _numPanels : 4; unsigned short _numPages : 4; unsigned short _activePage : 4; unsigned short _bitDepth : 4; unsigned short _pLatch; unsigned short _pClock; unsigned short _pData; unsigned short* buf; unsigned short _pageMask; unsigned short _addressMask; unsigned short _numWords; unsigned short _pageStride; unsigned short _numPixels; unsigned short _width; unsigned short _height; public: FrekvensPanel(int p_latch, int p_clock, int p_data, int bitDepth, int numPanels); FrekvensPanel(int p_latch, int p_clock, int p_data); void init(int p_latch, int p_clock, int p_data, int bitDepth, int numPanels); void clear(); void scan(); void writeDeepPixel(unsigned short x, unsigned short y, unsigned short value); boolean getPixel(int16_t x, int16_t y); unsigned short width(); unsigned short height(); void drawPixel(int16_t x, int16_t y, uint16_t color) override; void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override; void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override; void fillScreen(uint16_t color) override; private: }; #endif //__FREKVENSPANEL_H ================================================ FILE: FrekvensPanel/examples/buttons/button.h ================================================ // // Created by /u/frumperino // #include #ifndef __simplebutton_h #define __simplebutton_h typedef void (*ButtonEH) (int, int); // id, state class SimpleButton { private: unsigned short pin : 8; unsigned short state : 1; unsigned short invert : 1; unsigned short changing : 1; unsigned short attached : 1; char id; ButtonEH handler = nullptr; unsigned short debounceMS; unsigned long tChange = 0; public: SimpleButton(char id, char pin, char invert, unsigned short debounceMS) { this->id = id; this->invert = invert; this->pin = pin; this->changing = false; this->debounceMS = debounceMS; this->state = digitalRead(pin); this->attached = false; } void attachHandler(ButtonEH handler) { this->handler = handler; this->attached = true; } char getState() { return this->state; } void scan() { char newState = digitalRead(pin); if (newState == state) { changing = false; } else { unsigned long tNow = millis(); if (!changing) { changing = true; tChange = tNow; } else { unsigned long tD = tNow - tChange; if (tD >= debounceMS) { state = newState; changing = false; if (attached) { handler(id, state ^ invert); } } } } } }; #endif //__simplebutton_h ================================================ FILE: FrekvensPanel/examples/buttons/buttons.ino ================================================ #include // see readme.md for wiring instructions #include "button.h" #define buttonCOM_VCC // comment out this line if button COM is wired to GND. #define p_ena 5 #define p_data 6 #define p_clock 9 #define p_latch 10 #define p_btn1 14 // A0 (Adafruit Feather M0) - RED button (black wire) #define p_btn2 15 // A1 (Adafruit Feather M0) - YELLOW button (white wire) FrekvensPanel panel(p_latch, p_clock, p_data); char activeProgram = 1; char activeBrightMode = 1; #ifdef buttonCOM_VCC // the red wire (COM) for the buttons is connected to (MCU board) VCC, // so the button signal is positive and the button input pins must be set to INPUT_PULLDOWN SimpleButton button1(1,p_btn1,0,20); SimpleButton button2(2,p_btn2,0,20); #else // the red wire (COM) for the buttons is connected to GND, so the button signal is inverted SimpleButton button1(1,p_btn1,1,20); SimpleButton button2(2,p_btn2,1,20); #endif void setBrightMode(int brightMode) { activeBrightMode = brightMode % 4; switch(activeBrightMode) { case 0: { pinMode(p_ena, OUTPUT); digitalWrite(p_ena, 1); } break; // off case 1: analogWrite(p_ena, 254); break; // very dim case 2: analogWrite(p_ena, 240); break; // dim case 3: { pinMode(p_ena, OUTPUT); digitalWrite(p_ena, 0); } break; // on 100% } } void setProgram(int program) { activeProgram = (program % 3); } void buttonHandler(int id, int state) { if (state) // button pressed { switch(id) { case 1: setBrightMode(activeBrightMode+1); break; case 2: setProgram(activeProgram+1); break; } } } void program0() { for (int i=0;i<20;i++) { panel.drawPixel(random(16),random(16),random(10) == 0); } } int p1_i = 0; void program1() { float pi23 = PI*2/3; float p = 0.02 * p1_i++; int x0 = 7.98 + 7.98 * cos(p); int y0 = 7.98 + 7.98 * sin(p); p += pi23; int x1 = 7.98 + 7.98 * cos(p); int y1 = 7.98 + 7.98 * sin(p); p += pi23; int x2 = 7.98 + 7.98 * cos(p); int y2 = 7.98 + 7.98 * sin(p); panel.clear(); panel.drawLine(x0,y0,x1,y1,1); panel.drawLine(x1,y1,x2,y2,1); panel.drawLine(x2,y2,x0,y0,1); } void program2() { int x = random(16); int y = random(16); panel.drawRect(x,y,random(16-x),random(16-y),random(6)==0); } void setup() { #ifdef buttonCOM_VCC pinMode(p_btn1, INPUT_PULLDOWN); pinMode(p_btn2, INPUT_PULLDOWN); #else pinMode(p_btn1, INPUT_PULLUP); pinMode(p_btn2, INPUT_PULLUP); #endif panel.clear(); pinMode(p_ena, OUTPUT); analogWrite(p_ena, 0); // full brightness button1.attachHandler(buttonHandler); button2.attachHandler(buttonHandler); panel.scan(); } void loop() { switch(activeProgram) { case 0: program0(); break; case 1: program1(); break; case 2: program2(); break; } panel.scan(); button1.scan(); button2.scan(); delay(2); } ================================================ FILE: FrekvensPanel/examples/pika/bm_pika.h ================================================ // // Created by /u/fumperino on 2020-02-15. // #ifndef __bm_pika_h #define __bm_pika_h static const unsigned short bm_pika_width = 56; static const unsigned short bm_pika_height = 56; static const unsigned char bm_pika[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row 00 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row 01 0xFF, 0xBF, 0x60, 0xDF, 0xBF, 0xC7, 0xFF, // row 02 0xFF, 0xBF, 0x6F, 0xDF, 0xBF, 0xB9, 0xFF, // row 03 0xFF, 0xBF, 0x6F, 0xDF, 0xBF, 0x7D, 0xFF, // row 04 0xFF, 0xBF, 0x6F, 0xDF, 0xBE, 0xFE, 0xFF, // row 05 0xFF, 0xBF, 0x6F, 0xDF, 0xBE, 0xFE, 0xFF, // row 06 0xFF, 0x80, 0x60, 0xDF, 0xBE, 0xFE, 0xFF, // row 07 0xFF, 0xBF, 0x6F, 0xDF, 0xBE, 0xFE, 0xFF, // row 08 0xFF, 0xBF, 0x6F, 0xDF, 0xBE, 0xFE, 0xFF, // row 09 0xFF, 0xBF, 0x6F, 0xDF, 0xBF, 0x7D, 0xFF, // row 10 0xFF, 0xBF, 0x6F, 0xDF, 0xBF, 0x3B, 0xFF, // row 11 0xFF, 0xBF, 0x60, 0x40, 0x81, 0xC7, 0xFF, // row 12 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row 13 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, // row 14 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFF, // row 15 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFF, // row 16 0xFF, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF, // row 17 0xF8, 0x7F, 0xF8, 0xBF, 0xFF, 0x83, 0xFF, // row 18 0xF7, 0xBF, 0xFB, 0xBF, 0xFC, 0x43, 0xFF, // row 19 0xEF, 0xDF, 0xF3, 0xBF, 0xF3, 0xC7, 0xFF, // row 20 0xEF, 0xDF, 0xF7, 0xC1, 0xCF, 0xCF, 0xFF, // row 21 0xEF, 0xDF, 0xF7, 0x3E, 0x3F, 0x9F, 0xFF, // row 22 0xE0, 0x1F, 0xF5, 0xFF, 0xFF, 0xBE, 0x7F, // row 23 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0x7D, 0xBF, // row 24 0xFF, 0xDF, 0xEF, 0xFF, 0xFE, 0xFD, 0xCF, // row 25 0xFF, 0xDF, 0xE7, 0xF9, 0xFD, 0xFB, 0xF7, // row 26 0xFF, 0xDF, 0xD7, 0xF4, 0xF3, 0xFB, 0xFB, // row 27 0xE0, 0x1F, 0xC7, 0xF0, 0xFB, 0xF7, 0xFB, // row 28 0xFF, 0xFF, 0xAD, 0xF9, 0xFB, 0xF7, 0xFB, // row 29 0xF1, 0x9F, 0xBF, 0xFF, 0x3D, 0xEF, 0xF7, // row 30 0xEE, 0x7F, 0x9A, 0x6E, 0x1D, 0xEF, 0xEF, // row 31 0xEE, 0xFF, 0x9C, 0x1E, 0x1E, 0xDF, 0xDF, // row 32 0xEE, 0xFF, 0x9E, 0x3F, 0x1E, 0xDF, 0xBF, // row 33 0xE0, 0x1E, 0x5E, 0x7F, 0xAE, 0x5F, 0x7F, // row 34 0xFF, 0xFD, 0xEF, 0xFE, 0x74, 0x6F, 0x3F, // row 35 0xFF, 0xFD, 0xF7, 0xFF, 0xF6, 0x77, 0xDF, // row 36 0xF8, 0x7E, 0xFF, 0xFF, 0xEF, 0xB7, 0x9F, // row 37 0xF7, 0xBF, 0x7F, 0xFF, 0xFF, 0xAF, 0x7F, // row 38 0xEF, 0xDF, 0x9F, 0xFF, 0x8E, 0x9C, 0xFF, // row 39 0xEF, 0xDF, 0xEF, 0xFD, 0x74, 0x2D, 0xFF, // row 40 0xEF, 0xDF, 0xE7, 0xFA, 0xBE, 0x0E, 0xFF, // row 41 0xF7, 0xBF, 0xF7, 0xFB, 0xBF, 0x89, 0xFF, // row 42 0xF8, 0x7F, 0xF7, 0xFB, 0xDF, 0x47, 0xFF, // row 43 0xE7, 0xFF, 0xF7, 0xFD, 0xDF, 0x1F, 0xFF, // row 44 0xF8, 0x7F, 0xFB, 0xFD, 0xDE, 0x7F, 0xFF, // row 45 0xFF, 0x9F, 0xFB, 0xFD, 0xBD, 0xFF, 0xFF, // row 46 0xF8, 0x3F, 0xFD, 0xFE, 0x7B, 0xFF, 0xFF, // row 47 0xE7, 0xFF, 0xFE, 0x7F, 0xC7, 0xFF, 0xFF, // row 48 0xF8, 0x7F, 0xFF, 0x9F, 0x3F, 0xFF, 0xFF, // row 49 0xFF, 0x9F, 0xFF, 0xE4, 0x7F, 0xFF, 0xFF, // row 50 0xF8, 0x3F, 0xFF, 0xF7, 0x7F, 0xFF, 0xFF, // row 51 0xE7, 0xFF, 0xFF, 0xF5, 0x7F, 0xFF, 0xFF, // row 52 0xFF, 0xFF, 0xFF, 0xFA, 0xFF, 0xFF, 0xFF, // row 53 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row 54 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // row 55 }; #endif // __bm_pika_h ================================================ FILE: FrekvensPanel/examples/pika/pika.ino ================================================ #include // see readme.md for wiring instructions #include "bm_pika.h" #define p_ena 5 #define p_data 6 #define p_clock 9 #define p_latch 10 #define p_btn1 14 // A0 (Adafruit Feather M0) - RED button (black wire) #define p_btn2 15 // A1 (Adafruit Feather M0) - YELLOW button (white wire) FrekvensPanel panel(p_latch, p_clock, p_data); int i = 0; void setup() { panel.clear(); panel.scan(); pinMode(p_ena, OUTPUT); analogWrite(p_ena, 0); // full brightness } void loop() { panel.fillScreen(1); int pw = panel.width(); int ph = panel.height(); int bw = bm_pika_width; int bh = bm_pika_height; float p = (0.002 * i++); float bw2 = (float) (bw - pw) / 2; float bh2 = (float) (bh - ph) / 2; int x = (int) (bw2 + bw2 * cos(p * 3)) - bw + pw; int y = (int) (bh2 + bh2 * sin(p * 4)) - bh + ph; panel.drawBitmap(x,y,&bm_pika[0],bw,bh,0); panel.scan(); } ================================================ FILE: FrekvensPanel/examples/sparkles/sparkles.ino ================================================ #include // see readme.md for wiring instructions #define p_ena 5 #define p_data 6 #define p_clock 9 #define p_latch 10 #define p_btn1 14 // A0 (Adafruit Feather M0) - RED button (black wire) #define p_btn2 15 // A1 (Adafruit Feather M0) - YELLOW button (white wire) FrekvensPanel panel(p_latch, p_clock, p_data); void setup() { panel.clear(); panel.scan(); pinMode(p_ena, OUTPUT); analogWrite(p_ena, 0); // full brightness } void loop() { for (int i=0;i<20;i++) { panel.drawPixel(random(16),random(16),random(10) == 0); } panel.scan(); // refreshes display delay(5); } ================================================ FILE: FrekvensPanel/library.properties ================================================ name=FrekvensPanel version=0.0.1 author=/u/frumperino maintainer=/u/frumperino sentence=Frekvens LED panel driver paragraph=Use an Arduino to drive the IKEA Frekvens LED panel. category=Display includes=FrekvensPanel.h architectures=* url=https://github.com/frumperino/FrekvensPanel ================================================ FILE: FrekvensPanel/readme.md ================================================ This is a first rough cut of an Arduino library for the IKEA FREKVENS LED panels developed jointly with Teenage Engineering. Contributions welcome. Arduino requirements: Must be type with 3.3V logic levels. If you want to try hooking this up through 8-bit 5V AVR antiques like an Arduino Uno, then you must use level shifters. Recommendation: Use Teensy 3+, Adafruit Feather M0 or Sparkfun SAMD21 mini breakouts. Should also work with 32U4 based boards as well, so long as they're 3.3V. Haven't tried with ESP8266 yet. Library dependencies: - Adafruit GFX library must be installed. Soldering instructions: - Open & disassemble the LED cube. - Extract the mainboard. - Desolder the original controller daughterboard (green color). * Use flux and solder wick. - Attach wires to the six solder points. * The outermost solder points are marked VCC and GND. * Let GND be (1) and VCC be (6). The middle four are as follows: * (2) LATCH - connect to a digital output pin. * (3) CLOCK - connect to a digital output pin. * (4) DATA - connect to a digital output pin. * (5) !ENABLE. This one is optional. If you DON'T need to be able to control brightness of the display, then just solder a wire from this pin to GND. If you DO want to control brightness, then connect this pin to a PWM ("analog") output pin on your Arduino. * Solder the GND wire to a GND pin on the Arduino. * Leave the VCC wire unconnected until you're done programming * The VCC wire has 4 volts on it coming from the isolated power * supply of the Frekvens cube. * While programming, the Arduino should be powered through USB. * After programming, disconnect USB cable and attach VCC wire * from Frekvens panel to battery input or "USB" voltage pin on * the Arduino. The Frekvens panel will now supply the Arduino * board voltages. - Optionally - hook up the red and yellow buttons on the back. * RED wire is common so connect this to GND or VCC of the * Arduino. BLACK wire is RED button, WHITE wire is YELLOW button. - Put the whole thing back together. - To use the library, add #include "Frekvenspanel.h" statement, and instantiate a FrekvensPanel object. See examples. - Library has several un-documented experimental modes, including multiple bit planes (intensity dithering) and support for daisy- chaining several modules. Needs more work. At the moment the daisy- chain is mapped vertically which seems wrong. - Happy hacking ================================================ FILE: readme.md ================================================ This is a first rough cut of an Arduino library for the IKEA FREKVENS LED panels developed jointly with Teenage Engineering. Contributions welcome. Arduino requirements: Must be type with 3.3V logic levels. If you want to try hooking this up through 8-bit 5V AVR antiques like an Arduino Uno, then you must use level shifters. Recommendation: Use Teensy 3+, Adafruit Feather M0 or Sparkfun SAMD21 mini breakouts. Should also work with 32U4 based boards as well, so long as they're 3.3V. Haven't tried with ESP8266 yet. Library dependencies: - Adafruit GFX library must be installed. Soldering instructions: - Open & disassemble the LED cube. - Extract the mainboard. - Desolder the original controller daughterboard (green color). * Use flux and solder wick. - Attach wires to the six solder points. * The outermost solder points are marked VCC and GND. * Let GND be (1) and VCC be (6). The middle four are as follows: * (2) LATCH - connect to a digital output pin. * (3) CLOCK - connect to a digital output pin. * (4) DATA - connect to a digital output pin. * (5) !ENABLE. This one is optional. If you DON'T need to be able to control brightness of the display, then just solder a wire from this pin to GND. If you DO want to control brightness, then connect this pin to a PWM ("analog") output pin on your Arduino. * Solder the GND wire to a GND pin on the Arduino. * Leave the VCC wire unconnected until you're done programming * The VCC wire has 4 volts on it coming from the isolated power * supply of the Frekvens cube. * While programming, the Arduino should be powered through USB. * After programming, disconnect USB cable and attach VCC wire * from Frekvens panel to battery input or "USB" voltage pin on * the Arduino. The Frekvens panel will now supply the Arduino * board voltages. - Optionally - hook up the red and yellow buttons on the back. * RED wire is common so connect this to GND or VCC of the * Arduino. BLACK wire is RED button, WHITE wire is YELLOW button. - Put the whole thing back together. - To use the library, add #include "Frekvenspanel.h" statement, and instantiate a FrekvensPanel object. See examples. - Library has several un-documented experimental modes, including multiple bit planes (intensity dithering) and support for daisy- chaining several modules. Needs more work. At the moment the daisy- chain is mapped vertically which seems wrong. - Happy hacking