Freitag, 15. November 2013

Dual-Core-Probleme mit der Echtzeituhr

 Bei der WeltWortUhr ist ein interessantes Phänomen aufgetreten. Manchmal zeigt eine der beiden Uhren die falsche Zeit an, obwohl beide Steuerplatinen die selbe Echtzeituhr über den I2C-Bus abfragen.

 Dank der "Dual-Core-Fähigkeit" der neuen 42ger-Classic-Platine konnte ich das Problem jetzt nachstellen (und hoffentlich lösen).

 In der Firmware für die Wortuhren wird das Einlesen der Zeit ja von einem Rechtecksignal der Echtzeituhr via Interrupt an dem Atmega328 getriggert.

 Das soll bedeuten: die DS1307 hat einen Pin, an dem sie ein Rechtecksignal ausgeben kann. Bei der Wortuhr-Firmware steht das auf 1 Hz, also einmal pro Sekunde wird eine Interrupt-Funktion auf dem Atmega328 aufgerufen. Das wird mitgezählt und nach 60 Sekunden wird die Uhrzeit von der DS1307 neu eingelesen. Als Hintergrund: das Einlesen kostet Zeit und führt zu einem sichtbaren Flackern. Mit neuen Minuten ändert sich die Anzeige sowieso, da merkt man das Einlese-Flackern nicht. Aber diese Triggerung bewirkt, daß beide MCUs exakt zur gleichen Zeit die DS1307 auslesen möchten.

 Und jetzt zurück zum Dual-Core-Problem: das Auslesen der DS1307 sieht vor, zuerst den "Register-Pointer" auf die richtige Stelle zu setzten:

  Wire.beginTransmission(_address);
  Wire.write((uint8_t)0x00);
  returnStatus = Wire.endTransmission();

... und dann die Daten zu lesen:

  Wire.requestFrom(_address, 7);
  _seconds = bcdToDec(Wire.read() & 0x7f);
  _minutes = bcdToDec(Wire.read());
  _hours = bcdToDec(Wire.read() & 0x3f);
  _dayOfWeek = bcdToDec(Wire.read());
  _date = bcdToDec(Wire.read());
  _month = bcdToDec(Wire.read());
  _year = bcdToDec(Wire.read());

Offensichtlich kann es im Dual-Core-Betrieb passieren, daß die erste MCU den Register-Pointer setzt, dann die zweite MCU den Register-Pointer erneut setzt, dann eine MCU die richtige Zeit liest und dann die zweite MCU im falschen Bereich liest, weil der Register-Pointer ja jetzt an der falschen Stelle steht!

Nach meinen Tests schafft folgende Code-Änderung Abhilfe:

1. Register-Pointer setzten, den Bus aber nicht freigeben (false):

  // Reset the register pointer
  Wire.beginTransmission(_address);
  Wire.write((uint8_t)0x00);
  result = Wire.endTransmission(false);

2. Daten lesen:

  count = Wire.requestFrom(_address, 7);
  _seconds = bcdToDec(Wire.read() & 0x7f);
  _minutes = bcdToDec(Wire.read());
  _hours = bcdToDec(Wire.read() & 0x3f);
  _dayOfWeek = bcdToDec(Wire.read());
  _date = bcdToDec(Wire.read());
  _month = bcdToDec(Wire.read());
  _year = bcdToDec(Wire.read());

3. Bus freigeben:

  result = Wire.endTransmission(true);

Vielleicht hilft das ja dem ein oder anderen Mitprogrammierer, wenn er auch "Kollisionen" auf dem I2C-Bus im Multi-Mikrocontroller-Betrieb hat.

Keine Kommentare:

Kommentar veröffentlichen