aktueller experimenteller Zwischenstand
/* Belegung Mega2560
CIPO = 50 unbenutzt (früher MISO)
DATA = COPI = 51 Pin von Hardware-SPI (früher MOSI)
CLOCK = SCA = 52 Pin von Hardware-SPI
LATCH = CS = 53 Chip Select Pin
OE = 49 Output Enable, frei wählbarer Pin
*/
/* Usecase by noiasca 2025-11-16 3870/218 in tlc.h */
#define agmue
auto &SerMon = Serial;
auto &MidiA = Serial1;
auto &MidiB = Serial2;
#include <Wire.h>
// EXPANDER (PCF8575)----------------------------------------------------------------------------------------------
constexpr uint8_t inputPins{ 16 }; // Anzahl Pins am PCF8575
constexpr uint8_t kOff{ 143 }; // Kanaloffset
constexpr uint32_t pcfMessIntervall{ 5000 }; // in µs
uint32_t pcfVorhin;
#include "ausgabe.h"
#include "gruppen.h"
#include "tlc.h"
//
uint16_t read16(uint8_t adresse) {
const uint8_t anzahl = 2;
Wire.requestFrom(adresse, anzahl);
uint16_t wert = Wire.read() | (Wire.read() << 8);
return ~wert; // Taster schalten nach GND
}
//
class PCF_Board {
const uint8_t i2cAdd;
const uint8_t pcfNum;
uint16_t wertAlt = 0;
public:
PCF_Board(const uint8_t i2cAdd, const uint8_t pcfNum)
: i2cAdd(i2cAdd), pcfNum(pcfNum){};
void init() {
wertAlt = read16(i2cAdd);
}
//
void auswertung() {
uint16_t wert = read16(i2cAdd);
uint16_t diff = wertAlt ^ wert;
wertAlt = wert;
if (diff) {
for (byte j = 0; j < inputPins; j++) {
if (bitRead(diff, j)) {
byte idx = j + pcfNum * inputPins;
if (taste[5].longpress()) {
if ( (idx == 40) || (idx == 48) || (idx == 80) ) {
grp[pcfTaste[idx].grpIDX].mode = 'A';
} else if ( (idx == 41) || (idx == 49) || (idx == 81) ) {
grp[pcfTaste[idx].grpIDX].mode = 'B';
} else if ( (idx == 56) || (idx == 64) ) {
grp[pcfTaste[idx].grpIDX].mode = 'A';
if (grp[pcfTaste[56].grpIDX].mode == 'C') grp[pcfTaste[56].grpIDX].mode = 'A';
if (grp[pcfTaste[64].grpIDX].mode == 'C') grp[pcfTaste[64].grpIDX].mode = 'A';
} else if ( (idx == 57) || (idx == 65) ) {
grp[pcfTaste[idx].grpIDX].mode = 'B';
if (grp[pcfTaste[57].grpIDX].mode == 'C') grp[pcfTaste[57].grpIDX].mode = 'B';
if (grp[pcfTaste[65].grpIDX].mode == 'C') grp[pcfTaste[65].grpIDX].mode = 'B';
} else if ( (idx == 58) || (idx == 66) ) {
grp[pcfTaste[58].grpIDX].mode = 'C';
grp[pcfTaste[66].grpIDX].mode = 'C';
}
SerMon.print("longPress idx "); SerMon.print(idx); SerMon.print("\tgrpIDX "); SerMon.print(pcfTaste[idx].grpIDX); SerMon.print("\tMode "); SerMon.println(grp[pcfTaste[idx].grpIDX].mode);
} else {
if (bitRead(wert, j)) {
printMidiTaste(pcfTaste[idx].midiAKanal, pcfTaste[idx].midiANote, pcfTaste[idx].midiBKanal, pcfTaste[idx].midiBNote, 127);
outMidiTaste(pcfTaste[idx].midiAKanal, pcfTaste[idx].midiANote, pcfTaste[idx].midiBKanal, pcfTaste[idx].midiBNote, 127);
SerMon.print("\tMode "); SerMon.println(grp[pcfTaste[idx].grpIDX].mode);
if (grp[pcfTaste[idx].grpIDX].mode == 'C') {
for (uint8_t j = grp[1].ersteLED; j <= grp[2].letzteLED; j++) {
tlc.off(j);
}
} else if (grp[pcfTaste[idx].grpIDX].mode == 'A') {
for (uint8_t j = grp[pcfTaste[idx].grpIDX].ersteLED; j <= grp[pcfTaste[idx].grpIDX].letzteLED; j++) {
tlc.off(j);
}
}
tlc.toggle(pcfTaste[idx].led);
} else {
printMidiTaste(pcfTaste[idx].midiAKanal, pcfTaste[idx].midiANote, pcfTaste[idx].midiBKanal, pcfTaste[idx].midiBNote, 0);
outMidiTaste(pcfTaste[idx].midiAKanal, pcfTaste[idx].midiANote, pcfTaste[idx].midiBKanal, pcfTaste[idx].midiBNote, 0);
if (grp[pcfTaste[idx].grpIDX].mode == 'D') tlc.off(pcfTaste[idx].led);
}
}
}
}
tlc.update();
}
}
};
#ifdef agmue
PCF_Board pcfBoard[]{ { 0x3A, 0 }, { 0x3B, 1 }, { 0x3C, 2 }, { 0x20, 3 }, { 0x21, 4 }, { 0x38, 5 } };
#else
PCF_Board pcfBoard[]{ { 0x20, 0 }, { 0x21, 1 }, { 0x22, 2 }, { 0x23, 3 }, { 0x24, 4 }, { 0x25, 5 } };
#endif
//
void loop() {
for (Taste &t : taste) t.update();
uint32_t jetzt = micros();
if (jetzt - pcfVorhin >= pcfMessIntervall) {
pcfVorhin = jetzt;
for (PCF_Board &p : pcfBoard) p.auswertung();
}
}
//
void setup() {
SerMon.begin(115200); // Texte und Upload
MidiA.begin(38400); // MIDI HairlessMIDI mit 38400, Standard MIDI mit 31250
MidiB.begin(38400); // MIDI HairlessMIDI mit 38400, Standard MIDI mit 31250
delay(1000);
Wire.begin();
Wire.setClock(400000);
for (Taste &t : taste) t.init();
for (PCF_Board &p : pcfBoard) p.init();
SPI.begin();
tlc.start();
tlc.update();
pinMode(pinOE, OUTPUT);
digitalWrite(pinOE, LOW);
SerMon.println("\nStart ...");
}
gruppen.h:
struct Taste {
const uint8_t pin; // Pin am Mega2560
const uint8_t midiAKanal; // MIDI A Kanal NOTE ON auf KANAL1 =1 Kanal2 =2 etc.
const uint8_t midiANote; // MIDI A Note 0-127
const uint8_t midiBKanal; // MIDI B Kanal
const uint8_t midiBNote; // MIDI B Note
uint32_t vorhin; // gemerkte Zeit
bool pressState; // long == true
bool aktZustand;
bool altZustand;
uint8_t schritt;
Taste(const uint8_t pin, const uint8_t midiAKanal, const uint8_t midiANote, const uint8_t midiBKanal, const uint8_t midiBNote)
: pin(pin), midiAKanal(midiAKanal), midiANote(midiANote), midiBKanal(midiBKanal), midiBNote(midiBNote), vorhin(0), pressState(0), aktZustand(0), schritt(0){};
void init() {
pinMode(pin, INPUT_PULLUP);
aktZustand = digitalRead(pin);
altZustand = aktZustand;
}
bool longpress() {
bool tmp = pressState;
pressState = false;
return tmp;
}
void update() {
aktZustand = digitalRead(pin);
switch (schritt) {
case 0:
if ((aktZustand != altZustand) & (!aktZustand)) {
vorhin = millis();
pressState = false;
schritt++;
}
break;
case 1:
if (millis() - vorhin >= 500) {
if (aktZustand) {
schritt = 3; // short
} else {
schritt++;
}
}
break;
case 2:
if (aktZustand) {
schritt = 3; // short
} else {
if (millis() - vorhin >= 1000) {
pressState = true;
schritt = 3;
}
}
break;
case 3:
if (millis() - vorhin >= 2000) {
schritt = 0;
}
break;
}
altZustand = aktZustand;
}
};
Taste taste[]{
// Mega2560
//pin, MIDI NoteA, MIDI NoteB
{ 22, 2, 60, 1, 0 }, // GROUP A Helligkeit LED - E2.7
{ 23, 2, 61, 1, 1 }, // GROUP B Helligkeit LED +
{ 24, 2, 62, 1, 2 }, // GROUP C
{ 25, 2, 63, 1, 3 }, // GROUP D
{ 26, 2, 64, 1, 4 }, // GROUP ALL
{ 27, 2, 65, 1, 5 }, // GROUP ML1 LONG für den MODE Wechsel
{ 28, 2, 66, 1, 6 }, // GROUP ML2
{ 29, 2, 67, 1, 7 }, // GROUP FLOOD
};
struct Gruppe {
const uint8_t ersteLED; // erste LED der Gruppe
const uint8_t letzteLED; // letzte LED der Gruppe
char mode;
};
// A = Einer von der Gruppe B= alle von Gruppe
Gruppe grp[] {
{99, 99, 'B'}, // 0: ohne Gruppe
{ 0, 13, 'D'}, // 1: Gruppe O
{14, 21, 'B'}, // 2: Gruppe N
{24, 35, 'B'}, // 3: Gruppe Chaser
{36, 41, 'A'}, // 4: Gruppe POSITIONS
{42, 47, 'A'}, // 5: Gruppe M
};
struct PCF_Taste {
const uint8_t led; // LED-Nummer 0 bis 47
const uint8_t grpIDX; // Gruppenindex
const uint8_t midiAKanal; // MIDI A Kanal NOTE ON auf KANAL1 =1 Kanal2 =2 etc.
const uint8_t midiANote; // MIDI A Note 0-127
const uint8_t midiBKanal; // MIDI B Kanal
const uint8_t midiBNote; // MIDI B Note
};
PCF_Taste pcfTaste[]{
// PCF EXPANDER A
//LED\GRP\MIDI NoteA\MIDI NoteB idx
{ 255, 0, 2, 5, 1, 0 }, // P00 01 COLOR WIPE 1 a6 MP S10
{ 255, 0, 2, 4, 1, 1 }, // P01 02 COLOR UV a5
{ 255, 0, 2, 11, 1, 2 }, // P02 03 COLOR WIPE 2 b6
{ 255, 0, 2, 10, 1, 3 }, // P03 04 COLOR CHASE b5
{ 255, 0, 2, 3, 1, 4 }, // P04 05 COLOR BLUE a4
{ 255, 0, 2, 9, 1, 5 }, // P05 06 COLOR PINK b4
{ 255, 0, 2, 2, 1, 6 }, // P06 07 COLOR GREEN a3
{ 255, 0, 2, 8, 1, 7 }, // P07 08 COLOR CYAN b3
{ 255, 0, 2, 1, 1, 8 }, // P10 09 COLOR RED a2
{ 255, 0, 2, 0, 1, 9 }, // P11 10 COLOR WHITE a1
{ 255, 0, 2, 7, 1, 10 }, // P12 11 COLOR ORANGE b2
{ 255, 0, 2, 6, 1, 11 }, // P13 12 COLOR YELLOW b1
{ 255, 0, 2, 34, 1, 34 }, // P14 13 FLASH 17 MP S11
{ 255, 0, 2, 35, 1, 35 }, // P15 14 FLASH 18
{ 255, 0, 0, 0, 0, 0 }, // unbenutzt
{ 255, 0, 0, 0, 0, 0 }, // unbenutzt
// PCF EXPANDER B
//LED\GRP\MIDI NoteA\MIDI NoteB
{ 255, 0, 2, 18, 1, 18 }, // P00 16 FLASH 1 MP S11
{ 255, 0, 2, 19, 1, 19 }, // P01 17 FLASH 2
{ 255, 0, 2, 20, 1, 20 }, // P02 18 FLASH 3
{ 255, 0, 2, 21, 1, 21 }, // P03 19 FLASH 4
{ 255, 0, 2, 22, 1, 22 }, // P04 20 FLASH 5
{ 255, 0, 2, 23, 1, 23 }, // P05 21 FLASH 6
{ 255, 0, 2, 24, 1, 24 }, // P06 22 FLASH 7
{ 255, 0, 2, 25, 1, 25 }, // P07 23 FLASH 8
{ 255, 0, 2, 26, 1, 26 }, // P10 24 FLASH 9
{ 255, 0, 2, 27, 1, 27 }, // P11 25 FLASH 10
{ 255, 0, 2, 28, 1, 28 }, // P12 26 FLASH 11
{ 255, 0, 2, 29, 1, 29 }, // P13 27 FLASH 12
{ 255, 0, 2, 30, 1, 30 }, // P14 28 FLASH 13
{ 255, 0, 2, 31, 1, 31 }, // P15 29 FLASH 14
{ 255, 0, 2, 32, 1, 32 }, // P16 30 FLASH 15
{ 255, 0, 2, 33, 1, 33 }, // P17 31 FLASH 16
// PCF EXPANDER C PIN idx GRP MODE LED
//LED\GRP\MIDI NoteA\MIDI NoteB
{ 255, 0, 2, 12, 1, 53 }, // P00 32 GOBO OPEN MP S10.13
{ 255, 0, 2, 13, 1, 53 }, // P01 33 GOBO I
{ 255, 0, 2, 14, 1, 53 }, // P02 34 GOBO II
{ 255, 0, 2, 15, 1, 53 }, // P03 35 GOBO III
{ 255, 0, 2, 16, 1, 53 }, // P04 36 GOBO IV
{ 255, 0, 2, 17, 1, 53 }, // P05 37 GOBO V
{ 255, 0, 2, 53, 1, 53 }, // P06 38 GOBO VI
{ 255, 0, 2, 53, 1, 53 }, // P07 39 GOBO Wheel rotation
{ 36, 4, 2, 66, 1, 53 }, // P10 40 POSITIONS I MODE A 36 MP E2.13
{ 37, 4, 2, 67, 1, 53 }, // P11 41 POSITIONS II MODE B 37
{ 38, 4, 2, 68, 1, 53 }, // P12 42 POSITIONS III 38
{ 39, 4, 2, 69, 1, 53 }, // P13 43 POSITIONS IV 39
{ 40, 4, 2, 70, 1, 53 }, // P14 44 POSITIONS V 40
{ 41, 4, 2, 71, 1, 53 }, // P15 45 POSITIONS VI 41
{ 255, 0, 2, 98, 1, 53 }, // P16 46 TEMPO TAP MP BLACKOUT
{ 255, 0, 2, 53, 1, 53 }, // P17 47 Playlight Reset
// PCF EXPANDER D PIN GRP MODE LED
//LED\GRP\MIDI NoteA\MIDI NoteB
{ 42, 5, 2, 72, 1, 53 }, // P00 48 M1 MODE A 42 MP Evironment Chaser
{ 43, 5, 2, 73, 1, 53 }, // P01 49 M2 MODE B 43
{ 44, 5, 2, 74, 1, 53 }, // P02 50 M3 44
{ 45, 5, 2, 75, 1, 53 }, // P03 51 M4 45
{ 46, 5, 2, 76, 1, 53 }, // P04 52 M5 46
{ 47, 5, 2, 77, 1, 53 }, // P05 53 M6 47
{ 255, 0, 2, 53, 1, 53 }, // P06 54 PRELOAD PWM 2(MEGA)
{ 22, 0, 2, 35, 1, 53 }, // P07 55 STROBE 22
{ 14, 2, 2, 42, 1, 53 }, // P10 56 N 1 MODE A 14 MP E1.7
{ 15, 2, 2, 43, 1, 53 }, // P11 57 N 2 MODE B 15
{ 16, 2, 2, 44, 1, 53 }, // P12 58 N 3 MODE C 16
{ 17, 2, 2, 45, 1, 53 }, // P13 59 N 4 17
{ 18, 2, 2, 46, 1, 53 }, // P14 60 N 5 18
{ 19, 2, 2, 47, 1, 53 }, // P15 61 N 6 19
{ 20, 2, 2, 48, 1, 53 }, // P16 62 N 7 20
{ 21, 2, 2, 49, 1, 53 }, // P17 63 N 8 21
// PCF EXPANDER E PIN GRP MODE LED
//LED\GRP\MIDI NoteA\MIDI NoteB
{ 0, 1, 2, 54, 1, 53 }, // P00 64 O 1 MODE A 0 MP E2
{ 1, 1, 2, 55, 1, 53 }, // P01 65 O 2 MODE B 1
{ 2, 1, 2, 56, 1, 44 }, // P02 66 O 3 MODE C 2
{ 3, 1, 2, 57, 1, 45 }, // P03 67 O 4 3
{ 4, 1, 2, 58, 1, 46 }, // P04 68 O 5 4
{ 5, 1, 2, 59, 1, 47 }, // P05 69 O 6 5
{ 6, 1, 2, 60, 1, 10 }, // P06 70 O 7 6
{ 7, 1, 2, 61, 1, 11 }, // P07 71 O 8 7
{ 8, 1, 2, 62, 1, 27 }, // P10 72 O 9 8
{ 9, 1, 2, 63, 1, 28 }, // P11 73 O 10 9
{ 10, 1, 2, 64, 1, 32 }, // P12 74 O 11 10
{ 11, 1, 2, 65, 1, 27 }, // P13 75 O 12 11
{ 12, 1, 2, 66, 1, 35 }, // P14 76 O 13 12
{ 13, 1, 2, 67, 1, 58 }, // P15 77 O 14 13
{ 255, 0, 0, 0, 0, 0 }, // unbenutzt(78)
{ 255, 0, 0, 0, 0, 0 }, // unbenutzt (79)
// PCF EXPANDER F PIN idx GRP MODE LED
//LED\GRP\MIDI NoteA\MIDI NoteB
{ 24, 3, 2, 78, 1, 0 }, // P00 80 Chaser 1 MODE A 24 MP E3
{ 25, 3, 2, 79, 1, 1 }, // P01 81 Chaser 2 MODE B 25
{ 26, 3, 2, 80, 1, 2 }, // P02 82 Chaser 3 26
{ 27, 3, 2, 81, 1, 3 }, // P03 83 Chaser 4 27
{ 28, 3, 2, 82, 1, 4 }, // P04 84 Chaser 5 28
{ 29, 3, 2, 83, 1, 5 }, // P05 85 Chaser 6 29
{ 30, 3, 2, 84, 1, 6 }, // P06 86 Chaser 7 30
{ 31, 3, 2, 85, 1, 7 }, // P07 87 Chaser 8 31
{ 32, 3, 2, 86, 1, 8 }, // P10 88 Chaser 9 32
{ 33, 3, 2, 87, 1, 9 }, // P11 89 Chaser 10 33
{ 34, 3, 2, 88, 1, 10 }, // P12 90 Chaser 11 34
{ 35, 3, 2, 89, 1, 11 }, // P13 91 Chaser 12(OFF) 35
{ 255, 0, 0, 0, 0, 0 }, // unbenutzt
{ 255, 0, 0, 0, 0, 0 }, // unbenutzt
{ 255, 0, 0, 0, 0, 0 }, // unbenutzt
{ 255, 0, 0, 0, 0, 0 }, // unbenutzt
};
ausgabe.h (neu):
void outMidiTaste(const uint8_t midiAKanal, const uint8_t midiANote, const uint8_t midiBKanal, const uint8_t midiBNote, const byte val) {
MidiA.write(kOff + midiAKanal);
MidiA.write(midiANote);
MidiA.write(val);
MidiB.write(kOff + midiBKanal);
MidiB.write(midiBNote);
MidiB.write(val);
}
//
void printMidiTaste(const uint8_t midiAKanal, const uint8_t midiANote, const uint8_t midiBKanal, const uint8_t midiBNote, const byte val) {
SerMon.print(kOff + midiAKanal);
SerMon.print('\t');
SerMon.print(midiANote);
SerMon.print('\t');
SerMon.print(val);
SerMon.print('\t');
SerMon.print(kOff + midiBKanal);
SerMon.print('\t');
SerMon.print(midiBNote);
SerMon.print('\t');
SerMon.println(val);
}
tlc.h (unverändert):
#include <TLC5947_SPI.hpp>
constexpr uint8_t DEVICES{ 2 }; // Anzahl der angeschlossenen Platinen mit je 24 LEDs.
constexpr uint8_t pinLATCH{ 53 }; // Pin für die Datenübernahme.
constexpr uint8_t pinOE{ 49 }; // Pin für die Aktivierung der LED-Treiber (O̲utput E̲nable).
class MyTLC : public TLC5947_SPI {
protected:
static const uint8_t numPWM = 4;
const uint16_t valuesPWM[numPWM] = { 50, 200, 1000, 4095 };
uint8_t stepPWM = numPWM - 3;
uint16_t offPWM = 0; // PWM for off state. When you set this value >0 the LED will be slightly dimmed also in off state.
public:
MyTLC(const uint8_t latch, const uint8_t deviceCount = 1, const uint32_t SPIclock = 8000000UL, SPIClass &spi = SPI)
: TLC5947_SPI(latch, deviceCount, SPIclock, spi) {}
// call this function in setup().
// it can't be called .begin() as there is already a .begin() in the parent class but the parent class has no late binding (not virtual), hence we name it .start()
void start() {
begin(); // the parent class needs a .begin()
setAll(offPWM);
}
// switch on
void on(const uint8_t channel) {
setPWM(channel, valuesPWM[stepPWM]);
}
// switch off
void off(const uint8_t channel) {
setPWM(channel, offPWM);
}
// toggle
void toggle(const uint8_t channel) {
if (channel < (DEVICES * 24)) {
if (getPWM(channel) != offPWM) {
off(channel);
} else {
on(channel);
}
}
}
// switch to day lighter mode
void
lighterPWM() {
if (stepPWM < numPWM - 1) {
stepPWM++;
modifyActive(valuesPWM[stepPWM]);
}
}
// switch to day darker mode
void darkerPWM() {
if (stepPWM > 0) {
stepPWM--;
modifyActive(valuesPWM[stepPWM]);
}
}
// change PWM of active channels
void modifyActive(const uint16_t newPWM) {
for (byte channel = 0; channel < getChannels(); channel++) {
if (getPWM(channel) != offPWM) {
setPWM(channel, newPWM);
}
}
}
};
MyTLC tlc(pinLATCH, DEVICES); // create an instance of your new class