[GELÖST] Zwei Metro Mini via I2C verbunden - Hochladen bzw. reset?

Wegen der Inkompatibilität der FastLED und WIFININA libraries möchte ich zwei Metro Mini via I2C verbinden, der eine übernimmt nur die WLAN-Kommunikation, der Andere alles Weitere.

Spielt es eine Rolle, ob man zuerst zum TX oder RX Teil hochlädt, bzw. nach dem asynchronen Hochladen die reset Knöpfe drückt, wenn man keinen Laptop anschließen kann, und keine Eingabe/Ausgabe über den seriellen Monitor hat?

Ja.
Nein.

Es kann beides gültig sein. Das hängt ganz davon ab, wie defensiv die Teile programmiert sind und was sie wann von der Gegenseite erwarten.
Im Übrigen senden und empfangen auch beim I2C-Bus beide Seiten Daten (mindestens das ACK-Bit).

Ok, ich folge diesem Beispiel. Vom master kommt in meinem Fall ein Mal pro Sekunde ein byte (CO2 Sensor) und alle drei Sekunden ein anderes byte (PM 2.5 Sensor), die der slave empfangen und dann via WLAN im gleichen Zeitabstand an ThingSpeak senden soll.

Leider kein Erfolg. Sobald ich

// MASTER

#include <Wire.h>
#define SLAVEADDRESS 7

int analogPin = 1;
int txValue = 0;

void setup()
{
  Wire.begin();
  
  Serial.begin(9600);
}

void loop()
{
  txValue = map(analogRead(analogPin), 0, 1023, 1, 255);

  Serial.print("TX: "); Serial.println(txValue);

  Wire.beginTransmission(SLAVEADDRESS);
  Wire.write(txValue);
  Wire.endTransmission();
}

sowie

// SLAVE

#include <Wire.h>
#define SLAVEADDRESS 7

int LED = 13;
int rxValue;
int blinkRate;

void setup()
{
  pinMode(LED, OUTPUT);
  
  Wire.begin(SLAVEADDRESS);
  Wire.onReceive(receiveEvent);
  
  Serial.begin(9600);
}

void receiveEvent()
{
  rxValue = Wire.read();
  
  Serial.print("RX: "); Serial.println(rxValue);
}

void loop()
{
  blinkRate = map(rxValue, 1, 255, 100, 1000);

  digitalWrite(LED, HIGH);
  delay(blinkRate);
  digitalWrite(LED, LOW);
  delay(blinkRate);
}

mit FastLED code auf dem Master verbinde, kommen die Werte beim Slave nur noch stotternd an. Die WLAN Übertragung habe ich gar nicht erst ausprobiert. Ist I2C also auch keine Möglichkeit der FastLED/WIFININA Inkompatibilität zu entrinnen?

Die Inkompatibilität kommt bei FastLED oft durch die Verwendung von Interrupts. Diese werden bei gesperrt um die engen Timings einzuhalten. I2C braucht genauso Interrupts

Dann bleibt eigentlich nur noch, die WS2812 durch APA 102 zu ersetzen, die haben diese Probleme nicht.

Gruß Tommy

Dann will ich mal meinen Senf dazu geben.
Erstens:
Kein Serial.print begin(9600)!
Und schon gar nicht ein Serial.print() ohne das sich der Wert geändert hat! Du flutest Dir den Port.

Ich hab mal auf das Schematic geschaut.
https://learn.adafruit.com/assets/45231

Was bezweckst Du hiermit:

int analogPin = 1;

Das ist ein DigitalPin. (PD1)
Und somit dem Seriellen Port zugeordnet.

Wenn man dem Mechanical trauen darf
https://learn.adafruit.com/assets/45199
stimmt meine Annahme.

Also mal ganz umgebaut:
Dein PIN 1 wird PIN A1.
Sowohl SerMon als auch Wire.write() kommt nur noch zum Einsatz, wenn sich der Wert geändert hat.
Und dann das #define weg.

// MASTER

#include <Wire.h>
const byte SLAVEADDRESS = 7;

const byte analogPin = A1;
byte txValue = 0;
byte oldTxValue = 0;

void setup()
{
  Wire.begin();
  Serial.begin(115200);
}

void loop()
{
  txValue = map(analogRead(analogPin), 0, 1023, 0, 254);
  if (oldTxValue != txValue)
  {
    Wire.beginTransmission(SLAVEADDRESS);
    Wire.write(txValue);
    Wire.endTransmission();
    Serial.print("TX: "); Serial.println(txValue);
    oldTxValue = txValue;
  }
}

kompiliert fehlerfrei.
Tut was?

Senf 2:
Dein Empfänger ist kaputt:

15:30: warning: invalid conversion from 'void (*)()' to 'void (*)(int)' [-fpermissive]
   Wire.onReceive(receiveEvent);

Aber das übersehe ich mal und hab Dir einen Empfängersketch gebaut. (Natürlich mit der Warnung drin - sollst ja auch noch was zu tun bekommen...)

Der grösste und wichtigste Unterschied:
KEIN DELAY!
Naja und das mit "ich mach nur was, wenn sich was ändert" findet hier natürlich auch Anwendung.

Ungetestet - ohne Kommentare - aber ich wollte halt mal...

// SLAVE

#include <Wire.h>
const byte SLAVEADDRESS = 7;

const byte LED = 13;
byte rxValue = 0;
byte oldRxValue = 0;
unsigned int blinkRate;

void setup()
{
  pinMode(LED, OUTPUT);
  Wire.begin(SLAVEADDRESS);
  Wire.onReceive(receiveEvent);
  Serial.begin(115200);
}

void receiveEvent()
{
  rxValue = Wire.read();
}

void loop()
{
  if (oldRxValue != rxValue)
  {
    blinkRate = map(rxValue, 0, 254, 100, 1000);
    Serial.print("RX: "); Serial.println(rxValue);
    oldRxValue = rxValue;
  }
  tik(blinkRate);
}

void tik(unsigned long blinkTime)
{
  static unsigned long lastmillis = 0;
  if (millis() - lastmillis > blinkTime)
  {
    digitalWrite(LED, !digitalRead(LED));
    lastmillis = millis();
  }
}

Problem ist ich habe in jeder Installation NeoPixel Ringe mit 24 RGB verbaut, das kann ich nicht ändern. Trotzdem - Danke!

Danke - schade, ich dachte einen zweiten uC zu verwenden (paßt noch in die Gehäuse der Installationen rein) der nur WLAN Übertragung macht, wäre ein Weg, dem FastLED/WIFININA Problem zu entkommen.

An diesem Pin ist testhalber ein Potentiometer angeschlossen. Die beiden Codes oben, genau wie ich sie hier einkopiert habe, funktionieren. Drehe ich am Potentiometer am Master, ändert sich die Blinkgeschwindigkeit am Slave von sehr schnell (100 ms) zu langsam (1000 ms).

Also sind meine beiden uC wohl nicht defekt und der Pin 1 scheint ok zu sein. Wenn ich 1 auf A1 ändere, ändert sich nichts. Es funktioniert (ohne FastLED, ohne WLAN) wie zuvor. Ich habe auch andere Metro Mini ausprobiert, mit dem gleichen Resultat.

Serial.print scheint auch normal zu funktionieren, denn im Serial Monitor sehe ich die Werte des Master bzw. Slave, ob ich am Potentiometer drehe oder nicht.

Das #defiine in eine Variablendeklaration zu ändern macht leider auch keinen Unterschied. Es funktioniert (ohne FastLED, ohne WLAN) wie zuvor.

Der Code, den Sie alternativ vorschlagen, funktioniert ebenso.

Sobald ich aber

Wire.begin();

und

txValue = map(analogRead(analogPin), 0, 1023, 0, 254);
  if (oldTxValue != txValue)
  {
    Wire.beginTransmission(SLAVEADDRESS);
    Wire.write(txValue);
    Wire.endTransmission();
    Serial.print("TX: "); Serial.println(txValue);
    oldTxValue = txValue;
  }

in den Code mit FastLED auf dem Master integriere, erhalte ich wiederum Stotterwerte auf dem Slave.

Danke, der Slave Code funktioniert auch, wie der alte, aber wenn auf dem Master FastLED benutzt wird, kommen auch mit Ihrem verbesserten Code leider nur Stotterwerte auf dem Slave an.

Es ist völlig egal, ob man analogRead(1) oder analogRead(A1) schreibt.
Man kann darüber streiten, was schöner ist, aber es bleibt dennoch gleichwertig.

also ein ESP ala NodeMCU oder WemosD1 hat meiner Meinung kein Problem mit Wifi und Fastled. Bevor ich da mit mehreren Controllern rummache würde ich einen Testen der alles kann.

Ich bin ganz Deiner Meinung, allerdings wurde dieser Vorschlag bereits im vorangegangenen Thema verworfen.

Leider finde ich hier im Thema nirgendwo Programmteile für FastLED oder habe ich was übersehen? Die Schnipsel hier mit den Verbesserungen von @my_xy_projekt funktionieren mit zwei UNOs prima.

Also denke ich mir selbst was aus, getestet mit zwei UNOs und WS2815:

// MASTER

#include <Wire.h>
#include <FastLED.h>

FASTLED_USING_NAMESPACE
#define DATA_PIN    11
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define NUM_LEDS    24
CRGB leds[NUM_LEDS];

const byte SLAVEADDRESS = 7;
const byte analogPin = A1;
byte txValue = 0;
byte oldTxValue = 0;

void setup()
{
  Wire.begin();
  Serial.begin(9600);
  Serial.println(F("Start"));
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.show();
}

void loop()
{
  txValue = map(analogRead(analogPin), 0, 1023, 0, 254);
  if (oldTxValue != txValue)
  {
    Wire.beginTransmission(SLAVEADDRESS);
    Wire.write(txValue);
    Wire.endTransmission();
    setzeFarbe(txValue);
    Serial.print(F("TX: ")); Serial.println(txValue);
    oldTxValue = txValue;
  }
}

void setzeFarbe(byte farbe) {
  for (byte led = 0; led < NUM_LEDS; led++) {
    leds[led] = CHSV(farbe, 255, 255);
  }
  FastLED.show(); // send the 'leds' array out to the actual LED strip
}
// SLAVE

#include <Wire.h>
const byte SLAVEADDRESS = 7;

const byte LED = 13;
byte rxValue = 0;
byte oldRxValue = 0;
unsigned int blinkRate;

void setup()
{
  pinMode(LED, OUTPUT);
  Wire.begin(SLAVEADDRESS);
  Wire.onReceive(receiveEvent);
  Serial.begin(9600);
  Serial.println(F("Start"));
}

void receiveEvent(int numBytes)
{
  for (byte j = 0; j < numBytes; j++)
  {
    rxValue = Wire.read();
  }
}

void loop()
{
  if (oldRxValue != rxValue)
  {
    blinkRate = map(rxValue, 0, 254, 100, 1000);
    Serial.print(F("RX: ")); Serial.println(rxValue);
    oldRxValue = rxValue;
  }
  tik(blinkRate);
}

void tik(unsigned long blinkTime)
{
  static unsigned long lastmillis = 0;
  if (millis() - lastmillis >= blinkTime)
  {
    digitalWrite(LED, !digitalRead(LED));
    lastmillis = millis();
  }
}

Abweichungen fallen mir keine auf:

Master Slave
12:29:38.119 -> TX: 174 12:29:38.119 -> RX: 174
12:29:38.119 -> TX: 173 12:29:38.119 -> RX: 173
12:29:38.119 -> TX: 174 12:29:38.119 -> RX: 174
12:29:38.119 -> TX: 173 12:29:38.119 -> RX: 173
12:29:38.119 -> TX: 174 12:29:38.119 -> RX: 174
12:29:38.166 -> TX: 173 12:29:38.166 -> RX: 173
12:29:38.166 -> TX: 172 12:29:38.166 -> RX: 172

Die LEDs leuchten, wie sie sollen, ich kann Dein Problem daher nicht reproduzieren, wo hast Du es versteckt?

Ich hatte zunächst je einen Metro Mini plus AirLift FeatherWing ESP32 verwenden wollen, aber FastLED/WIFININA vertragen sich nicht. Dann bekam ich den Hinweis es notfalls mit einem zweiten uC zu probieren, aber FastLED/I2C vertragen sich anscheinend auch nicht.

Ich habe in diversen Installationen (U-Bahn Station) bereits die Metro Mini (mit Dezibel Meßgerät und zwei Potentiometern, also drei Analogeingänge benötigt) plus AirLift FeatherWing ESP32 (für WLAN) Lösung verbaut. Ohne FastLED funktioniert alles, auch nach WLAN Ausfall, usw. Im Gehäuse ist gerade noch Platz für einen zweiten uC.

Ich habe hier noch einen Adafruit Feather Huzzah ESP8266 (hat nur einen Analog-Pin, aber ich brauche drei), einen Sparkfun ESP32 Thing (viele Analog-Pins, die aber laut Sparkfun noch nicht nutzbar seien), und einen NodeMCU mit ESP32 (ich habe keine Erfahrung damit).

Danke, das hier unten ist der letzte Stand, bei dem beim Slave die gesendeten bytes nur ruckelig ankommen (Serial Monitor Ansicht).

Master

#include <Wire.h>
#include "FastLED.h"

const byte pinData = 3;
const byte pinSEN0232 = A0;
const byte pinPotentiometerMin = A1;
const byte pinPotentiometerMax = A2;

const byte ledCount = 24;
const byte ledBrightness = 192;
struct CRGB ledRing[ledCount];
struct CRGB ledGradient[ledCount];

const int intervalPeakHold = 900;
const int intervalPeakDecay = 30;

const float EMA_a = 0.9;

const byte addressSlave = 7; // Added for I2C

CHSV gradientHueStart = CHSV(96, 255, 96);
CHSV grandientHueEnd = CHSV(0, 255, 192);

unsigned long timeSEN0232 = 0;

long dBValue = 0;
int dBValueEMA = 0;
byte dBMin = 0;
byte dBMax = 0;

int newPeak = 0;
int previousPeak = 0;
unsigned long timePeak = 0;
byte brightnessPeak = 0;
bool decay = false;

void setup()
{
  Wire.begin(); // Added for I2C
  Serial.begin(115200); // Added for I2C
  
  FastLED.addLeds<NEOPIXEL, pinData>(ledRing, ledCount);

  FastLED.setBrightness(ledBrightness);

  fill_gradient(ledGradient, 0, gradientHueStart, 23, grandientHueEnd, SHORTEST_HUES);
}

void loop()
{
  readSEN0232();

  readPotentiometers();

  if (newPeak >= previousPeak)
  {
    previousPeak = newPeak;
    timePeak = millis();
    brightnessPeak = ledBrightness;
    decay = false;
  }

  else if (!decay && (millis() - timePeak >= intervalPeakHold))
  {
    timePeak += intervalPeakHold - intervalPeakDecay;
    decay = true;
  }

  else if (decay && (millis() - timePeak > intervalPeakDecay))
  {
    if (previousPeak > 0)
    {
      previousPeak --;

      if (brightnessPeak <= 0)
      {
        brightnessPeak = 0;
      }

      else
      {
        brightnessPeak -= 16;
      }
      timePeak += intervalPeakDecay;
    }
// 
    Wire.beginTransmission(addressSlave); // Added for I2C
    Wire.write(dBValueEMA); // Added for I2C
    Wire.endTransmission(); // Added for I2C
  }

  FastLED.clear();

  for ( byte i = 0; i <= newPeak; i++)
  {
    ledRing[i] = ledGradient[i];
  }

  ledRing[previousPeak] = CHSV(0, 255, brightnessPeak);

  FastLED.show();
}

void readSEN0232()
{
  float voltageValue;

  voltageValue = analogRead(pinSEN0232) / 1024.0 * 5.0;
  dBValue = voltageValue * 50.0;

  dBValueEMA = int ((EMA_a * dBValue) + ((1 - EMA_a) * dBValueEMA)) + 0.5;

  newPeak = constrain(map(dBValueEMA, dBMin, dBMax, 0, 23), 0, 23);
  Serial.println(dBValueEMA); // Added for I2C
}

void readPotentiometers()
{
  dBMin = constrain(map(analogRead(pinPotentiometerMin), 0, 1023, 35, 60), 35, 60);
  dBMax = constrain(map(analogRead(pinPotentiometerMax), 0, 1023, 60, 130), 60, 130);
}

Slave

#include <Wire.h>
const byte addressSlave = 7;

int rxValue;

void setup()
{
  Wire.begin(addressSlave);
  Wire.onReceive(receiveEvent);
  Serial.begin(115200);
}

void receiveEvent()
{
  rxValue = Wire.read();
  Serial.print("RX: "); Serial.println(rxValue);
}

void loop()
{
  // Later millis() timed WLAN and MQTT code for publishing to AIO
}

mach mal ein Systembild.
du hast also

  • 24 Neopixel
  • 3 Analoge Eingänge?
  • Ein CO2 Sensor - was genau welche Schnittstelle?
  • du brauchst wifi damit du irgendwohin Daten schicken willst?
  • du bist größenbeschränkt?

Was macht deines erachtens die WIFININA was du nicht auch ohne lösen kannst?

Die zeitliche Beschränkung sehe ich in Deinem Programm nicht.

Hiermit blockierst Du:

void loop()
{
...
  FastLED.show();
}

Konzeptionelle Verbesserung: Nur wenn Du sekündlich einen neuen Wert bekommst, schickst Du diesen per I2C und zeigst ihn mittels FastLED.show(); an.

// MASTER

#include <Wire.h>
#include <FastLED.h>

FASTLED_USING_NAMESPACE
#define DATA_PIN    11
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB
#define NUM_LEDS    24
CRGB leds[NUM_LEDS];

const byte SLAVEADDRESS = 7;
const byte analogPin = A1;
byte txValue = 0;

void setup()
{
  Wire.begin();
  Serial.begin(9600);
  Serial.println(F("Start"));
  FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.show();
}

void loop()
{
  uint32_t jetzt = millis();
  static uint32_t vorhin = jetzt;
  if (jetzt - vorhin >= 1000)
  {
    vorhin = jetzt;
    txValue = map(analogRead(analogPin), 0, 1023, 0, 254);
    Wire.beginTransmission(SLAVEADDRESS);
    Wire.write(txValue);
    Wire.endTransmission();
    setzeFarbe(txValue);
    Serial.print(F("TX: ")); Serial.println(txValue);
  }
}

void setzeFarbe(byte farbe) {
  for (byte led = 0; led < NUM_LEDS; led++) {
    leds[led] = CHSV(farbe, 255, 255);
  }
  FastLED.show(); // send the 'leds' array out to the actual LED strip
}

Ich habe hier ein Schema, wie alle Komponenten momentan verbunden sind.

Das Dezibel-Meßgerät gibt 0,6 - 2,6V aus, die in dB Werte von 30 - 130 umgewandelt werden. Die zwei Potentiometer dienen dazu, je nach U-Bahn Station die untere und obere Lärmschwelle einzustellen, damit stets alle 24 RGB LEDs ausgenutzt werden. Der RGB LED Ring ist hinter einer Fresnel-Linse angebracht und visualisiert die dB Werte. Die dB Werte werden per WLAN und MQTT zu AIO (alternativ ThingSpeak) übertragen.

Das funktionierte im ursprünglichen set-up (ohne Master/Slave, nur ein Metro Mini plus AirLift FeatherWing) auch gut, aber eben entweder ohne WLAN, oder ohne FastLED.

Ich bin bauraumbeschränkt nur in soweit, daß das, was auf dem Schema zu sehen ist, in das 3D-gedruckte Kunststoffgehäuse gerade so eben paßt.

Einen CO2 Sensor gibt es in diesem set-up nicht.

Es gibt je Station am anderen Ende ein zweites set-up mit einem SENSIRION SCD30 CO2 and RH/T Sensor und einem SENSIRION Particulate Matter Sensor SPS30 mit der gleichen Problematik. Mit WLAN die Daten übertragen - kein Problem. Parallel dazu einen NeoPixel Ring mit FastLED zu betreiben geht nicht.

Das Grundproblem ist also, daß die FastLED (und auch Adafruits NeoPixel library) und WIFININA library nicht kompatibel sind, und so kam es, nach einem Forenvorschlag zu der Idee, zwei uC zu verwenden, mit I2C verbunden, wobei der zweite uC nur für WLAN zuständig ist.

Wenn FastLED und WIFININA doch, irgendwie, problemlos zusammenarbeiten könnten, dann bräuchte es auch keinen zweiten uC und kein I2C.