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