I2C-Takt reduzieren

Erstmal vielen Dank euch beiden.

Den Multi-Scan-Sketch hatte ich vorhin auch schon mal überflogen, aber da war so viel Kram bei, den ich auf die Schnelle nicht verstanden habe, dass ich den wieder verworfen hatte.
Den habe ich jetzt mal in einen Pro Micro mit 16MHz geladen und ausgeführt. Für einen Scan zeigt er mir 297ms an, wobei ein anderer Pro Micro als Slave auf allen getesteten Frequenzen zwischen 50 und 800 kHz gefunden wird.

Ich kombiniere: Wenn die Slaves ausreichende Fähigkeiten haben, kommen sie ohne weitere Einstellungen mit der Masterfrequenz zurecht.

Wenn ich das richtig sehe, dann ist TWBR ein Register, in welches man theoretisch beliebige Werte von 0 bis 255 schreiben kann, woraus sich unterschiedliche Taktraten für I2C ergeben.

Der Vorteiler wird in o.g. Scan-Tool nicht manipuliert, soweit ich das überblicke. Ich habe daher einfach nach und nach diese Zeilen eingefügt:

// TWSR |= _BV (TWPS0);
// TWSR |= _BV (TWPS1);

BTW: Es hat ganz schön lange gedauert, bis ich verstanden habe, dass _BV() ein Makro ist und wie es funktioniert. Was es macht, konnte ich mir natürlich schon denken :slight_smile:

Mit den Vorteiler 4 und 64 hat das Scantool dann für einen Durchlauf 455 und 3.636 ms gebraucht. Ich habe nichts da, um den Takt messen zu können, aber ich gehe davon aus, dass das auch wirklich funktioniert. Das Scantool zeigt zumindest auch mit 64er Vorteiler und maximalem TWBR an, dass es den Slave erkannt hat.
Das müssten dann also 50 kHz / 64 = 781 Hz sein.

Damit sollte ich ausreichend Spiel haben, um auch lange und eher ungeeignete Leitungen verwenden zu können.

Falls ich letztlich einen sehr geringen Takt verwende, sollte ich dann den Vorteiler und TWBR bei den Slaves auch verstellen?
Nur weil es beim Testaufbau mit extrem kurzer Leitung funktioniert, muss es ja nicht auch bei 40m Leitung funktionieren.

Hallo,

den Takt gibt der Master vor. Darauf hat der Slave keinen Einfluss.
Und 40m wirst Du nicht schaffen. Das Thema gabs schon einmal. Bei ca. 5m sollte Schluss sein, je nach Kabelqualität. Probieren kannst es aber, wäre interessant für alle.

Wiki:
Er wird hauptsächlich geräteintern für die Kommunikation zwischen verschiedenen Schaltungsteilen benutzt, z. B. zwischen einem Controller und Peripherie-ICs.

Sowas würde ich nicht als Busleitung großartig rauslegen. Das Problem ist die Buskapazität. In einem Beitrag(Link unten), hat jemand den Bus auf 50m Problemlos erweitert. Jedoch arbeitet dieser bereits mit 12V Pegeln.

Steige am besten direkt auf einem Seriellen 485 Bus um. Dann kannste die Buslänge vernachlässigen.

http://www.mikrocontroller.net/topic/168946

Es gibt 2 Möglichkeiten einen I2C Bus über längere Distanzen zu betreiben:
[0] Busextender wie den P82B715 oder PCA9507
[0] I2C Acelerator wie den LTC4311 oder PCA9511 bis 14

Grüße Uwe

I2C wurde eigentlich wie der Name sagt für die Kommunikation zwischen ICs innerhalb eines Gerätes konzipiert. Ursprünglich vor allem in Fernsehern. Deshalb ist es nicht für größere Distanzen ausgelegt auch wenn das in gewissen Grenzen funktioniert.

Die Pullups niedriger zu machen wirkt der Leitungskapizität auch etwas entgegen. Das wird auf der Nick Gammon Seite auch gezeigt:

Aber richtige Treiber ICs sind wie gesagt die bessere Option

Nachtrag:

Das Scantool zeigt zumindest auch mit 64er Vorteiler und maximalem TWBR an, dass es den Slave erkannt hat.
Das müssten dann also 50 kHz / 64 = 781 Hz sein.

16.000.000 Hz / (16 + (255 * 2 * 64)) = 490 Hz

Dass I2C eigentlich nur für den Datentransfer innerhalb von Geräten gedacht war, habe ich schon gelesen. Es gibt aber auch Berichte von Leuten, die 200m geschafft haben sollen.

Die anderen Schnittstellen habe ich mir auch mal angesehen, aber keine wäre so einfach zu integrieren wie I2C. Deswegen wollte ich es damit versuchen und ggf. die Taktrate deutlich verringern oder notfalls zusätzliche Treiber einsetzen.

Als Kabel habe ich YR12x0,8 zur Verfügung. Die Leitungskapazität liegt bei maximal 300nF/km.
Wenn ich von 50m für die längste Strecke und 100m als Gesamtlänge ausgehe, dann komme ich bei 4k7 auf etwa 1kHz. Die Widerstände kann ich auch noch verkleinern, ohne dass der Leitungswiderstand zum Problem wird.
Alles rein theoretisch. Ich habe es noch nicht ausprobiert.

Ich werde bei nächster Gelegenheit einfach mal die komplette Rolle mit 100m Kabel anschließen und gucken, ob und wenn ja, bei welcher Taktrate und welchem Pullup-Widerstand es funktioniert.

@Serenifly
Mit dem "maximalen TWBR" meinte ich das Maximum (152), was im Scantool verwendet wird. 490 Hz kann ich morgen auch noch ausprobieren.

...gucken, ob und wenn ja, bei welcher Taktrate und welchem Pullup-Widerstand es funktioniert.

Dann guck auch gleich, was für einen Unterschied es macht, ob die unbenutzten Adern deines 12x0,8 Kabels auf GND oder offen sind (oder andere Stör-Signale tragen), und ob die 100 m aufgewickelt oder nicht sind.

Einerseits aus Interesse, andererseits weil ich fürchte, ein einzelner Versuchsaufbau wird kein verwertbares Ergebnis bringen, leider.

Danke !

So, ich habe mal ein bisschen mit dem Kabel und Kapazitäten gespielt.

Die 100m Kabel haben zwischen zwei Adern mit offenen Enden rund 13nF.
Damit geht am Arduino gar nix. Wenn ich auch nur die Ader für die Taktleitung am Master anschließe, hängt er sich auf, selbst mit 780 Hz.
Ich habe die PullUps auf bis zu 330 Ohm reduziert, bringt aber nix.

Dann habe ich mit diversen verfügbaren Kapazitäten herumgespielt. Wenn ich 7nF zwischen SCL und SDA hänge und 330 Ohm PullUps verwende, dann funktioniert der Bus. sogar mit 400 kHz.

Das Kabel will ich jetzt nicht zerschneiden und zerknüddeln. Das brauche ich noch.

Der Test hat aber gezeigt, dass zumindest bei 5V die Limits überschritten sind. Zwar war die längste Strecke, die ich überbrücken wollte "nur" ca. 30m lang, was vielleicht funktionieren würde, aber später sollte noch der eine oder andere Arduino hinzu kommen, und dann klemmt's vielleicht.

Das Verhalten des Multiscan-Sketches kann ich mir noch nicht erklären. Vielleicht kann mir dabei jemand unter die Arme greifen.
Wenn ich SDA und SCL unbeschaltet lasse, dann läuft ein Scan komplett durch und meldet für jede Adresse und Taktrate, dass er keine Gegenstelle gefunden hat.
Wenn ich dann nur eine Ader an SCL des Masters klemme, dann bleibt das Sketch direkt nach der Anzeige des Headers stehen. Klemme ich die Ader wieder ab, läuft der Scann weiter, oder das Programm bleibt komplett stehen. Dann muss ich den Arduino resetten.
Was passiert da? Warum läuft das Programm nicht weiter? Die Datenleitung sollte relativ unbeeiflusst sein.

Ich habe den Multiscan-Sketch erweitert, um noch langsamere Geschwindigkeiten mit Vorteilern testen zu können.
Das ist nix Wildes, aber vielleicht hilft es ja jemandem, der womöglich noch weniger Ahnung von C hat als ich.
Man kann mit den Kommandos 1, 4 und 6 die Vorteiler 1, 4 und 64 einstellen. Die verwendete Frequenz wird im Header entsprechend angezeigt.

//
//    FILE: MultiSpeedI2CScanner.ino
//  AUTHOR: Rob Tillaart
// VERSION: 0.1.03
// PURPOSE: I2C scanner @different speeds
//    DATE: 2013-11-05
//     URL:
//
// Released to the public domain
//
// added prescale control (24.7.2014)
// Attention!  Novice!
//

#include <Wire.h>
#include <Arduino.h>

// scans devices from 50 to 800KHz I2C speeds.
// lower than 50 is not possible
// DS3231 RTC works on 800 KHz. TWBR = 2; (?)
long speed[] = { 
  50, 100, 200, 250, 400, 500, 800 };
const int speeds = sizeof(speed)/sizeof(speed[0]);

// DELAY BETWEEN TESTS
#define RESTORE_LATENCY  5    // for delay between tests of found devices.
bool delayFlag = false;

// MINIMIZE OUTPUT
bool printAll = true;
bool header = true;

// STATE MACHINE
enum states {
  STOP, ONCE, CONT, HELP, PRESCALE1, PRESCALE4, PRESCALE64 };
states state = STOP;

uint32_t startScan;
uint32_t stopScan;

byte Prescaler;
float HeaderSpeed;

void setup() 
{
  Serial.begin(57600);
  Wire.begin();
  displayHelp();
  Prescaler = 1;
}


void loop() 
{
  switch (getCommand())
  {
  case 's': 
    state = ONCE; 
    break;
  case 'c': 
    state = CONT; 
    break;
  case 'd': 
    delayFlag = !delayFlag;
    Serial.print(F("<delay="));
    Serial.println(delayFlag?F("5>"):F("0>"));
    break;
  case 'e': 
    // eeprom test TODO
    break;
  case 'h': 
    header = !header;
    Serial.print(F("<header="));
    Serial.println(header?F("yes>"):F("no>"));
    break;
  case '?': 
    state = HELP; 
    break;
  case 'p': 
    printAll = !printAll;
    Serial.print(F("<print="));
    Serial.println(printAll?F("all>"):F("found>"));
    break;
  case 'q': 
    state = HELP; 
    break;
    case '1': 
    state = PRESCALE1; 
    break;
    case '4': 
    state = PRESCALE4; 
    break;
    case '6': 
    state = PRESCALE64; 
    break;
  default:
    break;
  }

  switch(state)
  {
  case ONCE: 
    I2Cscan(); 
    state = HELP;
    break;
  case CONT:
    I2Cscan();
    delay(1000);
    break;    
  case HELP:
    displayHelp();
    state = STOP;
    break;
  case STOP:
    break;
  case PRESCALE1:
    Prescaler = 1;
    bitClear(TWSR,TWPS0);
    bitClear(TWSR,TWPS1);
    break;
  case PRESCALE4:
    Prescaler = 4;
    bitSet (TWSR,TWPS0);
    bitClear(TWSR,TWPS1);
    break;
  case PRESCALE64:
    Prescaler = 64;
    bitSet (TWSR, TWPS0);
    bitSet (TWSR, TWPS1);
//    TWSR |= _BV (TWPS0);
//    TWSR |= _BV (TWPS1);
    break;
  default: // ignore all non commands
    break;
  }
}

char getCommand()
{
  char c = '\0';
  if (Serial.available())
  {
    c = Serial.read();
  }
  return c;
}

void displayHelp()
{
  Serial.println(F("\nArduino I2C Scanner - 0.1.03\n"));
  Serial.println(F("\ts = single scan"));
  Serial.println(F("\tc = continuous scan - 1 second delay"));
  Serial.println(F("\tq = quit continuous scan"));
  Serial.println(F("\t1 = Prescaler =  1 (default)"));
  Serial.println(F("\t4 = Prescaler =  4"));
  Serial.println(F("\t6 = Prescaler = 64"));
  Serial.println(F("\td = toggle latency delay between successful tests."));
  Serial.println(F("\tp = toggle printAll - printFound."));
  Serial.println(F("\th = toggle header - noHeader."));
  Serial.println(F("\t? = help - this page"));
  Serial.println();
}

void I2Cscan()
{
  startScan = millis();
  uint8_t count = 0;

  if (header)
  {
    Serial.print(F("TIME\tDEC\tHEX\t"));
    for (uint8_t s = 0; s < speeds; s++)
    {
      HeaderSpeed = speed[s];
      HeaderSpeed = HeaderSpeed/Prescaler;
      Serial.print(F("\t"));
      Serial.print(HeaderSpeed);
    }
    Serial.println(F("\t[KHz]"));
    for (uint8_t s = 0; s < speeds + 5; s++)
    {
      Serial.print(F("--------"));
    }
    Serial.println();
  }

  // TEST
  for (uint8_t address = 0; address < 128; address++)
  {
    bool printLine = printAll;
    bool found[speeds];
    bool fnd = false;

    for (uint8_t s = 0; s < speeds ; s++)
    {
      TWBR = (F_CPU/(speed[s]*1000) - 16)/2;
      Wire.beginTransmission (address);
      found[s] = (Wire.endTransmission () == 0);
      fnd |= found[s];
      // give device 5 millis
      if (fnd && delayFlag) delay(RESTORE_LATENCY);
    }

    if (fnd) count++;
    printLine |= fnd;

    if (printLine)
    {
      Serial.print(millis());
      Serial.print(F("\t"));
      Serial.print(address, DEC);
      Serial.print(F("\t0x"));
      Serial.print(address, HEX);
      Serial.print(F("\t"));

      for (uint8_t s = 0; s < speeds ; s++)
      {
        Serial.print(F("\t"));
        Serial.print(found[s]? F("V"):F("."));
      }
      Serial.println();
    }
  }

  stopScan = millis();
  if (header)
  {
    Serial.println();
    Serial.print(count);
    Serial.print(F(" devices found in "));
    Serial.print(stopScan - startScan);
    Serial.println(F(" milliseconds."));
  }
}

EDIT:
Ich habe das jetzt noch mal mit einer externen Spannungsversorgung (vorher über USB) und 220 Ohm Pullups getestet.
Jetzt läuft es auch mit den 100m Kabel, wenn ich nur zwei Adern für SDA und SCL verwende. Bis zu einem Takt 3,9 kHz wird ein Slave erkannt. Darüber nicht mehr.

Klemme ich zusätzlich eine Ader für GND an, läuft der Scann zwar noch durch, aber es wird kein Slave mehr erkannt. Das liegt dann wohl an der weiteren Kapazitätserhöhung.

Das ist aber schon mal ein Fortschritt. Ein BusExtender könnte die Sache vielleicht doch noch zum Laufen bringen. Beim P82B715 sprechen die zwar "nur" davon, dass die Kapazität 10x höher, also 4nF sein darf, aber die gehen dabei von 100 kHz aus- Ich wäre damit zumindest schon mal in der richtigen Größenordnung.

EDIT2:
Ich habe jetzt noch ca. 15m Steuerleitung YSLY 3x1 getestet. Das hat zwischen zwei Adern 3,3 nF und funktioniert bis 125 kHz. Lege ich die dritte Ader auf GND, geht es nur noch bis 100kHz.
Parallele Adern auf GND erhöhen die Kapazität drastisch und senken die Ü-Rate.

Ich werde es mit höheren Spannungen und Treiberbausteinen testen, auch wenn die nicht gerade preiswert sind.
Es sieht aber so aus, als wäre es für eine hohe Reichweite unumgänglich, eine separate, geschirmte Leitung für den Bus zu benutzen.

EDIT3:
Jetzt mit 100m CAT6 SSTP getestet. Ich habe es so verdrahtet, wie es in den AppNotes der Datenblätter zu den Treiberbausteinen vorgeschlagen wird. Ein verdrilltes Paar für SCL und GND, ein Paar für SDA und Vcc. 220Ohm Pullup.
Damit funktioniert der Scan bis 250 kHz und der Slave wird erkannt. Bei 400 kHz hängt sich der Aufbau auf und ich muss den Slave resetten.

Das sind doch schon mal ganz brauchbare Werte, die mich optimistisch stimmen. Mit höheren Spannungen geht bestimmt noch mehr, was aber für meine Zwecke nicht notwendig ist.

Wenn ich meinen letzten Post nur editiere, liest es vermutlich niemand mehr :slight_smile:

Ich habe nun auch CAT6 U/UTP (also ungeschirmt) getestet. Wegen der fehlenden Schirmung hatte ich mit einer geringeren Kapazität als beim SSTP gerechnet, aber das Gegenteil ist der Fall. Zwischen zwei Adern eines Adernpaars mit 100m Länge messe ich 6,5nF. Beim SSTP waren es 4nF. Wenn ich eine Ader eines anderen Paares parallel schalte steigt die Kapazität auf 7,9nF.

Wie auch immer, auch damit kann ich bis 250 kHz Daten übertragen. Der Aufbau ist gleich geblieben: 5V, 220 Ohm Pullup, 2 Adernpaare (Ucc/SDA und GND/SCL), Stromversorgung über 3,5m USB-Kabel für den ersten Arduino und über die 100m CAT6 für den zweiten Arduino. Das finde ich bemerkenswert, da eine Ader des CAT6 schon 11 Ohm Widerstand hat.

Ich bin begeistert, denn 100m CAT6 kosten keine 20 Euro :slight_smile:

Die Pullups solltest du übrigens so hoch wie möglich machen. Zu niedrig ist auch nicht gut, da man nicht mehr auf 0V kommt. Außerdem fließen bei 220 Ohm ca. 23mA.

Mit 330 Ohm funktioniert es aber schon nicht mehr. Die hohe Leitungskapazität macht hier die kleinen Widerstände notwendig, damit die Spannung wieder schnell genug ansteigt.

[Korinthenkackermodus on]
Da der AVR die 220 Ohm nicht ganz gegen GND gezogen bekommt, sind es nicht ganz 23mA.
Und da der Pegel immer nur kurz gegen GND gezogen wird, fließt im Mittel ca. die Hälfte des Stroms - dafür aber auf zwei Adern parallel :slight_smile:
[Korinthenkackermodus off]
Glücklich bin darüber auch nicht, aber das wird hier ja auch weit außerhalb der Spezifikationen betrieben und vor allen Dingen auch ohne Treiber IC. Dafür finde ich das schon ganz ordentlich.

Hallo zusammen,

ich hole diesen Beitrag mal hoch, da ich Momentan vor dem gleichen Problem stehe.

Betreibe an einem Mega2560 über I2C, ca 8m Cat7 und 1kohm Pullup einen MPR121
Funktioniert auch soweit, jedoch habe ich sporadisch Störungen/falsche Werte.
Jetzt würde ich gerne den Bus-Takt reduzieren um dieses Problem zu beheben.

die Formel "freq = clock / ( 16+ ( 2 * TWBR * prescaler ))" ist soweit klar, verstehe sie auch.

Im Hauptprogramm wird der Bus mit
i2cInit(); initialisiert

und in der I2C.h steht folgendes:


void i2cInit(void)
{
// set i2c bit rate to 40KHz
i2cSetBitrate(100);
// enable TWI (two-wire interface)
sbi(TWCR, TWEN); // Enable TWI

//output on ADC4 (PC4, SDA)
DDRC |= 0b00010011;
// Pull-ups on I2C Bus
PORTC = 0b00110000;
}

void i2cSetBitrate(unsigned short bitrateKHz)
{
unsigned char bitrate_div;
// set i2c bitrate
// SCL freq = F_CPU/(16+2*TWBR))
cbi(TWSR, TWPS0);
cbi(TWSR, TWPS1);

//calculate bitrate division
bitrate_div = ((F_CPU/4000l)/bitrateKHz);
if(bitrate_div >= 16)
bitrate_div = (bitrate_div-16)/2;
outb(TWBR, bitrate_div);
}

Wie kann ich den Takt verändern?

Um ganz sicher zu gehen, würde ich bei der Länge mit I2C-Bus-Extender arbeiten.
Dann brauchst du nichts am Takt zu ändern.

Indem du i2cSetBitrate() mit deiner gewünschten Frequenz (in kHz) als Argument aufrufst. Wenn du schon in den Header schaust, dann sollte das sofort ersichtlich sein

Oder du setzt die Register per Hand. Die zwei Bits TWPS0/1 im TWSR Register geben den Prescaler an. Und TWBR entsprechend der Formel. Normal sind beide Bits 0 und der Prescaler ist damit 1. Aber du kannst den auch auf 4 erhöhen um auf recht niedrige Frequenzen zu kommen
Das ist in Post #1 erklärt.

Aber wie gesagt ist I2C nicht für so lange Kabel gedacht. Das hängt mit deren Kapizität zusammen

HotSystems:
Um ganz sicher zu gehen, würde ich bei der Länge mit I2C-Bus-Extender arbeiten.
Dann brauchst du nichts am Takt zu ändern.

Das wäre Plan B. Habe mittlerweile viele Threads gelesen wo durch sehr niedrigem Takt die Reichweite deutlich gesteigert werden konnte..

Serenifly:
Indem du i2cSetBitrate() mit deiner gewünschten Frequenz (in kHz) als Argument aufrufst. Wenn du schon in den Header schaust, dann sollte das sofort ersichtlich sein

Fürs Verständnis: wenn ich 2 eintrage, dann habe ich 2kHz, wenn 26, dann 26khz? So einfach?

Serenifly:
Oder du setzt die Register per Hand. Die zwei Bits TWPS0/1 im TWSR Register geben den Prescaler an. Und TWBR entsprechend der Formel. Normal sind beide Bits 0 und der Prescaler ist damit 1. Aber du kannst den auch auf 4 erhöhen um auf recht niedrige Frequenzen zu kommen
Das ist in Post #1 erklärt.

wie würde das in meinem Fall aussehen, bzw. welche Zeile(n) muss angepasst werden?

Sry, dass ich mich etwas dumm anstelle, bin nicht so fit im Programmieren :frowning:

Für solch niedrige Frequenzen muss man es per Hand machen. Die fertige Funktionen arbeitet nämlich nur mit einem Prescaler von 1 und passt den nie an

wie würde das in meinem Fall aussehen, bzw. welche Zeile(n) muss angepasst werden?

Ich habe schon alles nötige dazu gesagt. Die Formel hast du wie du sagst verstanden. Also wo ist das Problem das umzusetzen? Es sind doch nur 2 Register und 2 Bits

Prescaler 64 wählen:

bitSet(TWSR, TWPS0);
bitSet(TWSR, TWPS1);

Das heißt der Prozessor-Takt wird durch 64 geteilt

Die Formel geht so:
freq = clock / (16 + (2 * TWBR * prescaler))

1kHz = 16MHz / 16000

Daraus folgt:
16000 = 16 + (2 * TWRB * 64)
16000 = 16 + (128 * TWRB)
15984 = 128 * TWRB
TWRB ~ 125

Also:

TWRB = 125;

Dabei beachten dass das ein 8 Bit Register ist. Also nur Werte bis 255 annehmen kann

Ich habe dies hier noch gefunden, aber selbst nicht getestet.
Könnte dein Vorhaben deutlich vereinfachen.

HotSystems:
Ich habe dies hier noch gefunden, aber selbst nicht getestet.
Könnte dein Vorhaben deutlich vereinfachen.

Das fasst aber auch den Prescaler nicht an:

void twi_setFrequency(uint32_t frequency)
{
  TWBR = ((F_CPU / frequency) - 16) / 2;
}

Ist also eher dazu gedacht zwischen Frequenz von mehreren 100 kHz umzuschalten

Serenifly:
Ist also eher dazu gedacht zwischen Frequenz von mehreren 100 kHz umzuschalten

Ist doch aber genau das, was der TO möchte und war als Alternative gedacht, wenn er mit der Berechnung nicht klar kommt.

HotSystems:
Ist doch aber genau das, was der TO möchte und war als Alternative gedacht, wenn er mit der Berechnung nicht klar kommt.

Rechne mal durch auf was für eine Frequenz du mit Prescaler 1 minimal kommst. Dann merkst du dass es nicht die Lösung ist.

In der Beschreibung der Methode ist das auch angedeutet:

Accepted values are 100000 (standard mode) and 400000 (fast mode)

Das ist nicht universal