I2C Bus Scannen, Adressen zuweisen und Prüfen

Hallo,

ich wurschtel mich gerade durch meinen Code, also nicht böse sein wenn das nicht 100% der Norm entspricht.

Zu meinem Problem, ich habe einen Teensy4.1, am I2C habe ich 4 bzw. 5 PCF8574A/T und einen eeprom, die PCF haben ja je nach Typ haben die eine andere Adresscodierung, was ich noch gelöst bekomme.
Nun möchte ich Prüfen ob alle Teilnehmer da sind, aber irgendwie komme ich da nicht sinnvoll weiter.

Ich habe den I2C_Scanner bisschen umgeschrieben, das die Zuweisung der Adressen funktioniert.

Ich hänge einfach mal den Codeschnippsel an, vielleicht hat einer eine andere Lösung.

//*************I2C Scan***************

nDevices = 0;
I2C_failure = 0; //Bool wenn I2C device fehlt dann 1
for(address = 1; address < 127; address++ ){

Wire.beginTransmission(address);
error = Wire.endTransmission();

if (error == 0){    
if (address == 0x25){input1 = 0x25;}    //PCF8574A input 1
if (address == 0x3D){input1 = 0x3D;}   //PCF8574T input 1
if (address == 0x23){input2 = 0x23;}    //PCF8574A input2
if (address == 0x3B){input2 = 0x3B;}    //PCF8574T input2      
if (address == 0x20){output1 = 0x20;}    //PCF8574A output1    
if (address == 0x38){output1 = 0x38;}    //PCF8574T output1    
if (address == 0x21){output2 = 0x21;}    //PCF8574A output2    
if (address == 0x39){output2 = 0x39;}    //PCF8574T output2 
if (digitalRead(ext_en)==0){                      //wenn extender board installiert 
if (address == 0x27){output3 = 0x27;}   //PCF8574A output3 auf extender board    
if (address == 0x3F){output3 = 0x3F;}   //PCF8574T output3 auf extender board
}
if (address == 0x50){eeprom_addr = 0x50;}   //adresse für EEPROM
nDevices++;
}

}

if (input1 == 0x25 || input1 == 0x3D){
  I2C_failure=1;}
if (input2 != 0x23 || input2 != 0x3B){
  I2C_failure=1;}
if (output1 != 0x20 || output1 != 0x38){
  I2C_failure=1;}  
if (output2 != 0x21 || output2 != 0x39){
  I2C_failure=1;}
if ((digitalRead(ext_en)==0)&(output3 != 0x27 || output3 != 0x3F)){
  I2C_failure=1;}
if (eeprom_addr != 0x50){
  I2C_failure=1;}

// Dartstellung im Nextion

if (error==4||I2C_failure==1||nDevices == 0){
Nextion.print("t23.txt=\"fail! ->I2C error\"");
Nextion.write(Nextion_end,3);
failure = failure +1;}
else{
digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
Nextion.print("t23.txt=\"ok\"");
Nextion.write(Nextion_end,3);  
delay(250);} 

irgendwo in meinen abfragen ob die Adressen den richtigen devices zugeordnet sind passt was, aber was?

mfg René

Warum nimmst Du nicht erst mal den originalen I2C-Scanner und lässt Dir einfach die Adressen anzeigen, die er findet?
Ansonsten solltest Du zu Deinem Code schon ansagen, was passt und was nicht.

Gruß Tommy

Hallo,

der Scanner findet die Adressen alle, das passt soweit, auch die richtige Zuweisung.
Alle Teilnehmer sind HW Seitig andressiert, nur gibt es bei den PCF8574 zwei typen mit unterschiedlichen Adressbereichen, deswegen die Zuweisung.
Ich werde den code der einfachheithalber noch kommentieren, da gebe ich dir recht.

mfg René

Wenn er alle Adressen richtig findet, wo ist dann Dein Problem?
Du musst nur wissen, welcher IC zu welcher Adresse gehört. Dazu im Zweifel eine LED (mit Vorwiderstand) an dem Baustein an Pin 0 ansteuern.
Wobei die Zuordnung ja bereits durch die HW-Beschaltung klar sein sollte.

Kommentare dürften da wenig bringen oder ich verstehe Dein Problem nicht.

Gruß Tommy

@KeBeNe Ich vermute auch, du suchst hier Fehler / Probleme, wo es keine gibt.
Einmal alles richtig verdrahtet und adressiert und gut ists.

Soweit stimmt das schon,

zum Hintergrund, das wird eine Ansteuerung für eine Endstufe, ich habe eine Bootroutine, die soweit alle Parameter prüft, wenn jetzt ein I2C Teilnehmer fehlt, soll die Endstufe in den Protect gehen.

Ich hatte den fall, das ein Out Teilnehmer aufgrund einer kalten Lötstelle nicht da war, man merkt das dann erst im Betrieb, wo es zu spät sein könnte, deswegen will ich wissen ob die Adressen vergeben/ gefunden worden.

hoffe das erklärt es ein bisschen...

mfg René

Aber du weist doch welche pcf typen du verbaust. Entweder mit a oder ohne. Daher sind auch die Adressen klar. 27 oder 3f. Das ändert sich ja nicht zufällig

...was genau war nochmal das Problem?

2 Likes

Da würde ich eine Struktur pro Adresse nehmen und ein Array daraus.

struct adresse {
  byte addr;
  bool found;
}

adresse adressen[] = {{0x25,false},{0x3d,false}, ...
}; 
const byte anzahl = sizeof(adressen) / sizeof(adressen[0]);

Und dann bei jeder gefundenen Adresse das Array durchgehen und bei Übereinstimmung found auf true setzen. Wird die Adresse nicht gefunden --> Error.
Am Ende über alle Elemente drüber gehen und wenn bei einem found == false ist --> Error.
Das erscheint mir übersichtlicher. Codefragment nicht getestet, nur als Denkanstoß.

Gruß Tommy

Das wird noch gesucht. :wink:

@KeBeNe
wozu scannst du 127 addressen wenn dich eh nur ein paar wenige interessieren?

Vorschlag:
Man sucht ganz explizit nach einem PCF. Wenn error, sucht man die Alternativ-Adresse.
Ist die auch nicht da - kann man eh schon abbrechen. Es fehlt was.

Der Code ist keine Schönheit *), aber imho geht das in die richtige Richtung.

mit einem 0x3F und ein paar auskommentierten Zeilen erfolgreich getestet:

// Sind mandatory devices am I2C Bus
// https://forum.arduino.cc/t/i2c-bus-scannen-adressen-zuweisen-und-prufen/1014679

#include <Wire.h>

byte input1 = 0;
byte input2 = 0;
byte output1 = 0;
byte output2 = 0;
byte output3 = 0;
const byte ext_en = 12;

bool isOisDo()  // I2C device fehlt dann 1, ansonsten 0 und die globalen Variablen bekommen die echten Addressen
{
  Serial.println(F("start scan"));
  int error;

  Wire.beginTransmission(0x25);
  error = Wire.endTransmission();
  if (error == 0) // success
  {
    input1 = 0x25;
  }
  else
  {
    Wire.beginTransmission(0x3D);
    error = Wire.endTransmission();
    if (error == 0)
    {
      input1 = 0x3D;
    }
    else
    {
      Serial.println(F("kein PCF für input1"));
      return 1;
    }
  }
  Wire.beginTransmission(0x23);
  error = Wire.endTransmission();
  if (error == 0) // success
  {
    input2 = 0x23;
  }
  else
  {
    Wire.beginTransmission(0x3B);
    error = Wire.endTransmission();
    if (error == 0)
    {
      input2 = 0x3B;
    }
    else
    {
      Serial.println(F("kein PCF für input2"));
      return 1;
    }
  }
  Wire.beginTransmission(0x20);
  error = Wire.endTransmission();
  if (error == 0) // success
  {
    output1 = 0x20;
  }
  else
  {
    Wire.beginTransmission(0x38);
    error = Wire.endTransmission();
    if (error == 0)
    {
      output1 = 0x38;
    }
    else
    {
      Serial.println(F("kein PCF für output1"));
      return 1;
    }
  }
  Wire.beginTransmission(0x21);
  error = Wire.endTransmission();
  if (error == 0) // success
  {
    output2 = 0x21;
  }
  else
  {
    Wire.beginTransmission(0x39);
    error = Wire.endTransmission();
    if (error == 0)
    {
      output2 = 0x39;
    }
    else
    {
      Serial.println(F("kein PCF für output2"));
      return 1;
    }
  }

  if (digitalRead(ext_en) == LOW)
  {
    Wire.beginTransmission(0x27);
    error = Wire.endTransmission();
    if (error == 0) // success
    {
      output3 = 0x27;
    }
    else
    {
      Wire.beginTransmission(0x3F);
      error = Wire.endTransmission();
      if (error == 0)
      {
        output3 = 0x3F;
      }
      else
      {
        Serial.println(F("kein PCF für output3"));
        return 1;
      }
    }
  }

  Wire.beginTransmission(0x50);  // EEPROM
  error = Wire.endTransmission();
  if (error != 0) // success
  {
    Serial.println(F("keine Antwort von EEPROM"));
    return 1;
  }

  Serial.println(F("alle I2C devices da"));
  Serial.print(F("input1=")); Serial.println(input1, HEX);
  Serial.print(F("input2="));  Serial.println(input2, HEX);
  Serial.print(F("output1=")); Serial.println(output1, HEX);
  Serial.print(F("output2=")); Serial.println(output2, HEX);
  Serial.print(F("output3=")); Serial.println(output3, HEX);
  return 0;
}

void setup() {
  Serial.begin(115200);
  Wire.begin();
  Serial.println(isOisDo());  // 0 OK, 1 irgendwas fehlt

}

void loop() {
  // put your main code here, to run repeatedly:

}

*) wennst nicht so - sorry - blöde durchnummerierte Variablen hättest, könnte man das in einer Schleife ohne viel dupliziertem Code abarbeiten...

Da

Dann sollte man konsequent die durchnummerierten Variablen durch Arrayelemente ersetzen, auch die beiden möglichen Adressen (struct).
Bei dem Codemonster sieht doch in spätestens 4 Wochen keiner mehr durch.

Gruß Tommy

wenigstens die Codeduplikate kann man reduzieren.
Besser wäre natürlich wenn der TO einen Gesamtzusammenhang klären würde.

// Sind mandatory devices am I2C Bus
// https://forum.arduino.cc/t/i2c-bus-scannen-adressen-zuweisen-und-prufen/1014679

#include <Wire.h>

byte input1 = 0;
byte input2 = 0;
byte output1 = 0;
byte output2 = 0;
byte output3 = 0;
const byte ext_en = 12;

byte checkAddress(byte address) // 1 fehler, oder eine gültige Addresse
{
  const byte offset = 0x18;     // difference between PCF8574 und PCF8574A
  int error;
  Wire.beginTransmission(address);
  error = Wire.endTransmission();
  if (error == 0)               // success
  {
    return address;             
  }
  else
  {
    Wire.beginTransmission(address + offset); // try alternative address
    error = Wire.endTransmission();
    if (error == 0)
    {
      return address + offset;
    }
    else
    {
      Serial.print(F("kein PCF auf 0x")); Serial.print(address, HEX);
      Serial.print(F(" oder 0x")); Serial.print((address + offset), HEX);
      return 1;
    }
  }
}

bool isOisDo()      // I2C device fehlt dann 1, ansonsten 0 und die globalen Variablen bekommen die echten Addressen
{
  Serial.println(F("start scan"));

  input1 = checkAddress(0x25); // 25/3D
  if (input1 == 1) return 1;
  input2 = checkAddress(0x23); // 23/3B
  if (input2 == 1) return 1;
  output1 = checkAddress(0x20); // 20/38
  if (output1 == 1) return 1;
  output2 = checkAddress(0x21); // 21/39
  if (output2 == 1) return 1;

  if (digitalRead(ext_en) == LOW)
  {
    output3 = checkAddress(0x27);
    if (output3 == 1) return 1;
  }

  Wire.beginTransmission(0x50);  // EEPROM
  int error = Wire.endTransmission();
  if (error != 0) // success
  {
    Serial.println(F("keine Antwort von EEPROM"));
    return 1;
  }

  Serial.println(F("alle I2C devices da"));
  Serial.print(F("input1=")); Serial.println(input1, HEX);
  Serial.print(F("input2="));  Serial.println(input2, HEX);
  Serial.print(F("output1=")); Serial.println(output1, HEX);
  Serial.print(F("output2=")); Serial.println(output2, HEX);
  Serial.print(F("output3=")); Serial.println(output3, HEX);
  return 0;
}

void setup() {
  Serial.begin(115200);
  Wire.begin();
  Serial.println(isOisDo());  // 0 OK, 1 irgendwas fehlt
}

void loop() {
}

Dann würde ich, wie schon geschrieben, nicht 127 Adressen scannen, sondern die notwendigen überprüfen. Idealerweise sollte das dann auch während des Programmlaufs geschehen, eine kalte Lötstelle oder andere Fehler können jederzeit auftreten. Getestet mit DS3231 und EEPROM:

#include "Wire.h"
enum {DS3231_I2C_ADDRESS, EEPROM_I2C_ADDRESS};
const uint8_t adresse[] = {0x68, 0x50};
uint8_t fehler = 0;

void setup()
{
  Wire.begin();
  Serial.begin(115200);
  delay(500);
  Serial.println("\nStart ...");
  for (auto &a : adresse)
  {
    Wire.beginTransmission(a);
    if ( Wire.endTransmission() ) fehler = a;
  }
}

void loop()
{
  if (fehler)
  {
    Serial.print(F("I²C Fehler Adresse 0x"));
    Serial.print(fehler, HEX);
    Serial.println(F("\nProgramm angehalten!"));
    while (1);
  }
  displayTime(); // display the real-time clock data on the Serial Monitor,
  delay(1000); // every second
}

void displayTime() {
  byte second, minute, hour;
  readDS3231time(&second, &minute, &hour);    // retrieve data from DS3231
  char buf[10] = {'\0'};
  snprintf(buf, sizeof(buf), "%02u:%02u:%02u\n", hour, minute, second);
  Serial.print(buf);
}

void readDS3231time(byte *second, byte *minute, byte *hour)
{
  int a = adresse[DS3231_I2C_ADDRESS];  // mit int ist die Bibliothek glücklich!
  Wire.beginTransmission( a );
  Wire.write(0); // set DS3231 register pointer to 00h
  if ( Wire.endTransmission() ) fehler = a;
  Wire.requestFrom(a, 3);
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val / 16 * 10) + (val % 16) );
}

Die Umrechnung von BCD auf Dezimalzahlen ließe sich auch noch etwas beschleunigen:

return val- 6 * (val >> 4);

Hallo,

nur mal eine Randbemerkung. Der gültige 7Bit Adressbereich liegt zwischen 0x08 und 0x77. Außerhalb dessen kann das ansonst zu unschönen Effekten und langer sinnfreier Fehlersuche führen.

Das Problem vom TO verstehe ich auch nicht. Man kennt die Adressen die man selbst durch Bausteinbeschaltung festgelegt hat. Wenn die alle vom Scanner gefunden werden ist alles i.O. Welche Adresse welcher Baustein hat ist vorher schon bekannt. Nur wenn eine Adresse fehlt oder gar eine Adresse zu viel auftaucht hätte man ein Problem.

Das heißt einfach nicht alle 128 Zahlen, die man mit 7 Bit darstellen kann können als I2C Adresse verwendet werden.
16 der 128 möglichen Adressen sind für Sonderzwecke reserviert.
siehe:

PCF8574 hat 8 Adressmöglichkeiten
PCF8574A hat weitere 8 Adressmöglichkeiten
Ein EEprom je nach Modell hat bis zu 8 Adressmöglichkeiten.
Also mußt Du max 24 Adressen abfragen. Da Du aber nur 4 /5 PCF8574x Bausteine verwenden willst weißt Du welche Adressen Du verwenden / Einstellen willst. Also beschränkt sich die Abfrage auf 8/10 Adressen für die Expander und 1 für das EEprom.

Grüße Uwe

Hallo Leute,

danke für die vielen Guten Ideen, manchmal sieht man den Wald vor lauter Bäumen nicht.
Der Einwand mit dem scannen der 127 Adressen ist vollkommen berechtigt und ich werde mir die Sache mit dem struct genau ansehen.
Dazu benötige ich 1-2 tage Zeit, da ich dieses "kleine" Projekt so nebenbei mache.

Ich bin eher der HW Entwickler als SW´er, also entschuldigt meinen Code, der ist weit von perfekt entfernt.

Danke schonmal an alle, ich melde mich!

schönes Wochenende...

mfg René

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.