Go Down

Topic: Anleitung: Anzeige einer sich schnell ändernden Zahl ohne Flackern (Read 209 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.

Go Up