Forum: Mikrocontroller und Digitale Elektronik SSD1306 - Adressierung einzelner Pixel im Buffer


von Max M. (maxmicr)


Lesenswert?

Guten Abend,

ich versuche gerade, die Adressierung der einzelnen Pixel bei einem 
SSD1306 zu verstehen, leider blicke ich da nicht ganz durch. Datenblatt 
dazu gibt es hier: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf

Die effizienteste Speicherung wäre wahrscheinlich in einem uint8_t-Array 
mit der Länge 1024. Ich weiß, es gibt dazu genügend Beispiele / 
Libraries, aber ich würde es gerne selber verstehen (auch wenns vllt. 
länger dauert) da ich für das Display auch einen Font bauen möchte.

Im "horizontal addressing mode" geht der Controller von links nach 
rechts und von oben nach unten. Wobei es nach rechts 128 Spalten und 
nach unten 8 Zeilen gibt. Daher nehme ich an, dass 8 Y-Werte in einem 
Byte gespeichert werden (dann kommt man wieder auf die 64-Pixel in der 
Höhe).

Kann mir jemand einen Tipp geben, wie ich bei einer gegebenen x- und 
y-Koordinate auf den entsprechenden Index in meinem Array 
(uint8_t[1024])schließen kann?

von Icke (Gast)


Lesenswert?

meint du sowas?
1
uint8_t array[1024];
2
3
int idx = 42;
4
5
int x = idx % MODULE_SIZE_Y;
6
int y = floor(idx / MODULE_SIZE_X);

rückwärts:
1
int idx = x * MODULE_SIZE_X + y;

von M. K. (sylaina)


Angehängte Dateien:

Lesenswert?

Ich verstehe noch nicht, was du mit dem 1024er Array willst. Ich habe 
dir mal meine Library in den Anhang gepackt die ich grade diese Woche 
erstellt habe. Sie dient lediglich dazu, die schicken kleinen 
OLED-Displays als Ersatz benutzen zu können für die üblichen, 
alpha-numerischen Displays mit HD44780-Controllern, die ich bisher mit 
der Lib von Peter Fleury benutzt habe. Das Display kann natürlich noch 
viel mehr, daran werde ich mich vielleicht nächste Woche dran setzen und 
Zeichenroutinen dazu entwickeln. Ich habe mich bei dem Display für eine 
eigene Lib entschieden da mir die U8GLib und die von Adafruit u. a. viel 
zu groß waren, die brauchen weit über 4k byte, meine Lib braucht aktuell 
keine 1.4k byte. Vielleicht hilft es dir ja.

von Max M. (maxmicr)


Angehängte Dateien:

Lesenswert?

Icke schrieb:
> meint du sowas?

Ich bin mir nicht sicher, aber ich glaube, das passt nicht zum 
horizontalen Modus. Ich hab mir das im Kopf so vorgestellt (siehe 
Anhang).

Horizontal hat man 128 Elemente (die Pixel). Vertikal dagegen nur 8 (da 
eines aus einem Byte also 8-Bit besteht, demnach werden in einem Array 
Element 8 y-Pixel gespeichert). Wenn man das in ein 1D-Array ausbreiten 
würde, sieht es in meiner Vorstellung so aus (siehe Anhang).

Davon bekomme ich einen Knoten im Gehirn :(

M. K. schrieb:
> Ich verstehe noch nicht, was du mit dem 1024er Array willst.

Du machst das anscheinend ohne Buffer, geht auch. Wie würdest du 
vorgehen, wenn du einen einzigen Pixel setzten müsstest?

: Bearbeitet durch User
von Jesus (Gast)


Lesenswert?

M. K. schrieb:
> Ich verstehe noch nicht, was du mit dem 1024er Array willst.


Per DMA im Hintergrund an den Controller übertragen?
Wenn man nicht den lausigen I2C, sondern SPI nutzt, gibt es nichts 
Effizienteres.

von M. K. (sylaina)


Lesenswert?

Max M. schrieb:
> Du machst das anscheinend ohne Buffer, geht auch. Wie würdest du
> vorgehen, wenn du einen einzigen Pixel setzten müsstest?

Wie du vielleicht gesehen hast übertrage ich immer 8 bit. Ich würde also 
an die entsprechende Page/Column springen und den Pixel zeichnen. 
Problem: Alle Pixel, die schon an der Stelle gesetzt sind, werden dann 
gelöscht. Bei SPI/I2C kommt man wohl nicht um den Buffer rum da man ja 
das RAM des Displays dabei nicht auslesen kann wenn ich das bisher recht 
gesehen hab.
Also wenn man mehr als Schrift auf das Display bringen will brauchts 
wohl den Buffer.

von Olaf (Gast)


Lesenswert?

> Also wenn man mehr als Schrift auf das Display bringen will brauchts
> wohl den Buffer.

Buffer empfiehlt sich immer. Du kannst dann naemlich in deinem Programm 
an beliebiger Stelle ohne Ruecksicht auf die Geschwindigkeit etwas 
ausgeben weil die Daten sehr schnell in den Buffer geschrieben werden.
Die Weitergabe an das Display kann dann mit einem IRQ von sehr niedriger 
Prioritaet geschehen und natuerlich auch nur wenn sich etwas geaendert 
hat.

Olaf

von M. K. (sylaina)


Lesenswert?

Olaf schrieb:
> Buffer empfiehlt sich immer.

Wofür ich meine Library schrieb steht oben. Ich komme ohne Buffer aus. 
Erkläre bitte warum sich auch da ein Buffer empfiehlt. In meinem Falle 
hielte ich ihn für mehr als überflüssig (deshalb hab ich ja auch 
keinen).
Ein Buffer empfiehlt sich nur wenn man in einer Column einer Page 
lediglich ein einzelnes Pixel ändern will, die anderen aber so belassen 
will wie sie sind und man nicht die Möglichkeit hat, das Display 
auszulesen (wie es bei SPI/i2C beim SSD1306 der Fall ist).

Max M. schrieb:
> Wie würdest du
> vorgehen, wenn du einen einzigen Pixel setzten müsstest?

Ich hab mir mal Gedanken gemacht wie ich ein einzelnes Pixel im Buffer 
setzen kann und den zum Display übertragen kann (in Anlehnung meiner 
Lib)
1
...
2
uint8_t displayBuffer[1024];
3
for (uint16_t i = 0; i < 1023; i++){
4
  displayBuffer[i] = 0x00;
5
}
6
...
7
void lcd_setPixel(uint8_t x, uint8_t y){
8
  displayBuffer[(uint8_t)(y / 8) * 128 +x] |= (1 << (y % 8));
9
  lcd_gotoxy_gfx(x, y);
10
  lcd_send_i2c_start();
11
  lcd_send_i2c_byte(0x40);
12
  lcd_send_i2c_byte(displayBuffer[(uint8_t)(y / 8) * 128 +x]);
13
  lcd_send_i2c_stop();
14
}
15
...
16
void lcd_gotoxy_gfx(uint8_t x, uint8_t y){
17
  x = x;
18
  lcd_send_i2c_start();
19
  lcd_send_i2c_byte(0x00);  // 0x00 for command, 0x40 for data
20
  lcd_send_i2c_byte(0xb0 + y);
21
  lcd_send_i2c_byte(((x & 0xf0) >> 4) | 0x10); // | 0x10
22
  lcd_send_i2c_byte((x & 0x0f) | 0x01);
23
  lcd_send_i2c_stop();
24
}
25
...
Ich habs noch nicht getestet, hab grad das Display nicht zur Hand, aber 
so in der Richtung würde ich es wohl versuchen zu lösen...das gibt viel 
Spass noch nächste Woche...denke ich... ;)

von Olaf (Gast)


Lesenswert?

> Erkläre bitte warum sich auch da ein Buffer empfiehlt.

Ich dachte das haette ich bereits getan. Nochmal einfacher...

Stell dir vor du hast einen Regler im Controller laufen, oder irgendwas 
zeitkritisches in einem IRQ. Dann kannst du nicht einfach an beliebiger 
Stelle mal printf aufrufen weil die Ausgabe an dein Display zu langsam 
ist. Das kann im Normalbetrieb schon schlecht sein, besonders schlimm 
ist es wenn du dir zu Debugzwecken mal irgendwo was ausgeben willst.


Olaf

von Jesus (Gast)


Lesenswert?

M. K. schrieb:
> Olaf schrieb:
>> Buffer empfiehlt sich immer.
>
> Wofür ich meine Library schrieb steht oben. Ich komme ohne Buffer aus.
> Erkläre bitte warum sich auch da ein Buffer empfiehlt. In meinem Falle
> hielte ich ihn für mehr als überflüssig (deshalb hab ich ja auch
> keinen).
> Ein Buffer empfiehlt sich nur wenn man in einer Column einer Page
> lediglich ein einzelnes Pixel ändern will, die anderen aber so belassen
> will wie sie sind und man nicht die Möglichkeit hat, das Display
> auszulesen (wie es bei SPI/i2C beim SSD1306 der Fall ist).


Das ist nicht richtig.
Nur wenn Speicherplatz ein Problem ist, empfiehlt sich ein Puffer.
Dein Ansatz verbraucht erheblich mehr Rechenzeit als ein Ansatz mit 
Puffer und DMA. Flexibler und jederzeit erweiterbar ist letzterer 
sowieso.
Daher kann man grundsätzlich zum DMA-/Puffer-Ansatz und nur im Falle von 
RAM-Knappheit Deinen Weg empfehlen.

von M. K. (sylaina)


Lesenswert?

Olaf schrieb:
> Stell dir vor du hast einen Regler im Controller laufen, oder irgendwas
> zeitkritisches in einem IRQ. Dann kannst du nicht einfach an beliebiger
> Stelle mal printf aufrufen weil die Ausgabe an dein Display zu langsam
> ist. Das kann im Normalbetrieb schon schlecht sein, besonders schlimm
> ist es wenn du dir zu Debugzwecken mal irgendwo was ausgeben willst.

Hast du dir mal meine Lib angesehen? Ich hab doch gar kein printf in 
Verwendung um auf das Display zu schreiben ;)
Ein Buffer ist nur sinnvoll, damit man weiß was im Display steht. Wenn 
man das nicht wissen muss sehe ich keinen Sinn in der Verwendung eines 
Buffers bei dem hier genannten Displaytyp. Das wäre lediglich eine 
Verschwendung von Speicher.
Jesus schrieb:
> Nur wenn Speicherplatz ein Problem ist, empfiehlt sich ein Puffer.

Quatsch, grade da empfiehlt sich kein Puffer denn grade der braucht ja 
Speicherplatz und wenn der knapp ist ist ein Puffer die denkbar 
schlechteste Lösung.
Jesus schrieb:
> Daher kann man grundsätzlich zum DMA-/Puffer-Ansatz und nur im Falle von
> RAM-Knappheit Deinen Weg empfehlen.
Selbst widersprochen? However, genau das ist doch das Ding bei mir. Ich 
hab im Atmega328 nunmal nur 2k RAM, soll ich da jetzt schonmal 1k nur 
für den Puffer, den ich nicht brauche, verschwenden?

: Bearbeitet durch User
von Max M. (maxmicr)


Lesenswert?

M. K. schrieb:
> Ich hab mir mal Gedanken gemacht wie ich ein einzelnes Pixel im Buffer
> setzen kann und den zum Display übertragen kann (in Anlehnung meiner
> Lib)

Danke für deine Antwort. Kannst du mir bitte erklären wie du auf das 
hier kommst:
1
displayBuffer[(uint8_t)(y / 8) * 128 +x] |= (1 << (y % 8));

bzw. auf:
1
  lcd_send_i2c_byte(((x & 0xf0) >> 4) | 0x10); // | 0x10
2
  lcd_send_i2c_byte((x & 0x0f) | 0x01);

Diese logischen Operationen erschließen sich mir nicht aus dem 
Datenblatt :(

: Bearbeitet durch User
von Andy (Gast)


Lesenswert?

M. K. schrieb:
>> Daher kann man grundsätzlich zum DMA-/Puffer-Ansatz und nur im Falle von
>> RAM-Knappheit Deinen Weg empfehlen.
> Selbst widersprochen? However, genau das ist doch das Ding bei mir. Ich
> hab im Atmega328 nunmal nur 2k RAM, soll ich da jetzt schonmal 1k nur
> für den Puffer, den ich nicht brauche, verschwenden?


Ja, bei Dir ist das richtig. Aber Max MMM hat die Frage allgemein 
gestellt und nicht von einem bestimmten Controller geschrieben.
Bei einem STM32 z.B. ist Dein Ansatz meistens der schlechtere.

von Olaf (Gast)


Lesenswert?

> Hast du dir mal meine Lib angesehen? Ich hab doch gar kein printf in
> Verwendung um auf das Display zu schreiben ;)

Es ist irrelevant wie deine Funktion heisst. Du kennst mein printf ja 
schliesslich auch nicht. Der Punkt ist das die Ausgabe zum Display 
relativ langsam ist.

Olaf

von Tany (Gast)


Lesenswert?

Hat nicht jeder Displaycontroller s.g. SRAM?
Ist's nicht so bei jedem Controller, dass man zuerst die RAM Adresse 
setzen muß, bevor man Daten in den RAM (also Buffer) schreibt?

von M. K. (sylaina)


Lesenswert?

Olaf schrieb:
> Es ist irrelevant wie deine Funktion heisst. Du kennst mein printf ja
> schliesslich auch nicht. Der Punkt ist das die Ausgabe zum Display
> relativ langsam ist.

Der Punkt ist, dass du immer den ganzen Puffer zum Display schicken 
musst während ich immer nur das Byte zum Puffer schicke, dass ich grade 
geändert habe. Warum das hier jetzt der schlechtere Ansatz sein soll 
verstehe ich nicht. Mein uC muss nur 1 Byte (oder 2 oder 3 usw) zum 
Controller senden während die Puffer-Lösung immer alle 1024 Byte zum 
Controller schicken muss, d.h. mein Controller ist schon längst wieder 
im Tiefschlaf während dein Controller nicht mal ein Zehntel der Daten 
übertragen hat. Du hast dir entweder mein Lib gar nicht angeschaut oder 
du weißt gar nicht wie das Display arbeitet.

Andy schrieb:
> Ja, bei Dir ist das richtig. Aber Max MMM hat die Frage allgemein
> gestellt und nicht von einem bestimmten Controller geschrieben.

Deswegen schrieb ich ihm ja auch wie ich das Ganze gelöst habe für 
meinen Fall.

Max M. schrieb:
> Danke für deine Antwort. Kannst du mir bitte erklären wie du auf das
> hier kommst:

Klar. Es geht ja dabei den Pixel mit den Koordinaten x,y auf dem Display 
zu setzen. Der Puffer habe 1024 Byte, das entspricht 8 Zeilen mit 
jeweils 128 Columns. Der Puffer stelle alle Zeilen hintereinander da, 
sei also eindimensional.
Wo x liegt ist recht einfach, das ist ja direkt die Column des Displays. 
Ich muss also nur raus bekommen, in welcher Zeile ich bin und das macht 
man indem man y durch die Zeilenhöhe teilt, das ist hier 8. Die 
Zeilennummer wird mit der Länge multipliziert und dann schlicht x hinzu 
addiert.
Nehmen wir mal ein Beispiel: Wir wollen den Punkt 10,10 setzen. Schaun 
wir mal wo der läge:

Die Column ist leicht, das ist ja direkt der x-Wert, also 10.
Die Page/Zeile ist: (uint8_t) 10/8 = 1, also Zeile mit der Nummer 1, da 
das Display bei 0 beginnt ist das also die 2. Zeile
In unserem Puffer beginnt die 2. Zeile bei Index 128 (Index 0 ist das 1. 
Byte, Index 1 das 2. usw.). 10. Column in der 2. Zeile muss demnach also 
das Byte mit dem Index 138 sein und (uint8_t)(10/8)*128+10 ergibt also 
138. Das ist anscheinend richtig.
Und welcher Pixel muss in der Column gesetzt werden? Auch einfach: Die 
Zeilen sind ja alle nur 8 Pixel hoch. Der Rest von y, der sich nicht 
mehr durch 8 teilen lässt, muss also der zu setzende Pixel sein: y % 8 
liefert uns diesen Pixel. Und wenn wir alle anderen Pixel in dieser 
Column nicht ändern wollen nehmen wir eine ein, schieben sie an die 
Pixelstelle und verknüpfen die Column mittels oder, daher also das |= (1 
<< (y % 8)

Max M. schrieb:
> bzw. auf:
>   lcd_send_i2c_byte(((x & 0xf0) >> 4) | 0x10); // | 0x10
>   lcd_send_i2c_byte((x & 0x0f) | 0x01);
>
> Diese logischen Operationen erschließen sich mir nicht aus dem
> Datenblatt :(

Damit wird eigentlich nur der Cursor an die Stelle der Zeile/Column 
gesetzt, in der der Pixel geändert werden soll. ;)

: Bearbeitet durch User
von Olaf (Gast)


Lesenswert?

> Controller senden während die Puffer-Lösung immer alle 1024 Byte zum
> Controller schicken muss,

Wie kommst du auf dieses duenne Brett?

So sieht z.B bei mir der Buffer fuer ein SSD1306 aus:

#define SEGMENTSIZE 32

typedef struct{
   unsigned char segment[SEGMENTSIZE];
   unsigned char dirty;
} OLEDColumnType;

#define PAGES 8
#define SEGMENTS 4
OLEDColumnType OLED_Buff[SEGMENTS][PAGES];

Rate mal wofuer 'dirty' ist. Und rate mal was passiert wenn die 
Verbindung zum Display kurz unterbrochen wird oder dessen Inhalt 
beschaedigt wird. .-)

Aber ich muss dich nicht bekehren.

Olaf

von Alter Lateiner (Gast)


Angehängte Dateien:

Lesenswert?

Jesus schrieb:
> M. K. schrieb:
> Ich verstehe noch nicht, was du mit dem 1024er Array willst.
>
> Per DMA im Hintergrund an den Controller übertragen?
> Wenn man nicht den lausigen I2C, sondern SPI nutzt, gibt es nichts
> Effizienteres.

Sorry, ich habe nur eine kurze Zwischenfrage, ich will keinesfalls 
diesen Thread kappern. Ich habe dieses Display. Siehe Bild im Anhang. 
Könnte man dieses auch mit SPI betreiben?

von M. K. (sylaina)


Lesenswert?

Olaf schrieb:
>> Controller senden während die Puffer-Lösung immer alle 1024 Byte
> zum
>> Controller schicken muss,
>
> Wie kommst du auf dieses duenne Brett?

Gut, dann hast du immer Päckchen von 32 byte, immer noch mehr als bei 
mir.
Den Speicher für den Puffer musst du dennoch vorhalten. Aber vielleicht 
zeigst du uns ja mal deine komplette Lib und wie sie funktiniert, dann 
wird mir vielleicht auch klar wie die funktioniert.
Wie bekommst du eigentlich mit, dass der Inhalt des Displays beschädigt 
wird? In SPI/IIC gibts ja keine Infos vom Display.

Alter Lateiner schrieb:
> Jesus schrieb:
>> M. K. schrieb:
>> Ich verstehe noch nicht, was du mit dem 1024er Array willst.
>>
>> Per DMA im Hintergrund an den Controller übertragen?
>> Wenn man nicht den lausigen I2C, sondern SPI nutzt, gibt es nichts
>> Effizienteres.
>
> Sorry, ich habe nur eine kurze Zwischenfrage, ich will keinesfalls
> diesen Thread kappern. Ich habe dieses Display. Siehe Bild im Anhang.
> Könnte man dieses auch mit SPI betreiben?

Ist es genau das vom Foto? Dann geht damit nur IIC. Die SPI-Variante hat 
5 oder 6 Pins ;)

von Johannes S. (Gast)


Lesenswert?

Alter Lateiner schrieb:
> Könnte man dieses auch mit SPI betreiben?

Der Controller selber kann das schon, es ist aber bei dem Flexkabel mit 
dem kleinen Pitch schwierig etwas zu ändern.
Im Datenblatt https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf auf 
S.15 ist eine Tabelle mit den Belegungen. Am ehesten geht 3-Wire SPI, da 
musst du versuchen an CS dranzukommen, das ist für I2C ja fest auf GND 
gelegt.
Es gibt viele Varianten des Breakoutboards, die bekommt man sogar 
einzeln und könnte das umlöten. Aber das würde ich mir nicht antun 
sondern gleich ein passendes neues bestellen, auf eBay findest du die 
reichlich.

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.