LED per I2C schalten

Hallo zusammen,

ich bin sowohl hier als auch in der Arduino-Welt neu.

Ich habe folgendes vor:
Über einen Raspberry (I2C-Master) möchte ich eine LED anschalten, welche an einem Arduino Nano hängt.
Später möchte ich allerdings ein Relais statt einer LED über die selbe Art ansteuern.

Folgender Code läuft auf dem Arduino:

#include <Wire.h>
#define SLAVE_ADDRESS 0x07
int number = 0;
int state = 0;

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(9600); // start serial for output
  // initialize i2c as slave
  Wire.begin(SLAVE_ADDRESS);
  
  // define callbacks for i2c communication
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
  
  Serial.println("Ready!");
}

void loop() {
  delay(100);

}

// callback for received data
void receiveData(int byteCount){
    while(Wire.available()) {
    number = Wire.read();
    Serial.print("data received: ");
    Serial.println(number);
    if (number == 13){
      digitalWrite(number, HIGH);
      delay(5000);
      digitalWrite(number, LOW);
    }

  }
}

// callback for sending data
void sendData(){
  Wire.write(number);
}

Die Kommunikation zwischen Raspberry und Arduino funktioniert tadellos.
Das Problem ist, dass die LED nur kurz aufblinkt und sofort wieder erlischt.
Mit dem delay wollte ich die LED allerdings 5 Sekunden angeschalten lassen und danach wieder ausschalten, was aber nicht funktioniert :frowning:

Könnt ihr mir bitte weiterhelfen?

Gruß, tomml

Du musst den Zustand speichern, bis wieder eine Anweisung zum Ausschalten kommt.

// callback for received data

Sowohl delay(), als auch SerialPrint, versagen in einer ISR, weil sie selber von ISRs abhängig sind.

Vielen Dank für die schnellen Antworten.
Wenn ich delay nicht nutzen kann, wie kann ich dann eine Pause von 5sek generieren?
Ist ein weiteres Steuersignal vom Raspberry die einzige Möglichkeit?

tomml:
Vielen Dank für die schnellen Antworten.
Wenn ich delay nicht nutzen kann, wie kann ich dann eine Pause von 5sek generieren?
Ist ein weiteres Steuersignal vom Raspberry die einzige Möglichkeit?

Du kannst einen Timer verwenden, da gibt es z.B. von combie den "wahren simple Timer".
Suche mal hier im Forum.

HotSystems:
Du kannst einen Timer verwenden

Du kann dir einen Timer machen, dann siehst du wie simpel das ist.

In der callback merkst du dir nur, dass eine 13 gekommen ist. (Wofür ist z.B. state?)
In loop prüfst du dies, merkst dir die Zeit, machst die LED an, und stellst deinen state-Merker auf einen anderen Zustand.
Ausserdem prüfst du in loop, ob die 5 Sekunden seitdem um sind und machst dann die Led wieder aus.

Fertig ist der Timer.

Spitzenmäßig, vielen Dank !
Hab nun einen Timer eingebaut und damit funktioniert das Ganze. :slight_smile:

tomml:
Spitzenmäßig, vielen Dank !
Hab nun einen Timer eingebaut und damit funktioniert das Ganze. :slight_smile:

Wenn es jetzt funktioniert, kannst gern den fertigen Sketch hier einstellen.
Das ist der Sinn des Forum und auch so üblich.

Selbstverständlich, hier der Sketch:

#include <Wire.h>
#define SLAVE_ADDRESS 0x07
int number = 0;
int state = 0;
unsigned long time_received = 0;

void setup() {
  pinMode(5, OUTPUT);
  Serial.begin(9600); // start serial for output
  // initialize i2c as slave
  Wire.begin(SLAVE_ADDRESS);
  
  // define callbacks for i2c communication
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
  
  Serial.println("Ready!");
}

void loop() {
  delay(100);
  Serial.print(millis()); Serial.print("-----"); Serial.print(time_received); Serial.print("-----"); Serial.println(millis() - time_received);
  if (state == 1 && millis() - time_received < 5100 && millis() - time_received > 4900){
    turnoff();
  }
}

// callback for received data
void receiveData(int byteCount){
    while(Wire.available()) {
    number = Wire.read();
    Serial.print("data received: ");
    Serial.println(number);
    if (number == 1){
        time_received = millis();
        turnon();
      }
    }
}

void turnoff(){
  digitalWrite(5, LOW);
  state = 0;
  time_received = 0;
}
void turnon(){
  digitalWrite(5, HIGH);
  state = 1;
}


// callback for sending data
void sendData(){
  Wire.write(number);
}

Ich hab' eine gewisse Toleranz bei der Abschaltung eingebaut.

Gruß, tomml

Weiterhin noch Serial Aufrufe in der ISR.
Auch sind die globalen Variablen nicht volatile, und das Auslesen dieser Variablen nicht ATOMIC.

Ich befürchte, dass dir das irgendwann auf die Füße fallen wird.
Ich empfehle etwas mehr Sorgfalt.

Hi combie,

ich hab' den Code nur aus einem I2C-Tutorial kopiert und etwas modifiziert.
Versteh' leider nicht, was das Problem ist?

Kannst du evtl. etwas genauer erklären, bitte?

Suche mal nach: "avr interrupt volatile atomic"
Oder, wenn das zu schwierig ist, einfach dieses lesen

tomml:
Versteh' leider nicht, was das Problem ist?

Für eine monostabile Kippstufe von fünf Sekunden benötigst Du keinen Interrupt, daher kannst Du die ISR auf ein Minimum reduzieren und das Kippen dem Hauptprogramm überlassen. Wenn Du nur ein Byte schickst, ist das schon atomar, so daß m. E. keine weiteren Maßnahmen notwendig sind.

Über Details, beispielsweise ob die monostabile Kippstufe retriggerbar sein soll, muß man sicher noch nachdenken.

Ich habe mit einem UNO als Master und einem Mega2560 als Slave getestet:

// I2C-Slave
#include <Wire.h>
volatile byte wert;
const uint32_t flopIntervall = 5000;
uint32_t aktMillis, flopMillis;

void setup() {
  Serial.begin(9600);
  Serial.println("Slave Start");
  Wire.begin(21);
  Wire.onReceive(receiveEvent);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  aktMillis = millis();
  if (wert == 1) {
    wert = 0;
    flopMillis = aktMillis;
    digitalWrite(LED_BUILTIN, HIGH);
    Serial.println("Ein");
  }
  if ((aktMillis - flopMillis >= flopIntervall) && digitalRead(LED_BUILTIN))
  {
    digitalWrite(LED_BUILTIN, LOW);
    Serial.println("Aus");
  }
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  for (byte j = 0; j < howMany; j++) {
    wert = Wire.read();    // receive byte
  }
}