Anleitung: Anzeige einer sich schnell ändernden Zahl ohne Flackern

Fragestellung: Im Forum taucht immer mal wieder die Frage auf, wie man eine Zahl, die sich schnell ändert, ohne Flackern darstellen kann.

Hardware: Ich verwende einen Mega2560 und ein Display mit SSD1331. Das ist allerdings unerheblich.

Ursachenforschung: Bei der Verwendung von Adafruit_GFX funktioniert die Darstellung ohne Flackern, wenn man den eingebauten Font ('Classic' built-in font) mit Nutzung der Hintergrundfarbe ( setTextColor(WHITE, BLACK); ) verwendet. Bei großem Text gefällt die "Klötzchendarstellung" aber nicht jedem.

Die weiteren mitgelieferten Fonts (Custom font) können durchaus gefallen, unterstützen aber nicht die Hintergrundfarbe. Dadurch muß erst der Hintergrund durch ein Rechteck oder die alte Ziffer in Hintergrundfarbe gelöscht werden, bevor die neue Ziffer geschrieben werden kann.

Dies wird vom menschlichen Auge als Flackern wahrgenommen.

Idee 0: Wie beim eingebauten Font kann man bei allen Fonts die Hintergrundfarbe berücksichtigen. Ein Warntext in der Bibliothek läßt ahnen, daß Ungemach droht. Tatsächlich hat LadyAda die Fontdefinition auf die Vordergrundpixel komprimiert. Innerhalb eines Nutzrechtecks (w * h) sind Vordergrund- und Hintergrundpixel definiert. Dieses Nutzrechteck wird dann mittels Offsets (xo, yo) vom Ursprung auf der Basislinie ausgehend an die darzustellende Position verschoben. Alle Pixel außerhalb des Nutzrechtecks bleiben unberührt.

Leider gilt dies auch für die Fonts mit "Mono" im Namen, erst recht für die proportionalen.

Auch wenn diese Idee nicht zum Ziel führt, so ist die Erweiterung zur Darstellung der Hintergrundpixel doch Grundlage für die weiteren Aktivitäten.

yobg = absolut größter Wert aller yo der Ziffern, aber mit Vorzeichen
hbg = größter Wert aller h der Ziffern (nicht im Bild)

Idee 1: Aus dem Lieblingsfont nimmt man nur die Ziffern und erweitert die Fontdefinition um alle Hintergrundpixel. Das sollte zusammen mit Idee 0 funktionieren, habe ich aber nicht probiert.

Idee 2: Der Font bleibt unverändert, alle Pixel außerhalb des Nutzrechtecks werden berechnet und auf die Hintergrundfarbe gesetzt. Diese Idee habe ich weiterverfolgt.

Fontdefinitionen der Ziffern (Beispiel aus FreeSansBold24pt7b.h)

             w    h   xA    xo    yo
  {   923,  24,  35,  26,    1,  -33 },   // 0x30 '0'
  {  1028,  14,  33,  26,    4,  -32 },   // 0x31 '1'
  {  1086,  23,  34,  26,    2,  -33 },   // 0x32 '2'
  {  1184,  23,  35,  26,    2,  -33 },   // 0x33 '3'
  {  1285,  22,  33,  26,    2,  -32 },   // 0x34 '4'
  {  1376,  23,  34,  26,    2,  -32 },   // 0x35 '5'
  {  1474,  23,  35,  26,    2,  -33 },   // 0x36 '6'
  {  1575,  23,  33,  26,    1,  -32 },   // 0x37 '7'
  {  1670,  24,  35,  26,    1,  -33 },   // 0x38 '8'
  {  1775,  24,  35,  26,    1,  -33 },   // 0x39 '9'

Die zugehörige Struktur aus gfxfont.h sieht so aus:

typedef struct { // Data stored PER GLYPH
 uint16_t bitmapOffset;     // Pointer into GFXfont->bitmap
 uint8_t  width, height;    // Bitmap dimensions in pixels
 uint8_t  xAdvance;         // Distance to advance cursor (x axis)
 int8_t   xOffset, yOffset; // Dist from cursor pos to UL corner
} GFXglyph;

Anpassungen in Adafruit_GFX.h:

protected:
  void
    charBounds(char c, int16_t *x, int16_t *y,
      int16_t *minx, int16_t *miny, int16_t *maxx, int16_t *maxy);
  const int16_t
    WIDTH, HEIGHT;   // This is the 'raw' display w/h - never changes
  int16_t
    _width, _height, // Display w/h as modified by current rotation
    cursor_x, cursor_y;
  uint16_t
    textcolor, textbgcolor;
  int8_t
    yobg;
  uint8_t
    textsize,
    rotation,
    hbg;
  boolean
    wrap,   // If set, 'wrap' text at right edge of display
    _cp437; // If set, use correct CP437 charset (default is off)
  GFXfont
    *gfxFont;
};

Anpassungen in Adafruit_GFX.cpp:

 } else { // Custom font

        // Character is assumed previously filtered by write() to eliminate
        // newlines, returns, non-printable characters, etc.  Calling
        // drawChar() directly with 'bad' characters of font may cause mayhem!

        uint8_t first = pgm_read_byte(&gfxFont->first);
        c -= (uint8_t) first;
        GFXglyph *glyph  = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c]);
        uint8_t  *bitmap = (uint8_t *)pgm_read_pointer(&gfxFont->bitmap);

        uint16_t bo = pgm_read_word(&glyph->bitmapOffset);
        uint8_t  w  = pgm_read_byte(&glyph->width),
                 h  = pgm_read_byte(&glyph->height),
                 xA = pgm_read_byte(&glyph->xAdvance);
        int8_t   xo = pgm_read_byte(&glyph->xOffset),
                 yo = pgm_read_byte(&glyph->yOffset);
        uint8_t  xx, yy, bits = 0, bit = 0;
        int16_t  xo16 = 0, yo16 = 0;

        if(size > 1) {
            xo16 = xo;
            yo16 = yo;
        }


        startWrite();
        if((size==1)&&(bg != color)&&(c+first>='0'-first)&&(c+first<='9')) {
            if((hbg==0) || (yobg==0)) {
                for(char z='0'; z<='9'; z++) {
                    uint8_t zz = z - (uint8_t)pgm_read_byte(&gfxFont->first);
                    GFXglyph *zglyph  = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[zz]);
                    uint8_t zh  = pgm_read_byte(&zglyph->height);
                    int8_t  zyo = pgm_read_byte(&zglyph->yOffset);
                    if(zh > hbg) {
                        hbg = zh;
                    }
                    if(zyo < yobg) {
                        yobg = zyo;
                    }
                }
            }
            for(yy=0; yy<yo-yobg; yy++) {           // top
                for(xx=xo; xx<w+xo; xx++) {
                    writePixel(x+xx, y+yobg+yy, bg);
                }
            }
            for(yy=0; yy<hbg+yobg-yo-h; yy++) {     // bottom
                for(xx=xo; xx<w+xo; xx++) {
                    writePixel(x+xx, y+yo+h+yy, bg);
                }
            }
            for(yy=0; yy<hbg; yy++) {               // left
                for(xx=0; xx<xo; xx++) {
                    writePixel(x+xx, y+yobg+yy, bg);
                }
            }
            for(yy=0; yy<hbg; yy++) {               // right
                for(xx=w+xo; xx<xA; xx++) {
                    writePixel(x+xx, y+yobg+yy, bg);
                }
            }
        }
        for(yy=0; yy<h; yy++) {
            for(xx=0; xx<w; xx++) {
                if(!(bit++ & 7)) {
                    bits = pgm_read_byte(&bitmap[bo++]);
                }
                if(bits & 0x80) {
                    if(size == 1) {
                        writePixel(x+xo+xx, y+yo+yy, color);
                    } else {
                        writeFillRect(x+(xo16+xx)*size, y+(yo16+yy)*size, size, size, color);
                    }
                } else if((size==1)&&(bg != color)&&(c+first>='0'-first)&&(c+first<='9')) {
                    writePixel(x+xo+xx, y+yo+yy, bg);
                }
                bits <<= 1;
            }
        }
        endWrite();

    } // End classic vs custom font
}
void Adafruit_GFX::setTextColor(uint16_t c, uint16_t b) {
    textcolor   = c;
    textbgcolor = b;
    hbg = 0;
    yobg = 0;
}

Einschränkungen:

  • Hintergrundpixel werden nur bei Ziffern gesetzt. Andere Zeichen werden transparent dargestellt.
  • Nur Fonts in Originalgröße ( setTextSize(1); ) finden Berücksichtigung.
  • Fonts mit unterschiedlichen Werten von xA innerhalb der Ziffern führen zu horizontal "tanzenden" Ziffern, weshalb solche Fonts zur Darstellung von Zählern ungeeignet sind. Das gilt unabhängig von dieser Erweiterung.
  • Fonts mit sich horizontal überlappenden Ziffern führen zu einzelnen flackernden Pixeln. Davon sind leider alle Fonts mit "Oblique" und "Italic" betroffen.

Test:

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1331.h>
#include <SPI.h>
#define FONT FreeSansBold24pt7b
#include <Fonts/FreeSansBold24pt7b.h>

#define rst  8
#define dc   9
#define cs   10
Adafruit_SSD1331 display = Adafruit_SSD1331(cs, dc, rst);

#define BLACK           0x0000
#define BLUE            0x001F
#define RED             0xF800
#define GREEN           0x07E0
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xFFE0
#define WHITE           0xFFFF

void setup(void) {
  display.begin();
  display.setFont(&FONT);
  display.setTextSize(1);
  display.fillScreen(YELLOW);

  display.writeFillRect(0, 41, 96, 64, RED);
  display.setCursor(0, 40);
  display.setTextColor(WHITE, BLACK);
  display.print("015");

}
void loop() {}

Das sieht dann so aus, wobei ich die errechneten Pixel links und rechts grün und oben und unten magenta eingefärbt habe, der rote Hintergrund ist unterhalb der Basislinie:

Fontbemaßung AdafruitGFX 002.png

Fontbemaßung AdafruitGFX 002.png

Aus meiner Frage habe ich inzwischen eine Anleitung gemacht. Die Änderungen können bei Bedarf gerne verwendet werden.

Anmerkungen und Verbesserungsvorschläge sind willkommen :slight_smile:

agmue:
Aus meiner Frage habe ich inzwischen eine Anleitung gemacht. Die Änderungen können bei Bedarf gerne verwendet werden.

Anmerkungen und Verbesserungsvorschläge sind willkommen :slight_smile:

Die Bibliothek "SSD1306.h" flackert grundätzlich nicht.
Der neuen Inhalt wird zuerst geschrieben, dann mit display.display(); auf dem Bildschirm gebracht.
Sie tickt aber ganz anders, als Adafruit: man schreibt immer an absoluten Koordinaten.

Wenn man Text mit Variablen in einer Zeile schreiben will, empfielt sich zuerst alles in einem String mit sprintf()zu rechnen und den String auszugeben.

Der Vorteil von sprintf() ist auch dass es die Formatierung von Uhrzeiten vollständig übernimmt.

Hallo agmue!

Danke für deine Ausführungen.
Leider verstehe ich als Anfänger noch nicht genug um das jetzt zu verstehen.

Kurz zu meinem Problem, das ja weitestgehend bekannt ist.

Ich habe hier ein Adafruit-Feather-M0-Proto-Board mit einem Featherwing 2,4".
Projekt soll analoge Sensorwerte auslesen und verarbeiten.
Diese sollen dann mit mindestens 24 Pixel Größe auf einem Display dargestellt werden.
Bei dieser Größe kann man die original 7x5 Schrift aber nicht ansehen.
Das Blinken wenn man ein Rechteck aufzieht ist gleichermaßen unerträglich.

Meine Überlegung ging in Richtung Schriftart so zu ändern, dass die Hintergrundfarbe berücksichtigt wird.
Ich habe das www schon durchsucht nach irgendwelchen Schriftarten die die Hintergrundfarbe unterstützen,
aber leider nichts gefunden.
Es muss doch möglich sein eine Schriftart nach dem gleichen Prinzip wie die originale GFX zu erstellen.
Noch dazu gehts ja nur um die Ziffern 0-9 und ein Komma.

Kann mir vielleicht jemand dabei helfen?

Schebi:
Kann mir vielleicht jemand dabei helfen?

Da wäre ich mal optimistisch.

Allerdings hast Du kein OLED, weshalb Deine Frage weder zu dem einen noch zu diesem Thema so recht passt. Daher wäre es m. E. sinnvoll, wenn Du ein eigenes Thema mit einem aussagekräftigen Titel anfängst. Dann erreichst Du mehr potentielle Helfer.

Danke für die Info!

Auf einem Display mit 9341 LCD driver lasse ich mir gerade Zahlen mit dem Font FreeMono24pt7b anzeigen. Soweit ich es erkennen kann, gibt es keine Probleme. Das könntest Du auch mal probieren.

Hab ich probiert, schreibt sich inneinander!
Allerdings verwende ich die GFX_Library

Hast Du denn auch die Anpassungen in der Bibliothek gemacht und die Einschränkungen in #2 berücksichtigt? Ohne geht es natürlich nicht.

Auch für den Fall dass ich etwas doof bin, aber du meinst genau das was du in diesem Fred ganz oben in der Library verändert hast, oder?

Das hab ich schon probiert, aber sicher irgendwas falsch gemacht.
Da hats mir eine Fehlermeldung angezeigt.

Ich probier es einfach nochmal.

Wenn ich die Library soweit geändert habe, dann kommt ein Fehler beim kompilieren.
Der ist echt lang.

Wie hängt man solchte Programmteil-Felder hier rein?

RIN67630:
Die Bibliothek "SSD1306.h" flackert grundätzlich nicht.

Wie zur Hölle kommst Du jetzt auf SSD1306? Entweder habe ich etwas in Agmues Sketches überlesen, oder Deine „interne Fehlerkorrektur“ ist über-ambitioniert :slight_smile:

Gruß

Gregor

Schebi:
Wenn ich die Library soweit geändert habe, dann kommt ein Fehler beim kompilieren.
Der ist echt lang.

Wie hängt man solchte Programmteil-Felder hier rein?

Ich habe das gleiche Problem, nachdem ich die Änderungen durchgeführt habe lässt sich nicht mehr kompilieren, Fehler im Anhang. Adafruit_GFX Library 1.7.5 mit einem SSD1351 Wäre super wenn sich das lösen ließe.

Gruß

Martin

compiler_error.txt (16.5 KB)

Inzwischen nutze ich die Bibliotheken von Oli Kraus, weil ich sie besser finde als die von Lady Ada. Für Dein Display wäre das die Ucglib. Kann ich Dich dazu überreden oder gar überzeugen?

Hatte ich mir kurz angesehen, dann muss ich halt alles neu schreiben :frowning:
Flickert die ucglib denn nicht?
Ich schreibe auch ein paar Bitmaps aufs Display, da hätte ich mit Ucglib noch keine Idee wie...

Ich mach mir mal ne Demo...

Danke und Gruß

Martin

Nach Reaktivierung von Bibliothek und Testsketch von April 2018 kompiliert dieser mit IDE 1.8.6 für den UNO ohne Probleme.

Wo jetzt Zeit investieren und wie?

azmartin:
Ich schreibe auch ein paar Bitmaps aufs Display, da hätte ich mit Ucglib noch keine Idee wie...

Im Anhang ein ungetesteter Sketch von Oli aus dem Jahr 2015 (Quelle). Hilft Dir das?

ShowTGA.ino (5.6 KB)

Hallo agmue,

erstmal allerherzlichsten Dank dass Du Dir Zeit für mich nimmst. Ich will das trotzdem nicht überstrapazieren und damit Du nicht raten musst: hier ein bisschen Hintergrundinfo zu dem was ich da eigentlich mache:

Das Projekt ist eine kleine Anzeige die verschiedene Seiten Messwerte anzeigt, mit einem Drehgeber geblättert. Hardware: Arduino nano mit SSD1351. Beim Booten soll ein kleiner Splashscreen gezeigt werden, ich habe aber keine SD oder so, daher stehen die 2 mono-Bitmaps roh im Code, das eine wird in weiss angezeigt und das andere darunter in rot. Die Sensorwerte ändern sich teils recht schnell und daher sollen sie so flickerfrei wie möglich sein. Es soll einen monospace-custom font geben der wie ein 7-Segment Display aussieht und optional dazu noch einen Arial-ähnlichen. Mit der Adafruit library reicht offenbar der Hauptspeicher nicht für die Bilder und beide Fonts, daher läuft da aktuell nur der 7-Segment Font.

Die ucglib-Demo hab ich mir schnell mal angepasst, sieht einstweilen flickerfrei aus...

Probleme zu lösen:
mit der Adafruit Library: Flickern
mit ucglib: Custom 7-Segment Font einbinden, 2 Bitmaps aus progmem hinpinseln, Bonus: Möglicherweise klappts hier mit den 2 Fonts besser.