Pages: [1]   Go Down
Author Topic: I2C-Takt reduzieren  (Read 253 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 7
Posts: 20
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hallo,

wie kann man am elegantesten den Takt der I2C-Schnittstelle reduzieren?

Nach langer Suche über Google habe ich nur eine Lösung gefunden, bei der man in einer Library Veränderungen vornehmen muss, dann zwei andere Dateien löschen muss und dann die Library neu compilieren muss.

Das finde ich ziemlich umständlich und auch Fehleranfällig, weil sich diese Änderungen dann auf alle Programme auswirken, die man danach mit der Arduino-IDE compiliert.

Gibt es vielleicht einen Befehl, den man einfach im Quelltext des Sketches einfügen kann, der dann alles übernimmt, was man braucht? Es müsste doch vermutlich nur ein einziges Register (der Teiler) im AVR geändert werden.


BTW: Ich suche die langsamste Geschwindigkeit, die möglich ist, denn ich möchte nur wenige Bytes auf langen, ungeschirmten Leitungen übertragen.
Logged

Offline Offline
Faraday Member
**
Karma: 97
Posts: 3524
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Es müsste doch vermutlich nur ein einziges Register (der Teiler) im AVR geändert werden.
Ja. Die Register kann man immer überschreiben. Das ist auch bei anderen Komponenten wie den Timern oder dem ADC der Fall. Da wird in der Regel der Prozessor-Takt durch einen Frequenzteiler (der Prescaler) geteilt und damit die Komponenten getaktet. Den Frequenzteiler kann man über Register einstellen.


Für I2C ist es hier erklärt:
http://www.gammon.com.au/forum/?id=10896
Etwas nach unten zu "Timing" scrollen

Was fehlt da fehlt ist die Prescaler Tabelle. Die ist im Atmega328 Datenblatt auf Seite 243. Das wird über das TWSR Register gemacht (Two Wire Status Register). Da gibt es die zwei Bits TWPS1 und TWPS0:
Quote
TWPS1 TWPS0 Prescaler
0               0                 1
0               1                  4
1                0                 16
1                1                  64

Bei I2C wird der Takt durch eine Kombination aus Precaler dem TWBR (Two Wire Bit Rate) Register eingestellt. Nach folgender Formel:
Quote
freq = clock / (16 + (2 * TWBR * prescaler))
clock ist der Systemtakt von 16MHz

Um z.B. den Prescaler auf 4 zu setzen, musst du das TWSP0 Bit setzen (standardmäßig sind beide Bits 0):
Code:
TWSR |= _BV (TWPS0);

Hier sind die Werte für gängige Frequenzen:
Quote
TWBR   prescaler   Frequency
 
  12       1       400   kHz  (the maximum supported frequency)
  32       1       200   kHz
  72       1       100   kHz  (the default)
 152       1        50   kHz
  78       4        25   kHz
 158       4        12.5 kHz

Langsamer geht aber vielleicht auch noch, da der Precaler bis 64 geht
« Last Edit: July 23, 2014, 11:27:48 am by Serenifly » Logged

Online Online
God Member
*****
Karma: 13
Posts: 842
ATmega 2560
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hallo,

eine Libary muß man nicht neu kompilieren. Das ist wie wenn man Änderungen an seinem Sketch vornimmt. Vorm neuen Upload zum Arduino wird eh neu kompiliert. That's it.

Wenn Du Dir den I2C Scanner als Bsp. nimmst, sieht man ganz unten die Funktion zum komfortablen ändern vom Takt.
Das Teil finde ich richtig gut. Da hat sich mal jemand Gedanken gemacht. Es werden Geräte am Bus gesucht und mit allen Frequenzen getestest und ordentlich im seriellen Monitor dargestellt. Ganz saubere Sache.

* Multispeed_I2C_Scanner.ino (3.81 KB - downloaded 10 times.)
Logged

Tschau
Doc Arduino

Offline Offline
Faraday Member
**
Karma: 97
Posts: 3524
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Alle Frequenzen sind das sind nicht. Sondern minimal 50kHz. Mit Prescaler 1  sind minimal ca. 30,4kHz für TWBR = 255 möglich. Es geht aber auch noch langsamer smiley
Logged

Online Online
God Member
*****
Karma: 13
Posts: 842
ATmega 2560
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hallo,

es macht ja nun keinen Sinn in 1pHz Frequenzsprüngen den Bus zu scannen ...   smiley-wink
Logged

Tschau
Doc Arduino

Offline Offline
Newbie
*
Karma: 7
Posts: 20
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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 smiley

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.

Logged

Online Online
God Member
*****
Karma: 13
Posts: 842
ATmega 2560
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Tschau
Doc Arduino

Online Online
Edison Member
*
Karma: 28
Posts: 2135
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote from: 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
Logged

Kein technischer Support über die Private Nachricht!

Forum Moderator
BZ (I)
Offline Offline
Brattain Member
*****
Karma: 249
Posts: 21195
+39 349 2158303
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Grüße Uwe
Logged

Offline Offline
Faraday Member
**
Karma: 97
Posts: 3524
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
http://www.gammon.com.au/forum/?id=10896&reply=5#reply5

Aber richtige Treiber ICs sind wie gesagt die bessere Option


Nachtrag:
Quote
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
« Last Edit: July 23, 2014, 02:35:04 pm by Serenifly » Logged

Offline Offline
Newbie
*
Karma: 7
Posts: 20
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Germany
Online Online
Faraday Member
**
Karma: 56
Posts: 2981
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
...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 !
Logged

Offline Offline
Newbie
*
Karma: 7
Posts: 20
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.


Code:
//
//    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.

« Last Edit: July 24, 2014, 05:23:34 pm by Bleiente » Logged

Pages: [1]   Go Up
Jump to: