Version | Anpassungen |
---|---|
1.0 (2011-12-30) | Initiale Version |
2.0 (2013-12-08) | Deutlich erweiterte Version |
3.0 (2022-11-04) | Zur Benutzung mit Arduino überarbeitet. Ebenfalls konnte vieles vereinfacht werden, weil vieles durch die Kompiler-Optimierung der modernen GCC-Version unnötig wurde. Für Details siehe Kommentare in Modifications.txt. |
4.0 (2022-11-13) | Fehler beim Erkennen der Startbedingung behoben: SCL wurde nicht immer frei gegeben. |
Die Idee ist, intelligente Sensoren und Aktoren durch Einsatz externer Mikroprozessoren zu
realisieren. Die Kommunikation soll per I²C stattfinden.
Meine Wahl fiel auf den ATtiny85.
Dieser Chip neben dem USI (Universal Serial
Interface), mit dem man recht einfach eine interrupt-gesteuerte
I²C-Kommunikation implementieren kann. Neben den für den I²C-Bus benötigten beiden Leitungen stehen
vier freie I/O-Leitungen, die als digitale I/O-Kanäle oder als analoge Eingänge (ADC)
betrieben werden können, zur Verfügung. Zwei Timer, über die u.a. eine PWM-Steuerung eingerichtet
werden kann, runden die Möglichkeiten dieses Chips ab. Außerdem ist er in einer DIL-8-Version erhältlich.
DIL-8:
Damit ist er einerseits groß genug, dass man ihn mit hobbymäßigen Werkzeug anschließen kann, andererseits klein genug, um in ein akzeptables Sensorgehäuse zu passen.
Benötigt man mehr Pins, bieten andere ATtiny-Versionen mit USI-Komponente an (ATtiny25, ATtiny45, ATtiny85, ATtiny24, ATtiny44, ATtiny84, ATtiny2313, ATtiny4313).
ATtinys programmieren mit dem Arduino-Overhead? Ja, es geht! Der Compiler filtert eine Menge unnützer Dinge heraus und optimiert den Rest recht gut. Die Binaries sind erstaunlich klein.
Inhaltsverzeichnis
Das ZIP-Archiv für Bibliothek UrsUsiTwiSlave zum Download. Die entpackten Dateien ins Verzeichnis <user>\Documents\Arduino\libraries kopieren (siehe Installing Additional Arduino Libraries).
Das Archiv enthält die Bibliotheksdateien
UrsUsiTwiSlave.tmp.h ist eine Vorlage für eine Konfigurationsdatei. Diese muss ins Projektverzeichnis kopiert und in UrsUsiTwiSlave.cfg.h umbenannt werden. Dann werden die notwendigen Anpassungen in der kopierten Datei vorgenommen.
UrsUsiTwiSlave.cfg.h wird von UrsUsiTwiSlave.h eingebunden. UrsUsiTwiSlave.h befindet sich im Bibliotheksverzeichnis. In der Arduino-IDE und im Visual-Studio-Plugin Visual Micro ist das Projektverzeichnis nicht in den Include-Pfaden eingebunden. Als Konsequenz lassen sich Dateien aus dem Projektverzeichnis nicht in Bibliotheksfunktionen einbinden .
Arduino-IDE: Man kopiert alle Quelldateien ins Projektverzeichnis.
Visual Micro: Bei Projekteigenschaften gibt es die Option Extra Flags. Dort kann man einen Verweis auf das Projektverzeichnis anbringen, z.B. -I"C:\Users\Ulli\Documents\Arduino\i2c-slave\i2c-slave".
Die Beispiele enthalten komplette Visual-Micro-Projekte. Arduino-IDE-Nutzer verwenden einfach nur die .ino-Datei.
Beispiel
Die erste Version eines USI-Slave-Bibliothek recht einfach aufgebaut. Die neue Version v2 bietet einen deutlich höheren Komfort.
Diese Bibliothek basiert auf der Atmel AppNote AVR312 - Using the USI module as a TWI slave. Sie stellt ein I²C-Framework auf Basis einer USI zu Verfügung, das über eine Konfigurationsdatei verschiedenen Projektbedürfnissen angepasst werden kann.
Das I²C-Protokoll / die I²C-Datenübertragung besteht aus folgenden Komponenten:
I²C-Geräte haben üblicherweise eine feste Bus-Adresse. Um mehrere gleichartige Geräte am gleichen Bus betreiben zu können, müssen sie unterschiedliche Adressen besitzen. Dies geschieht meist, indem einige Pins des ICs zur Modifikation der Adresse benutzt werden. Der μC muss dazu die Pins auslesen, die Adresse berechnen und der Bibliothek bei der Initialisierung (TwiInitialize) oder später (TwiSetAddress) zur Verfügung stellen.
Pins sind jedoch auf vielen ATtinys knapp. Deshalb besteht die Möglichkeit mit festen Adressen zu arbeiten. Dies spart etwas von dem geringen Speicher der ATtinys und ist auch ein wenig schneller. Der Nachteil ist, dass man für jedes Gerät ein eigenes Binary benötig. Und, wenn man nicht sorgfältig dokumentiert hat, welche Adresse man einprogrammiert hat, ist das Gerät meist wertlos.
Die Bibliothek bietet eine dritte Möglichkeit. Man kann sie so konfigurieren, dass man auch über die Broadcast-Adresse 0 mit dem Gerät kommunizieren kann. Die Callback-Funktion TwiTransmissionStart meldet sowohl die Datenrichtung (Lesen/Schreiben) als auch ob die Broadcast-Adresse verwendet wurde. Wird die Datenübertragung über die Broadcast-Adresse betrieben, kann beim Lesen das Gerät die eingestellte Adresse zurück liefern und beim Schreiben die Geräteadresse entsprechend einstellen und z.B. im EEPROM ablegen. Das folgende Code-Snippet verdeutlicht dieses Vorgegen:
TwiTransferType currentTransferType; // Speicher für Übertragungsmodus
uint8_t myI2cAddress; // Aktuelle Geräte-Adresse
void setup() {
myI2cAddress = EEPROM.read(0); // Geräte-Adresse aus dem EEPROM lesen
TwiInitialize(myI2cAddress); // Bibliothek mit dieser Adresse initialisieren
// ...
}
void TwiTransmissionStart(TwiTransferType rw) {
currentTransferType = rw; // Übertragungsmodus speichern
}
uint8_t TwiDataRequest() {
if (TwiIsBroadcast(currentTransferType))
return myI2cAddress; // Geräte-Adresse zurück liefern
// ...
}
void TwiDataReceived(uint8_t data) {
if (TwiIsBroadcast(currentTransferType)) {
myI2cAddress = data; // Neue Geräte-Adresse benutzen
EEPROM.write(0, data);
TwiSetAddress(data);
return;
}
// ...
}
Die Pins sind durch die Beschaltung der USI (Universal Serial Interface) festgelegt (in Klammern die Pin-Nummern im DIL-Gehäuse):
MCU | SDA | SCL |
---|---|---|
ATtiny24, 44, 84 | PA6 (7) | PA4 (9) |
ATtiny25, 45, 85 | PB0 (5) | PB2 (7) |
ATtiny26 | PB0 (1) | PB2 (3) |
ATtiny261, 461, 861 | PB0 (1) | PB2 (3) |
ATtiny2313, ATtiny4313 | PB5 (17) | PB7 (19) |
ATmega165, ATmega325, ATmega3250, ATmega645, ATmega6450, ATmega329, ATmega3290, ATmega169 |
PE5 | PE4 |
Das Modul besitzt zwei Adress-Modi. Über die Konstanten TWI_FIXED_TWI_ADDRESS kann diese konfiguriert werden:
Adress-Modi | |
NONE | Die Präprozessor-Konstante TWI_FIXED_TWI_ADDRESS hat die Ausprägung NONE. Die Geräte-Adresse wird bei der Modul-Initialisierung über void TwiInitialize(uint8_t TwiAddress) festgelegt und kann zu einem späteren Zeitpunkt über void TwiSetAddress(uint8_t TwiAddress) geändert werden. |
FIXED | Die Geräte-Adresse wird über die Präprozessor-Konstante TWI_FIXED_TWI_ADDRESS vorgegeben und beim Kompilieren fest eingestellt. Die Initialisierung erfolgt über void TwiInitialize (void) deklariert. TwiSetAddress steht nicht zur Verfügung. Mögliche Werte für TWI_FIXED_TWI_ADDRESS sind 1..127. |
Die Definition von TWI_FIXED_TWI_ADDRESS und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird beim Kompilieren überprüft.
In beiden Adress-Modi kann über TWI_ACCEPT_BROADCAST_MSG festgelegt werden, ob zusätzlich zu der oben festgelegten Adresse auch die I²C-Broadcast-Adresse 0 akzeptiert wird. Mögliche Werte sind TRUE oder FALSE. Die Definition von TWI_ACCEPT_BROADCAST_MSG und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird beim Kompilieren überprüft.
Beim Start einer Datenübertragung wird die Callback-Funktion TwiTransmissionStart(TwiTransferType rw) aufgerufen. rw gibt an, welche Art von Übertragung gestartet wurde:
enum class TwiTransferType {
TwiRead = 0, // Der Master will Daten von diesem Slave empfangen
TwiWrite = 1, // Der Master sendet Daten an diesen Slave
#if (TWI_ACCEPT_BROADCAST_MSG)
BroadcastRead = 2, // Der Master will Daten von allen Slaves empfangen
BroadcastWrite = 3, // Der Master sendet Daten an alle Slaves
#endif // ACCEPT_BROADCAST_MSG
};
Zur Vereinfachung dient die Methode
Methode | Bedeutung |
---|---|
bool TwiIsBroadcast(TwiTransferType rw) | Liefert true, wenn die Übertragung über die Broadcast-Adresse 0 gestartet wurde. |
Es stehen drei Modi zur Auswahl:
Betriebs-Modi | |
STANDARD | Synchronbetrieb. Es gibt keine Unterstützung durch die Bibliothek. Zu sämtlichen relevanten Ereignissen werden Callback-Funktionen aufgerufen, die von der Applikation implementiert werden müssen. |
SIMPLE | Asynchronbetrieb. Es wird jeweils nur ein einzelnes Byte zur Verfügung gestellt. Lese- und Schreibzugriffe des Masters werden vollkommen über interne Funktionen bedient. Der Datenaustausch erfolgt über die Variable TwiData. |
REGISTER | Synchronbetrieb. Die Daten werden in einem strukturierten und adressierbaren I²C-Register zur Verfügung gestellt bzw. dort abgelegt. Lese- und Schreibzugriffe des Masters werden vollkommen über interne Funktionen bedient und können durch zusätzliche Callback-Funktionen überwacht werden. |
Der Modus wird über die Präprozessor-Konstante TWI_OPERATION_MODE eingestellt. Mögliche Werte sind TWI_MODE_STANDARD, TWI_MODE_SIMPLE oder TWI_MODE_REGISTER. Die Definition von TWI_OPERATION_MODE und die Belegung mit einem der zuvor genannten Werte ist obligatorisch und wird beim Kompilieren überprüft.
Es werden keine Automatismen implementiert. Die Callback-Funktionen
Methode | Bedeutung |
---|---|
void TwiTransmissionStart(TwiTransferType rw) | Eine neue Datenübertagung hat begonnen (Start-Condition erkannt und übertragene Geräteadresse passt). Zu rw s.o. |
void TwiTransmissionStop() | Die Datenübertragung ist beendet (Stop- oder Repeated-Start-Condition erkannt). |
uint8_t TwiDataRequest() | Der Master fordert Daten an. TwiDataRequest liefert jeweils das nächste Byte. |
void TwiDataReceived(uint8_t data) | Der Master sendet Daten, data ist das gesendete Byte. |
müssen von der Applikation implementiert werden.
Wird eine der Methoden von der Applikation nicht benötigt, kann sie als static inline in UrsUsiTwiSlave.cfg.h hinterlegt werden. z.B.:
static inline uint8_t TwiDataRequest() {
return 0; // Immer 0 senden
}
Der Compiler wird sie heraus optimieren.
Das folgende Beispiel zeigt die Verwendung dieses Modus.
Die Konfigurationsdatei für dieses Beispiel:
#pragma once
/*
* UsiTwiSlave.cfg.h
*/
// Diese Datei enthält projektspezifische Einstellungen für das USI-TWI-Slave-Modul.
// --- Adress-Modus -----------------------------------------------------------------------------------------------------
#define TWI_FIXED_TWI_ADDRESS 0x30 // Feste Bus-Adresse des TWI-Slaves.
#define TWI_ACCEPT_BROADCAST_MSG FALSE // Transferanforderung über die Broadcast-Adresse (0) werden nicht akzeptiert.
// --- Betriebs-Modus ---------------------------------------------------------------------------------------------------
#define TWI_OPERATION_MODE TWI_MODE_STANDARD // Einstellung des Betriebs-Modus: Das API meldet I2C-Aktivitäten
// über die Callback-Funktionen
// TwiTransmissionStart, TwiTransmissionStop, TwiDataReceived, TwiDataRequest.
Der Applikation implementiert die geforderten Methoden. Wird ein Byte empfangen, wird der um 1 erhöhte Wert abgelegt und bei der nächsten Datenanforderung zurück geliefert. Außerdem wird, abhängig vom LSB des empfangenen Bytes Pin 4 (PB4 beim ATtiny85) geschaltet. Eine LED an diesem Pin kann zur Kontrolle des Empfangs dienen.
/*
Name: i2c_slave.ino
Created: 2022-11-05
Author: Ulli
*/
#include "UrsUsiTwiSlave.h"
uint8_t twiData = 0; // Zwischenspeicher für das empfangene Byte
void setup() {
TwiInitialize();
pinMode(4, OUTPUT);
}
void loop() {
// Nothing to do
}
uint8_t TwiDataRequest() {
return twiData;
}
void TwiDataReceived(uint8_t data) {
twiData = data + 1;
digitalWrite(4, data & 0x01);
}
void TwiTransmissionStart(TwiTransferType rw) {} // Leere Funktion
void TwiTransmissionStop() {} // Leere Funktion
Das Beispiel befindet sich im Ordner examples\i2c-slave-standard.
In diesem Modus wird jeweils nur ein einzelnes Byte übertragen. Dieses Byte wird jeweils in der Variablen TwiData zur Verfügung abgelegt. Über die Variable TwiDataReceivedFlag kann abgefragt werden, ob ein neues Byte empfangen wurde. Nach Auswertung des Flags muss dieses von der Applikation gelöscht werden.
Die Implementierung dieser Variante ist einfacher als die anderen, hat aber den Nachteil, das Daten verloren gehen können.
Das folgende Beispiel zeigt die Verwendung dieses Modus.
Die Konfigurationsdatei für dieses Beispiel:
#pragma once
/*
* UsiTwiSlave.cfg.h
*/
// Diese Datei enthält projektspezifische Einstellungen für das USI-TWI-Slave-Modul.
// --- Adress-Modus -----------------------------------------------------------------------------------------------------
#define TWI_FIXED_TWI_ADDRESS 0x30 // Feste Bus-Adresse des TWI-Slaves.
#define TWI_ACCEPT_BROADCAST_MSG FALSE // Transferanforderung über die Broadcast-Adresse (0) werden nicht akzeptiert.
// --- Betriebs-Modus ---------------------------------------------------------------------------------------------------
#define TWI_OPERATION_MODE TWI_MODE_SIMPLE // Einstellung des Betriebs-Modus: Das API meldet I2C-Aktivitäten
// über das Flag TwiDataReceivedFlag und die Variable TwiData.
Der Applikation implementiert die geforderten Methoden. Wird ein Byte empfangen, wird der um 3 erhöhte Wert abgelegt und bei der nächsten Datenanforderung zurück geliefert. Außerdem wird, abhängig vom LSB des empfangenen Bytes Pin 4 (PB4 beim ATtiny85) geschaltet. Eine LED an diesem Pin kann zur Kontrolle des Empfangs dienen.
/*
Name: i2c_slave.ino
Created: 2022-11-05
Author: Ulli
*/
#include "UrsUsiTwiSlave.h"
void setup() {
TwiInitialize();
pinMode(4, OUTPUT);
}
void loop() {
if (TwiDataReceivedFlag) {
TwiData = TwiData + 3;
digitalWrite(4, TwiData & 0x01);
TwiDataReceivedFlag = false;
}
}
Das Beispiel befindet sich im Ordner examples\i2c-slave-simple.
In diesem Modus werden die Daten in einem strukturierten und adressierbaren I²C-Register zur Verfügung gestellt bzw. dort abgelegt. Jede Schreiboperation des Maters beginnt in diesem Betriebsmodus zunächst mit der Übertragung des Registerindex (ein Byte). Jedes weitere Byte wird dann an der vom Index angegeben Stelle abgelegt und der Index inkrementiert.
Das Lesen erfolgt ebenfalls an der vom Index angegeben Stelle. Durch das Schreiben eines einzelnen Bytes (= Registerindex) vor dem Lesebefehl kann diese Stelle verschoben werden.
Die Aufbau des I²C-Registers wird über den Typ I2CRegister_t festgelegt. Dieser Typ muss in UsiTwiSlave.cfg.h definiert werden. Am sinnvollsten wird er als struct hinterlegt:
typedef struct
{ ...
} I2CRegister_t;
Es sind aber auch andere Darstellungen möglich. Intern wird auf das Register stets mit dem Typ-Konverter
((uint8_t *)&I2CRegister)[]
zugegriffen.
Die Verwaltung des Index (RegisterIndex) zum Zugriff auf das I²C-Register erfolgt intern. Wird der Index durch den Master auf einen Wert gesetzt, der größer als die Größe des Registers ist, wird der Index auf das letzte Byte des Registers gesetzt. Ähnliches gilt beim Inkrementieren des Index. Würde er zu Zugriffen außerhalb das Register-Speicherraums führen, wird das Inkrementieren unterdrückt.
Folgende Callback-Methoden müssen von der Applikation implementiert werden:
Methode | Bedeutung |
---|---|
void TwiTransmissionStart(TwiTransferType rw) | Eine neue Datenübertagung hat begonnen (Start-Condition erkannt und übertragene Geräteadresse passt). Zu rw s.o. |
void TwiTransmissionStop() | Die Datenübertragung ist beendet (Stop- oder Repeated-Start-Condition erkannt). |
bool TwiDataReceived(uint8_t index, uint8_t data) | Der Master sendet Daten index: Registerindex, an den das Byte geschrieben würde. data ist das gesendete Byte. Rückgabewert: true, wenn das Byte bereits ausgewertet wurde und nicht ins Register geschrieben werden soll. false: Das Byte wird an die Stelle index in das Register geschrieben. |
bool TwiDataRequest(uint8_t index, uint8_t* pData) | Der Master fordert Daten an. index: Registerindex, aus dem das Byte gelesen würde. pData: Pointer zu Übergabe des zu sendenden Bytes. Rückgabewert: true: Das Byte, das über pData beschrieben wurde, wird gesendet. false: Das Byte, auf das index im Register zeigt, wird gesendet. |
Wird eine der Methoden von der Applikation nicht benötigt, kann sie als static inline in UrsUsiTwiSlave.cfg.h hinterlegt werden. z.B.:
static inline bool TwiDataReceived(uint8_t index, uint8_t data) {
return false; // Byte ins Register speichern.
}
Der Compiler wird sie heraus optimieren.
Das Beispiel zu diesem Betriebsmodus zeigt, wie man komplexere Protokolle gestalten kann. Die Geräteadresse des Slaves soll per I²C eingestellt und im EEPROM gespeichert werden können. Das Senden einer neuen und das Abfragen der aktuellen Adresse erfolgt über die Broadcast-Adresse 0. Schreib- und Lese-Operation erfolgen über die reguläre-Adresse in das / aus dem Register. Zur sichtbare Kontrolle wird eine LED nach jedem Übertragungsvorgang umgeschaltet. Das Beispiel funktioniert mit dem I²C-Master, wie er in Arduino Software I2C Master: I²C auf allen Pins, Beispiel 2 beschrieben ist.
Die Konfigurationsdatei für dieses Beispiel:
#pragma once
/*
* UsiTwiSlave.cfg.h
*/
// Diese Datei enthält projektspezifische Einstellungen für das USI-TWI-Slave-Modul.
// --- Adress-Modus ------------------------------------------------------------------------------------------------------
#define TWI_FIXED_TWI_ADDRESS NONE // Variable Bus-Adresse des TWI-Slaves.
#define TWI_ACCEPT_BROADCAST_MSG TRUE // Transferanforderung über die Broadcast-Adresse (0) werden akzeptiert.
// --- Betriebs-Modus ----------------------------------------------------------------------------------------------------
#define TWI_OPERATION_MODE TWI_MODE_REGISTER // Einstellung des Betriebs-Modus
//******************************************************************
// Festlegung der I2C-Register-Struktur
//******************************************************************
typedef struct
{
uint8_t Version;
uint8_t Count;
} I2CRegister_t;
#define I2C_REGISTER_DEFAULT 13, 0 // Vorbelegung des Registers. Wird so benutzt: I2CRegister = {I2C_REGISTER_DEFAULT};
#define TWI_WRITE_PERMIT 1 // Schreiben ab dieser Position erlauben
Das I²C-Register enthält zwei Byte. Das erste Byte ist eine Versionsnummer, die nicht überschrieben werden soll (TWI_WRITE_PERMIT 1). Die Vorbelegung ist 13. Das zweite Feld count wird mit 0 vorbelegt (I2C_REGISTER_DEFAULT 13, 0).
Der Programmcode des Slaves:
/*
Name: i2c_slave.ino
Created: 2022-11-07
Author: Ulli
*/
#include <EEPROM.h>
#include "UrsUsiTwiSlave.h"
TwiTransferType currentTransferType; // Speicher für Übertragungsmodus
uint8_t myI2cAddress; // Aktuelle Geräte-Adresse
bool ledState = false;
void setup() {
myI2cAddress = EEPROM.read(0); // Geräte-Adresse aus dem EEPROM lesen
if (myI2cAddress == 0xFF) // EEPROM nicht belegt
myI2cAddress = 0x30; // 0x30 ist die Voreinstellung
TwiInitialize(myI2cAddress); // Bibliothek mit dieser Adresse initialisieren
pinMode(4, OUTPUT);
}
void loop() {
// Nichts zu tun
}
void TwiTransmissionStart(TwiTransferType rw) {
currentTransferType = rw; // Übertragungsmodus speichern
}
void TwiTransmissionStop() { // Übertragung beendet
// Hier erfolgt die Reaktion auf die Änderung der Registerinhalte
ledState = !ledState;
digitalWrite(4, ledState);
}
bool TwiDataRequest(uint8_t index, uint8_t* pData) {
if (TwiIsBroadcast(currentTransferType)) {
*pData = myI2cAddress; // Geräte-Adresse zurück liefern
return true;
}
else
return false; // Byte aus dem Register lesen.
}
bool TwiDataReceived(uint8_t index, uint8_t data) {
if (TwiIsBroadcast(currentTransferType)) {
myI2cAddress = data; // Neue Geräte-Adresse benutzen
EEPROM.write(0, data);
TwiSetAddress(data);
return true; // Auswertung des Byte abgeschlossen, nicht ins Register speichern.
}
// Hier erfolgt ggf. die Auswertung des gesendeten Bytes.
return false; // Byte ins Register speichern.
}
Der Programmcode des Masters:
/*
Name: ArduinoSoftI2c-Test
Created: 2022-11-07
Author: Ulli (https://ullisroboterseite.de)
Docs: https://ullisroboterseite.de/arduino-soft-i2c.html
*/
#include <UrsArduinoSofti2c.h>
const uint8_t sda = 8; // SDA pin is pin 8
const uint8_t scl = 9; // SCL pin is pin 9
uint8_t devAdr = 0x30; // Device address
uint8_t readBuffer[] = { 0, 0 };
uint8_t zeroRegAdr = 0;
uint8_t cnt = 0;
UrsArduinoSoftI2cClass& i2c = SoftI2c;
void setup() {
Serial.begin(9600);
delay(10);
Serial.println("I2C-Test");
i2c.begin(sda, scl);
uint8_t rtc = i2c.readPacket(0, readBuffer, 1); // read address
if (rtc == 1) {
devAdr = readBuffer[0];
Serial.print("Device address: "); Serial.println(readBuffer[0]);
}
else {
Serial.println("Device address cannot be determined");
while (true); // ... and stopp
}
}
void loop() {
cnt ++;
// Registerfeld 'count' (Index 1) beschreiben
uint8_t rtc = i2c.writeToRegister(devAdr, 1, &cnt, 1);
if (rtc == 1) {
Serial.println("Write ok");
}
else
Serial.println("Write error");
// Felder 'version' (index 0) und 'count' (index 1) auslesen
rtc = i2c.readFromRegister(devAdr, 0, readBuffer, sizeof(readBuffer));
if (rtc == sizeof(readBuffer)) {
Serial.print("read ok: "); Serial.print(readBuffer[0]); Serial.print(" "); Serial.println(readBuffer[1]);
}
else
Serial.println("read error");
Serial.println();
delay(1000);
}