Forum: Mikrocontroller und Digitale Elektronik ESP32 als ganz normaler I2C slave


von Jan (Gast)


Lesenswert?

Moin,

gibt es vielleicht doch Beispiele für eine gelungene und "normale" I2C 
Slave Implementierung auf dem ESP32, idealerweise für die Arduino IDE, 
gerne aber auch direkt per IDF?

Mit "normal" meine ich, dass der Slave vom Master direkt und ohne 
speziellen Wrapper angesprochen werden kann, wie es dieser Workaround 
erfordert: https://github.com/gutierrezps/ESP32_I2C_Slave

Ziel ist ein I2C Slave, der von beliebigen Mastern aus z.B. klassisch 
per Register angesprochen werden kann.

Hintergrund:

Espressif hat bis heute den Slave Modus beim ESP32 nur unvollständig 
implementiert und dokumentiert 
(https://github.com/espressif/esp-idf/issues/2202), v.a. in Bezug auf 
Interrupts (https://github.com/espressif/esp-idf/issues/3099). Die Wire 
library von Espressiv kennt kein onRequest() 
(https://github.com/espressif/arduino-esp32/issues/118). Es gibt zwar 
schon Leute, die an Lösungen arbeiten, bei denen geht aber seit Jahren 
nichts voran (z.B. https://github.com/espressif/esp-idf/pull/2096).

Alles, was die Espressiv API für Slaves anbietet 
(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/i2c.html#communication-as-slave) 
sind die Funktionen i2c_slave_read_buffer() und 
i2c_slave_write_buffer(). Bei denen besteht aber wohl das Risiko, 
unvollständige Pakete zu erhalten oder zu senden (vgl. 
https://github.com/gutierrezps/ESP32_I2C_Slave "This usage of the API 
functions raise some problems.")

Die angesprochene Library löst das mit einem Datenwrapper, der die 
Integrität der Pakete sicherstellen soll, aber auf Seiten des Masters 
voraussetzt, dass die Pakete eben entsprechend verpackt werden. Das geht 
vielleicht noch in geschlossenen Projekten, für einen Slave, der wie 
jedes andere I2C-Gerät von beliebigen Mastern ansprechbar sein soll, 
kommt das aber nicht in Frage.

Andere Lösungsansätze?

Ein Kommentar bei einem der o.g. issues 
(https://github.com/espressif/esp-idf/issues/3099#issuecomment-467725985) 
deutet eine Lösung mit einem "high priority task blocked on 
i2c_slave_read_buffer" an, dazu konnte ich aber nichts weiter finden. 
Ich stelle mir das so vor, dass ein solcher Task ähnlich wie ein 
Interrupt wirkt und implementiert werden kann, denn er schläft bis Daten 
vom Master angekommen sind und  blockiert dann aufgrund seiner Priorität 
den Haupttask.

Bisher habe ich aber noch keine Erfahrung mit Multitasking und 
inter-task Kommunikation auf dem ESP32. Ich arbeite mich da gerne rein, 
möchte Sackgassen aber vermeiden, denn diese Kommentare z.B. klingen 
leider wenig optimistisch: 
https://github.com/espressif/arduino-esp32/issues/118#issuecomment-812974529 
, 
https://github.com/espressif/arduino-esp32/issues/118#issuecomment-823695741

Ich freue mich daher über eine Einschätzung aus berufenerem Munde, ob 
das so klappen könnte. Mir wäre bei der Lösung auch nicht klar, wie ein 
request von einem normalen receive unterschieden wird und wie es mit dem 
clock stretching aussieht. Außerdem wäre mir nicht klar, wie dieses 
Blocking genau aussehen würde, denn geblockt werden darf ja nur der high 
priority task, nicht der Haupttask mit niedrigerer Priorität.

Gruß
Jan

von Simon Schmid (Gast)


Lesenswert?

Danke für die Erkärungen. Ich habe das gleiche Problem. Ich wollte das 
ESP32 als slave benutzen. Jedoch sehe ich Probleme wie du jetzt. 
Vermutlich werde ich dann den ESP32 als Master mit der 
ARDUINO-Bibliothek benutzen das scheint mir an effektivsten zu sein. 
Mein anderer Up muss dann wohl gehorchen....

von ESP4096 S. (esp4096)


Lesenswert?

Kurze Info:

Wurde vor ein paar Tagen gefixt:

https://github.com/espressif/arduino-esp32/pull/5746

Dauert wohl noch bis es dann in einem Release landet, der dann wiederum 
auf PlatformIO landen muss, für Leute die das einsetzen.

Btw als WO besteht noch dieser hier: 
https://github.com/gutierrezps/ESP32_I2C_Slave

: Bearbeitet durch User
von Jan (Gast)


Lesenswert?

ESP4096 S. schrieb:
> Dauert wohl noch bis es dann in einem Release landet, der dann wiederum
> auf PlatformIO landen muss, für Leute die das einsetzen.

Heute endlich erschienen:

https://github.com/espressif/arduino-esp32/releases/tag/2.0.1

"Wire-Slave is now implemented" - Halleluja!

Die Doku
https://docs.espressif.com/projects/arduino-esp32/en/latest/api/i2c.html#i2c-slave-mode
spricht allerdings immer noch von einer nach Workaround klingenden 
Funktion Wire.slaveWrite(). Aus der kurzen Beschreibung und dem unten 
auf der Seite stehenden Beispiel werde ich ad hoc nicht ganz schlau. Ist 
wohl erst mal ausprobieren angesagt.

vg
Jan

von Stefan F. (Gast)


Lesenswert?

Jan schrieb:
> Ist wohl erst mal ausprobieren angesagt.

Bitte berichte, was du dabei heraus findest. Ich nutze den ESP32 noch 
nicht, bin aber sehr gespannt darauf.

von Joerg W. (joergwolfram)


Lesenswert?

Ich befürchte, eine flexible Lösung wie bei anderen Controllern wird man 
nie erreichen. Das I2C-Interface ist eigentlich wie eine Art "Sequenzer" 
aufgebaut, und man kann das nicht einfach auf Byte-Ebene runterbrechen. 
Das gilt übrigens auch für den Master-Mode, wo sich auch nur eine 
komplette Übertragung mit Start und Stop einstelen lässt. "Einfach mal 
so" eine Startkondition + Adressbyte senden und dann schauen, wie man 
weiter verfährt ist nicht vorgesehen. Und wenn man mehr Daten als die 
internen Puffer übertragen will, muss man ständig überwachen oder DMA 
nehmen. Gleiches gilt übrigens auch für SPI.

Für mich ist das insoweit ärgerlich, dass ich meine HAL 
Highlevel-Bibliotheken, die auf "i2c_send_start()", 
"i2c_send_byte(data)" etc. aufbauen hier nicht mehr nutzen kann. 
Inzwischen habe ich mir mit einem Software-I2C beholfen, aber eine 
optimale Lösung ist das nicht.

Dazu möchte ich noch anmerken, dass die ESP32 Dokumentation lückenhaft 
ist und dazu noch jede Menge Fehler beinhaltet.

Jörg

von Jan (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Jan schrieb:
>> Ist wohl erst mal ausprobieren angesagt.
>
> Bitte berichte, was du dabei heraus findest.

Dann tue ich das doch mal:

Es geht jetzt (yippie!) und das bisher ziemlich reibungslos, zumindest 
was das Kompilieren eines typischen Wire-Sketches angeht. Im Test stellt 
sich dann aber heraus, dass die Daten vom Slave immer um einen Zyklus 
versetzt zu spät kommen.

Das hat mit der Ausnahme zu tun, die Jörg schon angedeutet hatte:

Joerg W. schrieb:
> "Einfach mal so" eine Startkondition + Adressbyte senden und dann schauen, wie 
man
> weiter verfährt ist nicht vorgesehen.

Sprich: Beim requestEvent() haut der Slave einfach raus, was bisher in 
den Buffer geschrieben wurde. Alle Wire.write() innerhalb vom 
requestEvent() kommen zu spät und werden erst im nächsten Zyklus 
rausgeschickt. Im Prinzip kann requestEvent() also komplett leer 
bleiben, den Buffer kann man im Hauptprogramm füllen, sobald man weiß, 
was rein soll.

Heißt aber: Spontanes Reagieren im Interrupt, z.B. Versenden von 
Default-Daten falls vorher keine spezifischen angefordert wurden oder 
Messwert erst zum Zeitpunkt des Requests auslesen, geht also nicht ohne 
Workarounds.

Hier eine funktionierende Implementierung des requestEvent() mit 
bedingter Kompilierung für AVR/ESP32:

https://github.com/ftjuh/AccelStepperI2C/blob/b00b994dbfd08128db3c431e701d63821b1d35fc/firmware/firmware.ino#L900

Hier die Stelle, wo für den ESP32 der Buffer vorab gefüllt wird:

https://github.com/ftjuh/AccelStepperI2C/blob/b00b994dbfd08128db3c431e701d63821b1d35fc/firmware/firmware.ino#L852

(Rückmeldungen willkommen, ich habe wenig Erfahrung mit C++, das Projekt 
ist noch work in progress).

Joerg W. schrieb:
> Dazu möchte ich noch anmerken, dass die ESP32 Dokumentation lückenhaft
> ist und dazu noch jede Menge Fehler beinhaltet.

Damit hadere ich auch. Was genau slaveWrite() vs. Write() macht wird mir 
[anhand der Doku und des 
Beispielcodes](https://docs.espressif.com/projects/arduino-esp32/en/latest/api/i2c.html#id2) 
nicht klar. Außerdem ist [das 
Beispiel](https://docs.espressif.com/projects/arduino-esp32/en/latest/api/i2c.html#example-application-wireslave-ino) 
irreführend, da dort innerhalb von onRequest() Daten (mit Wire.print(), 
i.e. Wire.write()) geschrieben werden, was aber den  irreführenden 
Eindruck macht, die würden dann auch tatsächlich mit diesem Interrupt 
verschickt (s.o.)

vg
Jan

von Stefan F. (Gast)


Lesenswert?

Danke für deinen Bericht.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.