Go Down

Topic: Anleitung: Anzeige einer sich schnell ändernden Zahl ohne Flackern (Read 570 times) previous topic - next topic

agmue

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.

agmue

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:

Code: [Select]
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:

Code: [Select]
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:

Code: [Select]
 } 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
}

Code: [Select]
void Adafruit_GFX::setTextColor(uint16_t c, uint16_t b) {
    textcolor   = c;
    textbgcolor = b;
    hbg = 0;
    yobg = 0;
}

agmue

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.

agmue

Test:
Code: [Select]
#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:



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 :)

RIN67630

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

Anmerkungen und Verbesserungsvorschläge sind willkommen :)

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.

Schebi

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?


agmue

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.


agmue

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.

Schebi

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

agmue

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

Schebi

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.


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?


gregorss

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 :-)

Gruß

Gregor
Meine Pizza schmeckt am besten, wenn ich sie selber esse.

Go Up