NeoPixel- oder FastLed Library - Welche ist schneller?

SoftwareSerial ist für Arduino/AVR auf 57600 Bd begrenzt:

  • On Arduino or Genuino 101 boards the current maximum RX speed is 57600bps.

Jedenfalls gemäß dieser Doku https://docs.arduino.cc/learn/built-in-libraries/software-serial/

Ist aber ziemlich egal, da man den kompletten Led-Kettenstatus in 8 Byte unterbringen kann, die bei 57600 Bd mit 5760 Byte/s übertragen werden können, also alle 1,4 ms oder theoretisch(!!) 720 mal pro Sekunde. Das wird nicht benötigt.

Ich habe mal eine Testumgebung mit einem UNO und einem NANO und einer 60er-Led-Kette hergestellt, wobei der UNO den Status der 60 Leds binär festlegt, per SoftwareSerial mit 57600 Bd an den NANO überträgt und der NANO die Umsetzung zu den WS2812B-Leds umsetzt.

Beide zusätzlich mit dem Hardware-Signal verbunden, das der NANO setzt, während er Daten zu den Leds schauffelt. Klappt bei mir problemlos:

Sketche und Verdrahtung

Sketch UNO:

#include <SoftwareSerial.h>
constexpr byte SIGNAL_PIN{ 4 };
constexpr byte rxPin{ 5 };
constexpr byte txPin{ 6 };
constexpr int NUM_LEDS{ 60 };

byte ledStates[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
int noOfBytes = sizeof(ledStates) / sizeof(ledStates[0]);

SoftwareSerial mySerial(rxPin, txPin);

void setup() {
  pinMode(SIGNAL_PIN,INPUT);
  Serial.begin(115200);
  Serial.println("Control Leds Via SoftwareSerial");
  mySerial.begin(57600);
}

void loop() {
  runEvery(10);
}

void runEvery(unsigned long interval) {
  static unsigned long lastEvent = 0;
  static int count = 0;
  if (millis() - lastEvent > interval) {
    lastEvent = millis();
    while (digitalRead(SIGNAL_PIN) == LOW) { delayMicroseconds(50); };
    for (int i = 0; i < noOfBytes; i++) {
      mySerial.write(ledStates[i]);
    }
    clearLeds();
    setLedNo(count);
    setLedNo(NUM_LEDS-count-1);
    count++;
    if (count >= NUM_LEDS) {
      count = 0;
    }
  }
}

void setLedNo(int num) {
  if (num < NUM_LEDS) {
    int index = num / 8;
    int actBit = num - index * 8;
    byte actByte = ledStates[index];
    ledStates[index] = actByte | (0x01 << actBit);
  }
}

void clearLeds() {
  for (int i = 0; i < noOfBytes; i++) {
    ledStates[i] = 0;
  }
}

Sketch NANO

#include <FastLED.h>
#include <SoftwareSerial.h>
constexpr int  NUM_LEDS   {60};
constexpr byte DATA_PIN   { 3};
constexpr byte SIGNAL_PIN { 4};
constexpr byte rxPin      { 5};
constexpr byte txPin      { 6};

// SoftwareSerial object
SoftwareSerial mySerial (rxPin, txPin);
CRGB leds[NUM_LEDS];

constexpr int noOfBytes = 8;
byte ledStates[noOfBytes];
int no = 0;

void setup() {
  pinMode(SIGNAL_PIN,OUTPUT);
  digitalWrite(SIGNAL_PIN, HIGH);
  Serial.begin(57600);
  Serial.println("LedStrip_Timing");
  mySerial.begin(57600);
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
}

byte count = 0;

void loop() {
  evaluateMySerial();
}

void evaluateMySerial(){
  if (mySerial.available()){
    char c = mySerial.read();
    ledStates[no] = c;
    no++;
    if (no >= noOfBytes){
        while (mySerial.available()){char c = mySerial.read();};  // Clear Buffer after 8 bytes to remove CR/LF/NL
        no = 0;
        setLeds();
    }
  }
}

void setLeds() {
  int startPos = 0;
  for (int i = 0; i < noOfBytes;i++){
    byte actByte = ledStates[i];
    for (int j = 0; j < 8;j++){
      int ledIndex = startPos+j;
      if (ledIndex < NUM_LEDS){
        if (actByte & 0x01 == 0x01) {
          leds[ledIndex] = CRGB::Green;
        } else {
          leds[ledIndex] = CRGB::Black;
        }
        actByte = actByte >> 1;
      }
    }
    startPos += 8;
  }
  digitalWrite(SIGNAL_PIN, LOW);
  FastLED.show();
  digitalWrite(SIGNAL_PIN, HIGH);
}

Das kann man sicher noch wesentlich(!) geschickter programmieren, hat mir aber als Test so genügt. Das Update der Led-Kette wird vom UNO alle 10 ms angestoßen, soweit der NANO nicht gerade "beschäftigt" ist. Die Anzahl der Leds ist durch die 8 Bytes auf 64 beschränkt, das ließe sich aber ebenfalls leicht anpassen, falls benötigt.

Für einen realen Einsatz würde man sicherlich auch noch eine Start-Synchronisierung der SoftwareSerial-Daten implementieren, um definierte Startbedingungen für beide Controller sicherzustellen.

Der Vorteil der Übertragung des Bitfeldes ist es, dass nur einer (in diesem Fall der UNO) den aktuell gewünschten Stand der leuchtenden bzw. nichtleuchtenden Led kennen muss. Der zweite Controller (hier der NANO) braucht sich für das nächste Update nichts zu merken, da bei jedem Update immer alle Zustände übertragen werden.

Gruß
ec2021

So, als letztes hier noch zwei überarbeitete Sketche.

Sketche und Verdrahtung

Zur direkten Ansteuerung der Led-Kette (ehemals "Nano2"):

#include <FastLED.h>
#include <SoftwareSerial.h>
constexpr int NUM_LEDS{ 60 };
constexpr byte DATA_PIN{ 3 };
constexpr byte SIGNAL_PIN{ 4 };
constexpr byte rxPin{ 5 };
constexpr byte txPin{ 6 };
constexpr byte CLEAR_PIN{ 7 };

// SoftwareSerial object
SoftwareSerial mySerial(rxPin, txPin);
CRGB leds[NUM_LEDS];

constexpr int noOfBytes = 8;
byte ledStates[noOfBytes];
int no = 0;

void setup() {
  pinMode(SIGNAL_PIN, OUTPUT);
  digitalWrite(SIGNAL_PIN, LOW);
  Serial.begin(57600);
  Serial.println("Led Access I");
  mySerial.begin(57600);
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  delay(10);
  digitalWrite(SIGNAL_PIN, HIGH);
}

void loop() {
  checkClearPin();
  evaluateMySerial();
}

void checkClearPin() {
  static byte lastState = HIGH;
  byte actState = digitalRead(CLEAR_PIN);
  if (actState != lastState) {
    lastState = actState;
    if (lastState == LOW) {
      clearBuffer();
    }
  }
}

void evaluateMySerial() {
  if (mySerial.available()) {
    byte c = mySerial.read();
    fillBuffer(c);
  }
}

void fillBuffer(byte c) {
  ledStates[no] = c;
  no++;
  if (no >= noOfBytes) {
    while (mySerial.available()) { byte d = mySerial.read(); };  // Clear Buffer after 8 bytes to remove CR/LF/NL
    no = 0;
    setLeds();
  }
}

void clearBuffer() {
  for (int i = 0; i < noOfBytes; i++) {
    ledStates[i] = 0;
  }
  no = 0;
}


void setLeds() {
  int startPos = 0;
  for (int i = 0; i < noOfBytes; i++) {
    byte actByte = ledStates[i];
    for (int j = 0; j < 8; j++) {
      int ledIndex = startPos + j;
      if (ledIndex < NUM_LEDS) {
        if (actByte & 0x01 == 0x01) {
          leds[ledIndex] = CRGB::Green;
        } else {
          leds[ledIndex] = CRGB::Black;
        }
        actByte = actByte >> 1;
      }
    }
    startPos += 8;
  }
  digitalWrite(SIGNAL_PIN, LOW);
  FastLED.show();
  digitalWrite(SIGNAL_PIN, HIGH);
}

Der Sketch für den UNO ("Nano1"), von dem der o.a. "Nano2" das Bit-Array der anzusteuernden Leds erhält:

#include <SoftwareSerial.h>
constexpr byte SIGNAL_PIN{ 4 };
constexpr byte rxPin{ 5 };
constexpr byte txPin{ 6 };
constexpr byte CLEAR_PIN{ 7 };
constexpr int NUM_LEDS{ 60 };

byte ledStates[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
int noOfBytes = sizeof(ledStates) / sizeof(ledStates[0]);

SoftwareSerial mySerial(rxPin, txPin);

void setup() {
  pinMode(CLEAR_PIN, OUTPUT);
  digitalWrite(CLEAR_PIN, HIGH);
  mySerial.begin(57600);
  pinMode(SIGNAL_PIN, INPUT_PULLUP);
  clearAllLeds();

  Serial.begin(115200);
  Serial.println("Led Control II");
}

void loop() {
  if (newData(20)) {
    sendData();
  }
}

boolean newData(unsigned long interval) {
  static unsigned long lastChange = 0;
  static int count = 0;
  static int lastCount = -1;
  if (millis() - lastChange > interval) {
    lastChange = millis();
    clearLastLeds(lastCount);
    setLedNo(count);
    setLedNo(NUM_LEDS - count - 1);
    lastCount = count;
    count++;
    if (count >= NUM_LEDS) {
      count = 0;
    }
    return true;
  }
  return false;
}


void sendData() {
  while (digitalRead(SIGNAL_PIN) == LOW) { delayMicroseconds(50); };
  clearRemoteBuffer();
  for (int i = 0; i < noOfBytes; i++) {
    mySerial.write(ledStates[i]);
  }
}

void setLedNo(int num) {
  if (num < NUM_LEDS && num >= 0) {
    int index = num / 8;
    int actBit = num - index * 8;
    byte actByte = ledStates[index];
    ledStates[index] = bitSet(actByte, actBit);
  }
}

void clearLedNo(int num) {
  if (num < NUM_LEDS && num >= 0) {
    int index = num / 8;
    int actBit = num - index * 8;
    byte actByte = ledStates[index];
    ledStates[index] = bitClear(actByte, actBit);
  }
}

void clearLastLeds(int num) {
  if (num >= 0) {
    clearLedNo(num);
    clearLedNo(NUM_LEDS - num - 1);
  }
}

void clearRemoteBuffer() {
  digitalWrite(CLEAR_PIN, LOW);
  delay(1);
  digitalWrite(CLEAR_PIN, HIGH);
}

void clearAllLeds() {
  for (int i = 0; i < noOfBytes; i++) {
    ledStates[i] = 0;
  }
}

Zwischen den beiden Controllern habe ich noch eine weitere digitale Verbindung (Pin 7 - Pin 7) eingeführt, über die der UNO den Empfangspuffer des NANO jeweils rücksetzen lässt, bevor die relevanten Daten übertragen werden. Das stellt die Synchronisation des Bit-Arrays auch dann sicher, wenn die beiden mal - z.B. durch Neustart eines Controllers - "aus dem Tritt" kommen.

Die SoftwareSerial-Rückleitung vom Nano zum Uno kann man auch weglassen, sie wird aktuell nicht genutzt.

Um 61 statt 60 Leds zu unterstützen muss in beiden Sketchen die folgende Konstante auf 61 gesetzt werden:

  • constexpr int NUM_LEDS{ 60 };

Für die Auswertung der Midi-Kommandos muss man in dem bei mir dem Uno zugeordneten Sketch nur die Funktion boolean newData() umschreiben. Dort wird aktuell alle "interval" Millisekunden das Array bearbeitet. Für Midi würde man

  • NoteON mit setLedNo(num) und 0 <= num < NUM_LEDS und
  • NoteOff mit clearLedNo(num) und 0 <= num < NUM_LEDS

umsetzen. Ich vermute, dass Du die Orgelbelegung nutzt (61 Tasten bis zum vier gestrichenen C, wobei dem tiefsten Ton C der Midi-Wert 36 zugeordnet ist). Dann ergäbe sich num = (Midi-Wert - 36).

Gibt die Funktion newData() true zurück, so wird in loop() die Funktion sendData() aufgerufen. Solange keine neuen Daten vorliegen (oder noch Daten "gesammelt" werden) gibt man false zurück.

Ob man die Auswirkung jeder NoteON/NoteOFF Message einzeln überträgt oder bis zu einem noch zu definierenden Timeout sammelt, muss man mal ausprobieren.

Viel Erfolg!
ec2021

Hallöchen,

wie versprochen, hier nun das Schaltbild und mein bisher erstellter Code dazu.

Schaltbild:

Programmcode:

// MIDI RGB Keyboard v4.01.01
// Begonnen am: 01.06.2021

#include                    <Adafruit_NeoPixel.h>                                                      // LED Stripe (WS2812B) ansteuern

#define                     numLed                             61                                      // Anzahl der LEDs/Tasten + 1
#define                     enableUSB                           6                                      // Update Steuerpin D5 [5] USB = MIDI Daten kommen über CD4066 an den Serial Port | LOW = MIDI Daten
#define                     ledStripe                           5                                      // LED Steuerpin    D4 [4] 
#define                     pinIntLed                          13                                      // Interne LED für flackerfreie MIDI Anzeige

byte                        keysOn[numLed + 1]            =  { };                                      // Array zum Speichern der gedrückten Keyboard Tasten
byte                        midiChannel                   =    1;                                      // MIDI Kanal (0 = Omni Mode/Alle Kanäle)
byte                        midiTranspose                 =    0;                                      // MIDI Transpose (Notenversatz)
byte                        keysPressed                   =    0;                                      // Anzahl gedürckter Tasten 
long     unsigned           ledTime;                                                                   // MIDILED Zeit (millis)

//bool                        ledStripeOn                 = true;                                      // Sind die LEDs eingeschaltet?
int                                H1 = 160, S1 = 255, V1 = 255;                                      // LED Werte für nicht gedrükte Tasten
int                                H2 = 230, S2 = 255, V2 = 255;                                      // LED Werte für gedrückte Tasten
Adafruit_NeoPixel pixels(numLed, ledStripe, NEO_GRB + NEO_KHZ800);                                     // LED Stripe initialiseren



// ################################################## SETUP ##################################################



void setup()
{
    pinMode(pinIntLed,           OUTPUT);                                                               // Pin Interne LED
    pinMode(enableUSB,           OUTPUT);                                                               // Seriellen Port aktivieren (KEIN Serieller Betrieb mehr möglich!)
    pinMode(ledStripe,           OUTPUT);                                                               // Datenpin des LED Stripes
    digitalWrite(pinIntLed,        HIGH);                                                               // Interne LED während der Initialisierung einchalten
    digitalWrite(enableUSB,         LOW);                                                               // Seriellen Port für MIDI deaktivieren (über CD4066), damit Updates möglich sind. LOW = USB | HIGH = MIDI

    Serial.begin(31250);                                                                                // RS-232 initialisieren (311250 Baud)

    // Selbsttest (1 x beim Einschalten)
    for(byte y = 0; y <= numLed; y ++)
    {
        pixels.setPixelColor(0      + y, pixels.ColorHSV( H1 * 256, S1, V1));                           // Alle Tasten von links mit Farbe 1...
        pixels.setPixelColor(numLed - y, pixels.ColorHSV( H2 * 256, S2, V2));                           // und von rechts mit Farbe 2 füllen.
        pixels.show();
        delay(30);
    }
    delay(500);

    for(byte y = 0; y < 33; y++)
    {
        pixels.setBrightness(map(y, 0, 32, 255, 0));                                                    // Helligeit aller LED von 64 bis 0 dimmen.
        pixels.show();
        delay(30);
    }
    delay(500);

    pixels.setBrightness(V1);                                                                          // Alle LEDs auf Helligkeit 1 (aus) setzen
    for(byte x = 0; x < numLed; x++)
    {
        pixels.setPixelColor((numLed / 2) - x / 2, pixels.ColorHSV( H1 * 256, S1, V1));                // Alle Tasten von links und rechts...
        pixels.setPixelColor((numLed / 2) + x / 2, pixels.ColorHSV( H1 * 256, S1, V1));                // mit Farbe 1 füllen.
        pixels.show();
        delay(20);
    }
    digitalWrite(pinIntLed, HIGH);
    digitalWrite(enableUSB, HIGH);                                                                      // Ab jetzt ist MIDI über den CD4066 auf den seriellen Port (TxD) gelegt.  
    ledTime     = millis();
}



// ################################################## LOOP ##################################################



void loop()
{
    if(millis() >= ledTime + 500 && digitalRead(pinIntLed))                                              // Wenn interne LED (MIDI Led) leuchtet, und 1 Sekunde vergangen ist, ...
        digitalWrite(pinIntLed, LOW);                                                                    // LED wieder ausschalten (MIDI Anzeige leuchtet also für 0.5 Sek.).
    
    if(Serial.available() > 2)                                                                           // Sind 3 Bytes oder mehr verfügbar?
    {
        byte midiCommand        = Serial.read();                                                         // Ja, dann das 1 Byte einlesen...
        byte midiReceiveChannel = (midiCommand & B00001111) + 1;                                         // MIDI Kanal errechnen

        if(midiReceiveChannel != midiChannel && midiChannel)                                             // Richtiger MIDI Kanal oder OMNI Mode (Kanal 0)?
        {
            Serial.flush();                                                                              // Nein, dann seriellen Speicher löschen...
            return;                                                                                      // ... und nicht reagieren
        }

        if(!digitalRead(pinIntLed))                                                                      // Wenn int. Led (MIDI Led) nicht bereits leuchtet, ...
        {
            digitalWrite(pinIntLed, HIGH);                                                               // ... int. led (MIDI Led) füe einschalten.
            ledTime = millis();                                                                          // Zeit merken 
        }

        byte command           = midiCommand >> 4;                                                       // Ja, dann MIDI Kommando errechnen, ...
        byte midiNoteNumber    = Serial.read() - 36 + midiTranspose;                                     // ... Byte für Notennummer lesen und Notenwert errechnen...
        byte midiVelocity      = Serial.read();                                                          // ... und Byte für Velocity lesen

        if(command == 9)                                                                                 // Ist command = NoteOn?
        {
            //keysOn[midiNoteNumber] = true;                                                               // Array für gedrückte Tasten aktualisieren
            keysPressed++;                                                                               // Zähler für gedrückte Tasten inkrementieren
            pixels.setPixelColor(midiNoteNumber, pixels.ColorHSV(H2 * 256, S2, V2));                     // Led Array aktualisieren
            pixels.show();                                                                               // LEDs aktualisieren
        }

        else if(command == 8)                                                                            // Ist Command = NoteOff?
        {
            //keysOn[midiNoteNumber] = false;                                                              // Array für gedrückte Testen aktualisieren
            if(keysPressed)                                                                              // Ist die Anzahl gedrückter Tasten > 0?
                keysPressed--;                                                                           // Ja, dann 1 abziehen
            pixels.setPixelColor(midiNoteNumber, pixels.ColorHSV(H1 * 256, S1, V1));                     // Led Array aktualisieren
            pixels.show();                                                                               // LEDs aktualisieren
        }
    }

    //if(millis() > (ledTimeout * 10000) + ledTime && ledTimeout && ledStripeOn)                           // LEDs nach n Minuten abschalten
    //    powerOffLed();
}



void powerOffLed()
{
  //ledStripeOn = false;                                                                                 // LEDs sind aus
  //for(byte x = 0; x <= round(numLed / 2); x++)
  //{
  //  pixels.setPixelColor(x         , pixels.ColorHSV( H1 * 256, S1, 0));                               // ... LED Leiste von links ...
  //  pixels.setPixelColor(numLed - x, pixels.ColorHSV( H1 * 256, S1, 0));                               // ... und rechts zur Mitte hin abschalten.
  //  pixels.show();
  //  delay(75);                                                                                         // Kurz warten, um den "Laufeffekt" zu simulieren
  //}
  //keysPressed = 0;
}



//void powerOnLed()
//{
//  for(byte x = 0; x <= round(numLed / 2); x++)
//  {
//    pixels.setPixelColor((numLed / 2) + x, pixels.ColorHSV( H1 * 256, S1, V1));
//    pixels.setPixelColor((numLed / 2) - x, pixels.ColorHSV( H1 * 256, S1, V1));
//    pixels.show();
//    delay(10);
//  }
//  ledStripeOn = true;
//  ledTime = millis();
//}

Ein paar Anmerkungen dazu:
Punkt 1:
Der Schaltungsteil um den 6N138 Optokoppler habe ich 1:1 so gebaut (abgekupfert), wie die Firma Korg ihn im meinem M1 (der verwendete Synthesizer) aufgebaut hat.
Meine Idee war: Was der Profi macht, sollte für den Bastler auch funktionieren.

Punkt 2:
Der rot umrahmte Schaltungsteil mit dem CD4066 (6 Analogschalter) benutze ich, um während der "Einschalt Sezuenz" MIDI von der Schaltung (RxD Pin) zu trennen, damit ich ein Programm in den Nano uploaden kann.
Es ist vielleicht nicht der Weisheit letzter Schluss, aber es funktioniert reibunugslos.
So muss ich nicht dauernd "umstöpseln" und muss nur 2 oder 3 kleine Löcher ins Metallgehäuse des Keybords bohren (Nano USB Port, Reset Taster und LED (wenn man sie denn braucht), die den Staus anzeigt.

Punkt 3:
Ich habe mir erspart, alle Kondensatoren/Elkos einzuzeichnen, da ich ein "Mann der alten Schule" bin und Schaltbilder tatsächlich noch mit Bleistift auf Millimeterpapier zeichne.
Ich habe dicht an jedem IC von VCC nach GND einen 0.1 uF Keramik Abblockkondensator.

Punkt 4:
Der LED Stripe (WS2812B) hat einen Puffer Elko von 1000uF zwischen VCC und GND.
Ich speise VCC an Anfang und Ende des Stripes ein.
Am VCC Eingang der Schaltung (Nano) liegt auch ein Pufferelko von 1000uF.

Punkt 5:
Die im Code ausmarkierten Teile soll(t)en mal rein, aber ich bin mir da nicht mehr so sicher.
Ich hatte vor, einen Timeout einzubauen, der alle LEDs abschaltet, wenn das Keyboard 10 Min lang nicht benutzt wird und beim 1. Tastendruck wieder einzuschalten. Dieser Code ist aber bisher unfertig.

Punkt 6:
Das Array "keysOn []" war dazu gedacht, die Notenwerte für eine Akkordanzeige zu speichern (war eine andere Frage von mir in diesem Forum). Das habe ich aber (nach den Timingbedneken hier in diesem Thread) erstmal beiseite gelegt.

Punkt 7:
Bisher arbeitet die Schaltung zu 99% sauber (NoteOff Hänger), d.h. ich kann spielen, die Anzeige funktioniert ohne spürbare Lags und ich kann auch die Tasten "durchrattern", ohne dass man eine Latenz bemerkt.
Selbst 20+ Tasten mit zwei flachen Händen gedückt, scheinen sauber zu funktionieren.

Was ich evel. noch vor habe ist, dass (das geht wohl aber erst mit 2 Nanos) ein OLED zur Anzeige der gespielten Akkorde und ggf. einen Rotary Encoder für eine Menüsteuerung ( Farben, MIDI Kamal, Transpose, Einstellugen, etc.) einbaue.
Das ist aber noch Zukunftsmusik (wie treffend) und sicherlich kein Muss.

Und jetzt lausche ich gespannt Euren Korrekturen und Ratschlägen und Danke Euch dafür im Voraus.

Gucky.

wie vor 9 Tagen bereits angenommen.

so etwas sieht fehleranfällig aus:

millis() >= ledTime + 500

schau dir mal das Beispiel "Blink Without Delay" an und prüfe auf die Differenz millis() - ledtime > 500

So mache ich es doch oder nicht?
Ich speichere in "ledTime" immer den neuen millis() Wert, wenn ein gültiges MIDI Kommando ( 3 Byte) erkannt wird. MIDI Sense wird dadurch (gewollt) ignoriert, da die M1 dieses Signal liefert, sobald sie an ist (das ist sowas ähnliches wie ein Ping).
Wenn dann der jetzige millis() Wert gleich oder größer des gespeicherten Wertes + 500 (ms) ist, geht die LED wieder aus. Ich habe das getestest, indem ich spiele UND gleichzeitig "wie ein Irrer" den Pitch Bend ändere (das führt übrigens auch zu keinerlei spürbaren Lags).
Ist das so falsch, bzw. gibt es eine bessere (schnellere) Lösung?

Gucky.

Hallo,

es gibt schon einen Unterschied zwischen
millis() >= ledTime + 500
und
millis() - ledTime >= 500

Ein Bsp. mit kleinen byte Wertebereich damit man es eher sieht.
Tausche die zwei a) Zeilen gegen b) Zeilen aus.
Schaue dir die Unterschiede an.
Die Wertebereichsüberläufe sind entscheidend, wann diese wie stattfinden.

Aktuell siehst du den Fehler nicht, weil mit Datentyp unsigned long der Fehler erst nach 49 Tagen auftritt.

// https://forum.arduino.cc/t/neopixel-oder-fastled-library-welche-ist-schneller/1385662/66
// 09.06.2025
// millis Simulation

unsigned int counter; // Hilfszähler für die Simulation
byte milliSec;
byte lastTime;
const byte intervall {50};

void setup (void)
{
  Serial.begin(9600);
  Serial.println("\nuC Reset ###");

  while(counter<500) {
    //if ((byte)(milliSec - lastTime) >= intervall) {  // a) richtiger Überlauf 
    if (milliSec >= (byte)(lastTime + intervall)) { // b) falscher Überlauf
      lastTime = milliSec;
      Serial.println(" --- Tick ---"); 
    }
    Serial.print(milliSec); Serial.print(" "); 
    //Serial.print((byte)(milliSec - lastTime));  // a) richtiger Überlauf 
    Serial.print((byte)(lastTime + intervall)); // b) falscher Überlauf
    Serial.print(" "); Serial.println(lastTime);
    milliSec++;
    counter++;
  }
}

void loop (void)
{ }

Da Deine millis()-Routine anscheinend nur die Status-Led am Controller ausschalten soll, wenn dies für ca. 500 ms eingeschaltet war, hat die - allgemein als fehleranfällig zu vermeidende Umsetzung, die sich tatsächlich auch erst bei einem Überlauf von millis() (also nach ca. 49 Tagen Dauereinsatz) zeitigt - wohl keinen Einfluss auf die beobachteten Phänomene.

Ich würde es aus grundsätzlichen Erwägungen umstellen, das kostet nix und ist die saubere Lösung.

Wahrscheinlicher für Fehlfunktionen des Sketches sind nicht detektierte Midi-Kommandos während des show()-Aufrufs (Sperrung der Interrupts) ...

Du hast Recht, das habe ich tatsächlich nicht gesehen. Für mich was das "das selbe", was es natürlich nicht ist.
Im Ergebnis macht das zwar den gleichen Effekt, aber durch Dein Beispiel ist es mir klar(er) geworden (49 Tage). Allerdings habe ich mein Keyboard noch nie 49 Tage am Stück.... Aber lassen wir das :wink:

Stimmt, das ist nur "Sahne auf dem Kuchen" und könnte auch komplett entfallen. Eine normale LED als MIDI Anzeige flackert im Takt der Daten. Das wollte ich auf diese Weise verhindern, indem die LED für 500ms leuchtet, nachdem erst 3 Bytes (1 MIDI Kommando) empfangen wurden. Der MIDI Sense (Ping), den einige (nicht alle) Geräte senden, möchte ich so ausblenden.

Gucky.

Eine zusätzliche Möglichkeit für Datenverlust ist die Annahme, dass nur 3 Byte Kommandos an der Schnittstelle auftauchen...

The MIDI protocol is made up of messages. A message consists of a string (ie, series) of 8-bit bytes. MIDI has many such defined messages. Some messages consist of only 1 byte. Other messages have 2 bytes. Still others have 3 bytes. One type of MIDI message can even have an unlimited number of bytes.

Ich weiß, dass zahlreiche Beispiele für den Midi-Empfang so aufgebaut sind; dies ist allerdings im Zusammenspiel mit einer "echten" Midi-Quelle riskant.

Die Auswertung von Midi-Streams kann je nach Quelle durchaus komplexer sein, als nur auf drei aufeinander folgende Bytes zu warten.

Ein weiteres Beispiel

Each RealTime Category message (ie, Status of 0xF8 to 0xFF) consists of only 1 byte, the Status. These messages are primarily concerned with timing/syncing functions which means that they must be sent and received at specific times without any delays. Because of this, MIDI allows a RealTime message to be sent at any time, even interspersed within some other MIDI message. For example, a RealTime message could be sent inbetween the two data bytes of a Note On message. A device should always be prepared to handle such a situation; processing the 1 byte RealTime message, and then subsequently resume processing the previously interrupted message as if the RealTime message had never occurred.

Quelle: http://midi.teragonaudio.com/tech/midispec.htm

Für eine zuverlässige Umsetzung muss man jedes Byte darauf prüfen, ob es ein Kommando ist oder Daten zum zuletzt empfangenen Kommando beinhaltet.

Einige Geräte senden u.U. auch ein NoteON mit Velocity 0 anstelle eines NoteOFF ...

Um festzustellen, was bei Deiner Anwendung tatsächlich gesendet wird, empfiehlt es sich, einmal Rohdaten aufzunehmen, also alles was kommt, wenn Du das Keyboard richtig"quälst" , in einer Datei zu speichern und in Ruhe sorgfältig auszuwerten.

Das könnte für Erhellung sorgen... Insbesondere, weil man solche Dateien geschickt auch zum wiederholbaren Testen der Auswertung nutzen kann.

1 Like

Ging mir bei dem Argument auch erst so. Jaaaa erst nach 49 Tagen... Aber !! Der Mensch ist Gewohnheitstier.

1 Like

Das stimmt zwar, aber ich möchte nur das MIDI Sense Signal nicht anzeigen (alle 300ms ein 0xFE) und eine einfache "MIDI in" Anzeige haben. Vielleicht baue ich noch eine Abfrage daraufhin ein, was aber wieder zusätzliche "Arbeit" für den Nano bedeutet.
Daher habe ich das nach der ohnehin vorhandenen Abfrage

if(Serial.available() > 2)

Um nur auf "NoteOn" und "NoteOff" zu regieren, könnte ich den Zähler dafür auch in den jeweiligen Ábfragen "if(command == 8)" und "if(command == 9)" setzen, denn die Anzeige muss eigentlich auch nur nech diesen Kommendos erfolgen, sie macht (z.B. wenn nur ein Aftertouch, Pitchbend, Pan, etc. pp. kommt) keinen wirklichen Sinn, da davor ohnehin erst eine "NoteOn", bzw, "NoteOff" Meldung kommen muss.
Oder übersehe ich etwas?

Gucky.

Überlege mal, was passiert, wenn das erste Byte, das einläuft, eine 1 Byte Message ist und dann ein NoteOn kommt...

Was passiert dann mit dem NoteOn-Kommando-Byte und dem ersten Daten-Byte?

Du hast prinzipiell natürlich Recht, aber hier geht es nicht um MIDI Daten zu "verarbeiten", sondern nur um eine einfache LED.
Im Grunde könnte, wie ich auch schon sagte, diese LED und der damit verbundene Code auch entfallen.
Die M1 hat im Original auch keine MIDI Anzeige LED. Diese dient jetzt mehr dazu, wenn der Stripe mal nicht funktioniert, sofort sehen zu können, ob ggf. die ganze Schaltung lebt oder hängt. Das könnte sogar eine LED im Inneren des Keyboards sein, die von außen für mich erkennbar ist (spart ein Loch :)).

Das habe ich bereits vor einiges Jahren mehrfach machen müssen, denn da kaufte ich einen "Roland U220" Expander. Da es noch keinen Editor dafür gab, musste ich alles "selber per Handarbeit austüfteln". Mein Anfang mit den MIDI Interna...
Es hat mich Wochen gekostet, die MIDI Implementierungstabelle (die zum Glück aber im Handbuch ist), zu verstehen. Mein 1. Editor war sogar noch in Basic programmiert (grins), aber er lief. Mann, hat es mich Nerven gekostet, MIDI Checksummen zu errechnen, um Exclusivmeldungen senden zu können.
Von alldem hatte ich bis dahin absolut keine Ahnung.

Andere Frage:
Da bisher nur Anmerkungen und Tipps zur MIDI LED kamen, nachdem ich den Code gepostet habe, frage ich mal "dumm" in den Raum:
Ist der denn irgendwie falsch oder verbesserungsfähig ?
Den Punkt RGB Werte statt HSV zu bemutzen (schneller), habe ich aber auf dem Schirm.
Die "millis()" Geschichte habe ich bereits geändert, wie es ec2021 angeregt hatte.

Gucky.

Der, der sich in dieser Materie (MIDI) und Arduino bestens auskennt hast Du ja freundlich ausgeladen.
Ich vermute "Keine Arme keine Kekse"

Soweit ich sehen, hast du nur 3 konstante Werte

pixels.ColorHSV( H1 * 256, S1, V1) //#00FFAA --> grünblau
pixels.ColorHSV( H1 * 256, S1, 0)  //#000000 -> schwarz
pixels.ColorHSV( H2 * 256, S2, V2) //#002AFF -> blau

Das kann man durch echte Konstanten im RGB Farbmodell ersetzen.

Richtig, aber wie man sieht, habe ich diese (insgesamt sind es ja 6 Farbwerte) als Byte, bzw. int Variablen festgelegt.
Ebenso wie den MIDI Kanal und Transposewert (Notenversasatz).
Ursprünglich war geplant, diese Schaltung mit einen OLED Display und einem Encoder auszustatten. um ggf. via Menü gewisse Einstellungen veränderbar zu machen und im EEprom abzuspeichern.
Dazu gehören dann natürlich auch dann die sechs Farbwerte.
Mit Konstanten müsste man aber jedes Mal, wenn ich z.B. der MIDI Kanal ändert, etc., den Nano neu flashen.
Aber ob und wie ich das mache, weiß ich noch nicht, zumal mich diese Gedanken zu "2 Nanos" dahingehend etwas "gebremst" haben.

Wie? Wann? Wo? Inwiefern? Ich bin mir keiner Schuld bewusst. Ausgeladen?
Meintest Du etwa deswegen, bzw. meiner Antwort darauf?

Das war keineswegs böse gemeint. Falls das so rüberkam, sorry.
Aber es erweckte in mir ein wenig ein "Los, nu mach hinne, sonst will ich ich mehr."
Das war aber dann bitte nicht gleichbedeutend mit:
"Ich will von Dir hier nix mehr sehen."

Gucky.

Das steht aber nicht im Code. Ich sehe aktuell nur 2 Farben und aus. Aber egal, ist dein Code.

Man kann auch in der Encoderfunktion einmal den RGB Wert berechnen (mit ColorHSV) und den dann benutzen.

a) Kannst Du mal erläutern, was dieser Teil Deines Sketches tun soll

if(midiReceiveChannel != midiChannel && midiChannel)                                             // Richtiger MIDI Kanal oder OMNI Mode (Kanal 0)?
        {
            Serial.flush();                                                                               // Nein, dann seriellen Speicher löschen...
            return;                                                                                      // ... und nicht reagieren
        }

Serial.flush() löscht, anders als viele es meinen, nicht den Rx-Buffer! Das Kommando wartet, bis der Tx-Buffer geleert ist ...

b) Wenn Du die Lösung mit zwei separaten Controllern verwirfst, wirst Du mit dem Problem der gesperrten Interrupts während der Datenübertragung zum Ledstreifen (dem in dieser Zeitspanne möglichen Midi-Datenverlust) leben müssen. Deine Entscheidung :wink:

ist Unsinn, weil immer = midiChannel

Gruß Tommy

Wird der != nicht vorher auf das linke midiChannel angewandt?