Forum: Mikrocontroller und Digitale Elektronik Langsamer Bildrefresh mit ESP8266 und OLED 128x64 (SSD1306)


von André R. (andr_r619)


Lesenswert?

Hallo,

ich nutze auf meinem ESP8266 die Library U8G2, um mein OLED-Display 
(SSD1306) per I2C zu beschicken. Auf dem Bus sitzt zeitgleich noch ein 
Temperatursensor Aosong AM2320.

Nun ist es so, dass ich auf dem Display eine extrem geringe Framerate 
bekomme. Beim Wechsel der Screens sieht man deutlich, wie das Bild von 
oben nach unten neu aufgebaut wird (Rolling-Shutter-Effekt). Ich 
schätze, dass die Framerate aufgrund der Refresh-Geschwindigkeit 
irgendwo bei maximal 10 FPS liegen dürfte.

Ich habe über die Wire-Library bereits die Taktrate vor den 
Zeichenfunktionen auf 400k erhöht und anschließend wieder auf 100k 
gesenkt, da der Sensor laut Datenblatt nicht mehr kann. Ist das 
überhaupt sinnvoll, oder bremst der Sensor trotzdem? Oder liegt der 
Fehler wo ganz anderes?

Beste Grüße
André

von Brummbär (Gast)


Lesenswert?

André R. schrieb:
> Oder liegt der Fehler wo ganz anderes?
Zeile 42 in der Datei "unknown.ino".

Jetzt wird meine Kristallkugel unklar:
Du beschreibst das Display in einer Schleife und löscht es vor jedem 
Schreiben.

von Stefan F. (Gast)


Lesenswert?

Das passt zu meiner Erfahrung (mit selbst geschriebenen Code ohne diese 
Library). Die Datenübertragung dauert einfach so lange. Schneller geht 
es mit SPI, aber auch dann nicht so schnell, dass es begeistern würde.

Für bewegte Bilder taugen diese Display jedenfalls nicht.

von André R. (andr_r619)


Lesenswert?

Brummbär schrieb:
> Du beschreibst das Display in einer Schleife und löscht es vor jedem
> Schreiben.

Genau so. Das Display wird über einen Hardware-Interrupt (Taster) 
aktiviert, zeigt dann via Timer nacheinander vier verschiedene 
Live-Status-Screens und schaltet anschließend wieder ab. Umgesetzt ist 
dieser Teil des Programms als Zustandsautomat. Da sich die Messwerte 
während der Anzeige ändern können, habe ich das Ganze einfach in der 
Schleife belassen und frische den Screen bei jedem Loop auf.

Es ist auch nicht so, dass es flackern würde. Man erkennt allerdings 
beim Wechsel zwischen den vier verschiedenen Screens, wie das Bild von 
oben nach unten "umschaltet".

Kann man denn mit der Wire-Library irgendwie auslesen, wie schnell der 
Bus gerade läuft?

Stefan U. schrieb:
> Die Datenübertragung dauert einfach so lange. Schneller geht
> es mit SPI, aber auch dann nicht so schnell, dass es begeistern würde.

Das wundert mich ja fast. Ich hätte erwartet, dass SPI bedeutend 
langsamer ist.

> Für bewegte Bilder taugen diese Display jedenfalls nicht.

Ist bei mir gar kein Use-Case. Ich sehe das auch eher als 
Schönheitsfehler an. Der Funktion tut es keinen Abbruch.

von Falk B. (falk)


Lesenswert?

@André R. (andr_r619)

>> Du beschreibst das Display in einer Schleife und löscht es vor jedem
>> Schreiben.

>Genau so. Das Display wird über einen Hardware-Interrupt (Taster)
>aktiviert, zeigt dann via Timer nacheinander vier verschiedene
>Live-Status-Screens und schaltet anschließend wieder ab.

Das ist aber nicht die Antwort auf die Frage. Anscheinend löschst du das 
LCD NICHT vor jeder Anzeige. Das ist auch nicht nötig.

>Schleife belassen und frische den Screen bei jedem Loop auf.

Naja, du hast immerhin 10 Hz Refreshrate, das sollte reichen.

>Es ist auch nicht so, dass es flackern würde. Man erkennt allerdings
>beim Wechsel zwischen den vier verschiedenen Screens, wie das Bild von
>oben nach unten "umschaltet".

Das kann man nur verhindern, wenn man deutlich schneller die Daten in 
das LCD übertragen kann oder wenn das LCD 2 getrennte Speicher (Pages) 
hat. In die eine schreibt man beliebig langsam neue Daten, die andere 
wird angezeigt. Dann schaltet man im richtigen Moment um, wenn die 
Darstellung der neuen Daten beginnt. Sowas ist aber sehr selten.

>Kann man denn mit der Wire-Library irgendwie auslesen, wie schnell der
>Bus gerade läuft?

Nein.

>> Die Datenübertragung dauert einfach so lange. Schneller geht
>> es mit SPI, aber auch dann nicht so schnell, dass es begeistern würde.

>Das wundert mich ja fast. Ich hätte erwartet, dass SPI bedeutend
>langsamer ist.

Warum? I2C läuft mit 100 oder 400kHz, SPI kann je nach IC mehrere MHz 
bis weit in den 2-stelligen Bereich.

>Ist bei mir gar kein Use-Case. Ich sehe das auch eher als
>Schönheitsfehler an. Der Funktion tut es keinen Abbruch.

Dann musst du den Refresh cleverer machen und nur den Bereich neu 
ausgeben, der sich wirklich ändert. Damit geht es schneller, weil 
weniger Daten übertragen werden müssen.

von Εrnst B. (ernst)


Lesenswert?

Wenn dein SSD1306 genug Ram (bzw. dein OLED wenig genug Pixel) hat:

Bau den neuen Screen im Hintergrund auf, und schalte das Display um, 
sobald fertig.

Befehl "Set Display Start Line"

von Stefan F. (Gast)


Lesenswert?

> Bau den neuen Screen im Hintergrund auf, und schalte das Display um,
> sobald fertig.

Dafür hat das Display zu wenig Speicher. Das geht nur beim kleineren 
Display mit 32 Zeilen.

> Dann musst du den Refresh cleverer machen und nur den Bereich neu
> ausgeben, der sich wirklich ändert.

Ja.

von Falk B. (falk)


Lesenswert?

"It has 256-step brightness control. Data/Commands are sent from general 
MCU through the hardware selectable 6800/8000 series compatible Parallel 
Interface, I2C interface or Serial Peripheral Interface. "

Beim Betrieb mit parallelem Interfache kann man laut Datenblatt 300ns 
Zykluszeit erreichen, das wären satte 3MB/s ;-)

Bei SPI dürfen es immerhin noch 10MHz SPI-Takt sein, macht netto 1MB/s

Bei I2C ist man auf max. 400kHz geschränkt, macht 50kB/s.

Das LCD hat 128x64 Bit = 1kB Speicher, also sollte man es auch bei 
400kHz ausreichend schnell beschreiben können, wenn der Rest paßt.

Man müßte mal mit dem Oszi oder Logic Analyzer schauen, wie die 
Datenübertragung aussieht. Wahrscheinlich vergurkt die Lib da noch 
einiges.

von Stefan F. (Gast)


Lesenswert?

> Bei I2C ist man auf max. 400kHz geschränkt, macht 50kB/s.

Du musst mindestens durch 11 teilen, nicht durch 8.

So einfach ist es aber nicht, denn ein paar der nötigen Kommandos 
brauchen beträchtliche Zeit zur Ausführung. Es geht nicht nur um die 
reine Datenübertragung.

von Falk B. (falk)


Lesenswert?

@Stefan Us (stefanus)

>Frames pro Sekunde. So einfach ist es aber nicht, denn ein paar der
>nötigen Kommandos brauchen beträchtliche Zeit zur Ausführung.

Ja welche denn? Und braucht man die, um neue Daten in den RAM zu 
schreiben?

von Stefan F. (Gast)


Lesenswert?

Man muss angeben, ab welche Adresse man schreiben will. Dann kann man 
nicht das ganze RAM in einem Zug füllen, weil der auto-increment nur bis 
256 zählen kann. Und außerdem konkurrieren Schreibzugriffe mit 
Lesezugriffen, was auch noch etwas zur Verlangsamung führt.

Ein bisschen mehr als 10 Frames pro Sekunde gehen schon, aber höchstens 
doppelt so viele. Das wäre immer noch deutlich sichtbar.

von Stefan F. (Gast)


Lesenswert?

> bremst der Sensor trotzdem

manche I²C Chips nutzen das Clock-Stretching, wenn sie mehr Zeit 
brauchen. Manche Chips erlauben höhere Taktraten wenn sie gerade NICHT 
angesprochen werden. Aber leider nicht alle.

Wenn das Datenblatt diese Details nicht erwähnt, musst du entweder mutig 
ausprobieren oder den kleinsten gemeinsamen Nenner (also 100kHz) nehmen.

von Marco H. (damarco)


Lesenswert?

Hmm also schleifen sind schon mal ganz blöd. Deine Task darf keine 
ungebremste schleife haben. Wie ist denn der Display Buffer im U8G2 
organisiert ? Man kann auch Zeilenweise schreiben und das mit der 
Eigenart vom ESP richtig blöd.

Denn du füllst erstmal einen Buffer mit CMDs und nach dem Start schreibt 
die Hardware die Daten nach draußen. Ich könnte mir vorstellen wenn die 
Pakete recht klein sind das dieses verfahren recht langsam ist.

Die i2c Funktion blockiert auch deine Task so lange wie Daten 
geschrieben werden.

Ich würde eine extra Display Task machen und per event dort den Wert 
übergeben. Man kann auch gezielt nur den variablen Inhalt im Controller 
RAM überschreiben anstatt immer das ganze Display zu übertragen.

Mit RTOS muss man etwas anders denken, also viel event basiert lösen.

: Bearbeitet durch User
von André R. (andr_r619)


Lesenswert?

Falk B. schrieb:
> Das ist aber nicht die Antwort auf die Frage. Anscheinend löschst du das
> LCD NICHT vor jeder Anzeige. Das ist auch nicht nötig.

Doch, derzeit habe ich immer einen Clear-Buffer-Befehl vor jedem 
Neuzeichnen. Am Ende des Zeichnens wird der komplette Buffer an den 
Screen geschickt (Full-Buffer-Mode).

Falk B. schrieb:
> Dann musst du den Refresh cleverer machen und nur den Bereich neu
> ausgeben, der sich wirklich ändert. Damit geht es schneller, weil
> weniger Daten übertragen werden müssen.

Das dürfte die Lib nicht hergeben. Es gibt glaube ich so einen Modus, da 
kann man allerdings nicht mehr alle Zeichenfunktionen nutzen. Oder?

Εrnst B. schrieb:
> Wenn dein SSD1306 genug Ram (bzw. dein OLED wenig genug Pixel) hat:
>
> Bau den neuen Screen im Hintergrund auf, und schalte das Display um,
> sobald fertig.

Wie findet man das heraus? Das Display ist dieses hier: 
https://www.amazon.de/gp/product/B01N2K3BC9/ref=oh_aui_detailpage_o01_s00?ie=UTF8&psc=1

Falk B. schrieb:
> Man müßte mal mit dem Oszi oder Logic Analyzer schauen, wie die
> Datenübertragung aussieht. Wahrscheinlich vergurkt die Lib da noch
> einiges.

So gut ausgestattet bin ich leider nicht.

Stefan U. schrieb:
> Und außerdem konkurrieren Schreibzugriffe mit
> Lesezugriffen, was auch noch etwas zur Verlangsamung führt.
>
> Ein bisschen mehr als 10 Frames pro Sekunde gehen schon, aber höchstens
> doppelt so viele. Das wäre immer noch deutlich sichtbar.

Ich dachte, I2C sei bei diesem Display/Controller nur unidirektional 
möglich …

Stefan U. schrieb:
> manche I²C Chips nutzen das Clock-Stretching, wenn sie mehr Zeit
> brauchen. Manche Chips erlauben höhere Taktraten wenn sie gerade NICHT
> angesprochen werden. Aber leider nicht alle.
>
> Wenn das Datenblatt diese Details nicht erwähnt, musst du entweder mutig
> ausprobieren oder den kleinsten gemeinsamen Nenner (also 100kHz) nehmen.

Ich hatte bei einem ursprünglichen Test ganz oben im Setup den Takt auf 
400k gesetzt. Ich weiß allerdings nicht, ob das effektiv war. 
Funktioniert hat jedenfalls alles. Aber ich habe eben keine Ahnung, wie 
ich testen soll, wie schnell der Bus real tatsächlich arbeitet und ob 
meine Setting von 400k überhaupt angewendet oder von irgend einer Lib 
wieder überschrieben wird. Jedenfalls kam es mir nicht so vor, als würde 
es einen großen optischen Unterschied machen, ob ich mit 100k oder 400k 
schreibe.

Marco H. schrieb:
> Hmm also schleifen sind schon mal ganz blöd. Deine Task darf keine
> ungebremste schleife haben. Wie ist denn der Display Buffer im U8G2
> organisiert ? Man kann auch Zeilenweise schreiben und das mit der
> Eigenart vom ESP richtig blöd.

Ich entwickle mit der Arduino-Library über Visual Micro im Visual 
Studio. Entsprechend nutze ich die Loop-Funktion. In diesem Loop kommt 
irgendwo ein If, welches fragt, ob mein Display-Mode-Enum > OFF (0) ist. 
Wenn ja, dann kommt darin zunächst ein Buffer-Clear und dann nach Art 
einer State-Machine ein Switch. Dieses zeichnet je nach aktuellem Modus 
den Frame in den Buffer. Nach dem Switch wird anschließend der Buffer 
auf den Screen geschrieben. Der Display-Mode wird alle 10 Sekunden durch 
einen Timer inkrementiert und springt nach dem vierten Modus wieder auf 
OFF. Dadurch wird das Display in den Energiesparmodus versetzt.

Es gibt also keine wirkliche Schleife sondern nur den Loop vom Arduino. 
Es wird also pro Loop-Durchlauf nur einmal gezeichnet und anschließend 
kommen die anderen Dinge, die der Prozessor noch so erledigen muss.

Marco H. schrieb:
> Ich würde eine extra Display Task machen und per event dort den Wert
> übergeben. Man kann auch gezielt nur den variablen Inhalt im Controller
> RAM überschreiben anstatt immer das ganze Display zu übertragen.

Auf dem ESP32 mit zwei Kernen würde ich das ja noch verstehen, aber 
welches Problem würde mir das auf dem ESP8266 lösen? Der Taster arbeitet 
über einen Hardware-Interrupt, wird also ohnehin nicht blockiert. Und 
gleichzeitig Sensor lesen und Display schreiben geht ohnehin nicht.

Was sind denn die Vorteile eines Tasks, bzw. wie wird dieser auf dem ESP 
überhaupt deklariert?

Der Grundaufbau des Programms ist, dass es zwei Timer gibt. Einer ist 
für das Display und wird erst gestartet, wenn der Interrupt kommt. Er 
wird wieder gelöscht, wenn der Display-Mode seinen letzten Zustand 
überschritten hat. Dieser Timer inkrementiert ausschließlich das Enum.

Der andere Timer läuft alle fünf Sekunden und setzt eine Bool-Variable 
auf True, welche für die Sensor-Steuerung zuständig ist.

In der Loop-Funktion werden das Enum und die Variable nacheinander 
geprüft und dann entsprechende Funktionen durchgeführt. Den Displayteil 
habe ich ja bereits beschrieben. Die Sensorfunktion schreibt die 
aktuellen Sensorwerte in einen Ringspeicher, sodass jederzeit die 
letzten 64 Sensorwerte als Historie zur Verfügung stehen.

Läuft der Pointer auf meinen Ringspeicher über, so wird eine weitere 
Variable gesetzt, die im Loop weiter unten ebenfalls geprüft wird. Diese 
kümmert sich um das Übermitteln der aktuell gespeicherten 64 Datensätze 
als Paket an einen Server.

von Stefan F. (Gast)


Lesenswert?

> Ich dachte, I2C sei bei diesem Display/Controller nur
> unidirektional möglich …

richtig. Ich meinte dass die Schreibzugriffe über I²C mit den internen 
Lesezugriffen der Displays selbst konkurrieren.

Der ESP8266 hat soweit ich weiß keine echte I²C Schnittstelle (in 
Hardware). Da man ohnehin die beiden I/O Pins Bit-Bangen muss, sehe ich 
keinen wesentlichen Nachteil darin, dass blockierend zu tun.

von c-hater (Gast)


Lesenswert?

Stefan U. schrieb:

> Ein bisschen mehr als 10 Frames pro Sekunde gehen schon, aber höchstens
> doppelt so viele. Das wäre immer noch deutlich sichtbar.

20fps deutlich sichtbar? Das kommt wohl SEHR auf die Inhalte der 
Frames an.

Wenn es da z.B. ein graphisch aufgemotzen (aber konstanten) Hintergrund 
gibt, auf dem ein paar variable Ziffern im Vordergrund erscheinen, dann 
siehst du bei 20fps allenfalls mal gelegentlich einen kleinen "Ruckler" 
dort, wo sich gerade eine Ziffer ändert. Das gilt selbst dann, wenn 
(unnötigerweise) ein kompletter Refresh des Frameinhaltes durchgeführt 
wird.

Wenn man hingegen komplett unterschiedliche Frames aufeinander folgen 
läßt, dann sieht das bei 20fps mit "rolling shutter" wirklich ziemlich 
Scheiße aus. Aber auch sowas läßt sich recht gut kaschieren. Man benutzt 
einfach einen Fade-Effekt, um den Übergang optisch weniger störend 
wirkend zu lassen. Blend z.B. oder fade to/from white/black. Schon recht 
wenige berechnete Zwischen-Frames lassen nicht nur den Effekt des 
"rolling shutter" dann optisch weitgehend verschwinden, nö, das Ergebnis 
sieht auch ingesamt sehr viel akttraktiver als das blödstumpfe 
Umschalten aus, so viel attraktiver, dass man solche Effekte sogar dann 
benutzt, wenn sie nicht wegen irgendwelcher Limitierungen der 
Display-Anbindung nötig werden würden, um das zu verdecken...

von Dieter F. (Gast)


Lesenswert?

Stefan U. schrieb:
> Für bewegte Bilder taugen diese Display jedenfalls nicht.

:-) in C geschrieben ...

https://www.youtube.com/watch?v=DVmTeQ6u3jQ

von Stefan F. (Gast)


Lesenswert?

Nicht schlecht, ein beeindruckendes Video.
Ich schätze dass dort nicht das ganze Bild neu gezeichnet wird, sondern 
nur wirklich geänderten Stellen. Aber egal, ist trotzdem beeindruckend.

von Pete K. (pete77)


Lesenswert?

Und es gibt immer noch kein Sourcecode ...

von Dieter F. (Gast)


Lesenswert?

Stefan U. schrieb:
> Ich schätze dass dort nicht das ganze Bild neu gezeichnet wird, sondern
> nur wirklich geänderten Stellen.

Nö, der Puffer (komplettes Bild) wird komplett übertragen. Es gibt auch 
Ansätze mit teilweise Übetragung, welche wohl (in manchen Fällen) 
schneller sind. Der Witz ist nur der Puffer, in welchem das Bild 
aufgebaut wird und dessen "optimaler" Übertragung.

Die

https://learn.adafruit.com/monochrome-oled-breakouts/arduino-library-and-examples

können das gut :-)

von c-hater (Gast)


Lesenswert?

Dieter F. schrieb:

> :-) in C geschrieben ...

Die Sprache spielt natürlich echt keine nennenswerte Rolle, wenn die 
IO-Anbindung des Displays mehr als eindeutig der limitierende Faktor 
ist...

von Dieter F. (Gast)


Lesenswert?

c-hater schrieb:
> Die Sprache spielt natürlich echt keine nennenswerte Rolle,

Selbstverständlich ... :-)

von Stefan F. (Gast)


Lesenswert?

Ich habe mit meinem eigenen Code nochmal ein bisschen herumgespielt, 
weil mich das Timing gerade interessiert und ich dies als gute 
Gelegenheit sehe, meinen neuen Logic Analyzer endlich mal zu benutzen.

Bei 170kHz Taktfrequenz komme ich auf 56ms für einen kompletten Refresh.
Bei 500kHz Taktfrequenz komme ich auf 19ms für einen kompletten Refresh.

Dabei übertrage ich die Daten in 8 Blöcken (Pages) und rufe zwischen den 
Blöcken jeweils einmal yield() auf, damit der Watchdog nicht triggert. 
So oft ist das vermutlich nicht nötig, aber das hat ohnehin keinen 
großen Einfluss auf die Gesamt Performance.

Meine vorherige Aussage, dass mehr als 20 Refreshes pro Sekunde nicht 
gehen muss ich somit revidieren.

von André R. (andr_r619)


Lesenswert?

Stefan U. schrieb:
> richtig. Ich meinte dass die Schreibzugriffe über I²C mit den internen
> Lesezugriffen der Displays selbst konkurrieren.

Ach so; das habe ich natürlich nicht bedacht. Da müsste man zur 
genaueren Analyse wohl wirklich mal einen Logicanalyzer anschließen. Nur 
habe ich so ein Teil, wie geschrieben, nicht.

Stefan U. schrieb:
> Der ESP8266 hat soweit ich weiß keine echte I²C Schnittstelle (in
> Hardware).

Exakt richtig.

c-hater schrieb:
> Man benutzt
> einfach einen Fade-Effekt, um den Übergang optisch weniger störend
> wirkend zu lassen.

Das wird auf einem Display mit 1 Bit Farbtiefe eher schwierig …

Stefan U. schrieb:
> Ich schätze dass dort nicht das ganze Bild neu gezeichnet wird, sondern
> nur wirklich geänderten Stellen.

Das vermute ich auch.

Stefan U. schrieb:
> Bei 170kHz Taktfrequenz komme ich auf 56ms für einen kompletten Refresh.
> Bei 500kHz Taktfrequenz komme ich auf 19ms für einen kompletten Refresh.

Dann stellt sich umso mehr die Frage, was bei mir schief läuft. 
Vielleicht sollte ich ja mal testweise das Display an einen zweiten Bus 
anschließen und somit vom Sensor entkoppeln. Denkt ihr, das könnte etwas 
bringen?

von abc (Gast)


Lesenswert?

Stefan U. schrieb:
> Man muss angeben, ab welche Adresse man schreiben will. Dann kann
> man
> nicht das ganze RAM in einem Zug füllen, weil der auto-increment nur bis
> 256 zählen kann. Und außerdem konkurrieren Schreibzugriffe mit
> Lesezugriffen, was auch noch etwas zur Verlangsamung führt.

Ich hab jetzt nicht intensiv nachgesehen, ob die Ansteuerung des SSD1306 
per I2C wirklich gegenüber SPI stark eingeschränkt ist, aber zumindest 
bei SPI gibt es keine Einschränkungen beim Autoinkrement.
Den ganzen Speicher kann man in einem Rutsch schreiben. Das ist 
praktisch, weil das so schön per DMA geht, auch lässt sich der SSD 
meiner Erfahrung nach auch mit SPI-Frequenzen jenseits der 10MHz 
fehlerfrei betreiben.
Effektiv sind nach Erst-Initialisierung nur die paar Befehle zum 
Anstossen des DMA nötig.

Damit erreicht man Refresh-Raten jenseits von gut und böse.

von S. Presso (Gast)


Lesenswert?


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.