Forum: Mikrocontroller und Digitale Elektronik DOGM-Lib von Jan (Mueschel) - Grafikfunktionen benötigt


von Martin G. (hb9tzw)


Lesenswert?

Mit der genannten Library (vielen Dank an den Autor an dieser Stelle) 
habe ich guten Erfolg gehabt mit einem DOGM132 Display, funktionierte 
schnell sehr gut.

Nun benötige ich aber noch die üblichen Grafikfunktionen, im Moment vor 
allem gefüllte und ungefüllte Rechtecke. Da die Library keine Funktion 
setpixel oder so ähnlich enthält, die meisten Rechteckfunktionen aber so 
eine benötigen, muss die als Erstes her. Wie realisiert man die bei 
diesem Display?

An die entsprechende Stelle zu kommen ist nicht das Problem, das es aber 
keinen Ramspeicher oder so gibt, und das auch so bleiben soll, gibt es 
ein Problem, da immer ein ganzes Byte geschrieben werden muss und die 
anderen Bits, die schon gesetzt sind, wieder überschrieben werden...

Gibts dafür eine Lösung?

Grüsse
Martin

von spess53 (Gast)


Lesenswert?

Hi

>Byte lesen, das bewusste Bit setzen, Byte zurückschreiben

Das Display kann man aber nicht auslesen!

Also entweder ein Abbild im RAM anlegen oder geschickt planen.

MfG Spess

von Karl H. (kbuchegg)


Lesenswert?

Martin Geissmann schrieb:

> Nun benötige ich aber noch die üblichen Grafikfunktionen, im Moment vor
> allem gefüllte und ungefüllte Rechtecke. Da die Library keine Funktion
> setpixel oder so ähnlich enthält, die meisten Rechteckfunktionen aber so
> eine benötigen, muss die als Erstes her. Wie realisiert man die bei
> diesem Display?


Byte lesen, Bit setzen, Byte schreiben

Da du vom LCD nicht lesen kannst, musst du dir im Programm ein 
entsprechendes Duplikat des Display-RAM anlegen (falls das deine Lib 
nicht sowieso schon macht)

> keinen Ramspeicher oder so gibt, und das auch so bleiben soll,

das geht nicht. Entweder du hast irgendwo Speicher aus dem du die 
aktuelle Bitsituation auslesen kannst, dann kannst du allgemeine 
Grafikfunktionen aufsetzen. Oder du hast ihn nicht. Dann gehts nicht - 
zumindest nicht in voller Allgemeinheit.

> Gibts dafür eine Lösung?

In dem Fall: im Allgemeinen - nein.

von Martin G. (hb9tzw)


Lesenswert?

Ok wenns nicht anders geht habe ich mich drangemacht, die Bibliothek so 
abzuändern, dass in einen Zwischenspeicher geschrieben wird. Teilweise 
konnte ich anderen Code zu Hilfe ziehen. Eine Rechteckfunktion habe ich 
jetzt und die scheint auch zu funktionieren, aber dafür habe ich 
Probleme mit der Schriftausgabe. Aber von vorne, ich habe fongendes 
getan:

Zuerst ein Array, das so lang ist wie das Display Punkte hat (132x32):
1
uint8_t lcd_ram[540];

und dann eine Funktion, die einzelne Pixel setzt:
1
void lcd_set_pixel(uint8_t x, uint8_t y, uint8_t pixel_status) {
2
3
  if(x < LCD_WIDTH && y < LCD_HEIGHT) {
4
    if(pixel_status != 0) {
5
      lcd_ram[x + ((y / 8) * 132)] |=  (1 << (y & 0x07));
6
    } else {
7
      lcd_ram[x + ((y / 8) * 132)] &= ~(1 << (y & 0x07));
8
    }
9
  }
10
}

und eine, die ganze Bytes schreibt:
1
void lcd_ram_write(uint8_t data, uint8_t page, uint8_t col) {
2
3
  uint8_t temp;
4
  for(temp = 0; temp < 8; temp++) {
5
    if(((data >> temp) & 0x01) == 1) {
6
      lcd_set_pixel(col, ((page*8)+temp), 0);
7
    } else {
8
      lcd_set_pixel(col, ((page*8)+temp), 1);
9
    }
10
  }
11
}

Soweit scheints zu funktionieren, zum Test habe ich ein kleines 
Schachbrettmuster geschrieben. Nun schaffe ich es aber nicht, die 
Funktion korrekt abzuändern, die einen ganzen Character schreibt. 
Original sieht die so aus:
1
uint8_t lcd_put_char(FONT_P font, uint8_t style, char character) {
2
3
    int8_t  i;
4
    uint8_t row  = 0;                             //current row of char
5
    uint8_t hc   = (style & DOUBLE_HEIGHT)?1:0;   //height changed
6
    uint8_t wc   = (style & DOUBLE_WIDTH)?1:0;    //width changed
7
    uint8_t ul   = (style & UNDERLINE)?0x80:0x00; //underline
8
    uint8_t inv  = (style & INVERT)?0xFF:0;       //inverted
9
    uint8_t tmp;
10
11
    //load information about character
12
     uint8_t char_width    = font_get_char_width(font,character); 
13
     uint8_t font_height   = font_get_height_bytes(font);
14
     uint8_t free_space    = font_get_add_space(font,character);
15
     PGM_P   tableposition = font_get_char_position(font,character);
16
17
    //final size of character
18
    uint8_t char_final_width  = (uint8_t)(char_width+free_space) << wc;
19
    uint8_t char_final_height = (uint8_t)font_height << hc; 
20
21
    //check for avail. space on display
22
    if((style & WRAP) && (LCD_CURRENT_COL() + char_final_width > LCD_WIDTH)) {
23
      LCD_MOVE_TO(LCD_CURRENT_PAGE()+char_final_height,0);
24
      if(character == ' ') return 0;
25
    }
26
  
27
    //write chracter
28
    do {
29
      for(i=(row>>hc); i<char_width*font_height; i+=font_height) {
30
          tmp = pgm_read_byte(tableposition+i);
31
          if(row == char_final_height-1) 
32
            tmp |= ul;
33
          if(hc)
34
            tmp = double_bits((row&1),tmp);
35
          if(inv)
36
            tmp = ~tmp;
37
          LCD_WRITE(tmp);
38
          if(wc) 
39
            LCD_WRITE(tmp);
40
        }
41
      if(free_space) {
42
          uint8_t c = inv;
43
          if(row == char_final_height-1) {
44
            c ^= ul; 
45
            if(hc)
46
                c ^= ul>>1;      
47
          }
48
          LCD_WRITE(c);
49
          if(wc) 
50
            LCD_WRITE(c);
51
        }
52
      LCD_MOVE(1,-char_final_width);
53
  } while(++row < char_final_height);
54
55
    //move cursor to upper right corner of character
56
    LCD_MOVE(-char_final_height,char_final_width);
57
    return char_final_width;
58
}

Statt
1
LCD_WRITE(tmp);
 soll die neue Funktion
1
lcd_ram_write(tmp, row, pos);
 verwendet werden, aber ich schaffs wie gesagt nicht, dass da was 
gescheites angezeigt wird, was wohl vor allem an der Position liegt. 
Zudem muss ich auch zugeben, dass ich nicht ganz durchblicke, wie diese 
Fonts organisiert sind.

Kann bitte jemand helfen? Der Originale Quelltext ist in folgendem 
Thread zu finden:

Beitrag "Library für EA-DOGM Grafikdisplays inkl. Font-Generator" (Version 092)

Grüsse
Martin

von Karl H. (kbuchegg)


Lesenswert?

Martin Geissmann schrieb:

> Zuerst ein Array, das so lang ist wie das Display Punkte hat (132x32):

Gut


> Schachbrettmuster geschrieben. Nun schaffe ich es aber nicht, die
> Funktion korrekt abzuändern, die einen ganzen Character schreibt.

Schreib sie neu.
So kompliziert ist das nicht und danach verstehst du sie.

Hier ist dein Ausgangspunkt
1
struct font_info {
2
    uint16_t size;       //size of data array
3
    uint8_t  width;      //(maximum) width of character
4
    uint8_t  height;     //height of character
5
    uint8_t  firstchar;  //the number of the first included character (often 0x20)
6
    uint8_t  lastchar;   //the last included character (often 0xFF)
7
    PGM_P    widthtable; //Pointer to the table holding character widths (NULL for monospaced fonts)
8
    PGM_P    data;       //Pointer to data arrray
9
    };

Die Beschreibung eines Fonts

Als Beispeil nimmst du dir den einfachst möglichen Font her. Fixed 8 
Pixel

So sieht seine Struktur aus
1
  const struct font_info font_fixed_8px PROGMEM =
2
  {  256*6,              // komplette Grösse der Data Sektion in Bytes
3
     6,                  // pro Zeichen werden 6 Bytes benötigt
4
     8,                  // wobei ein Zeichen aus 8 übereinanderliegenden Pixel besteht
5
     0x00, 0xFF,         // erstes Zeichen in der Tabelle = 0x00, letzten = 0xFF, also kompletter ASCII Code inkl Sonderzeichen
6
     0,                  // keine Kerning Daten
7
     font_fixed_8px_data  // und dort finden sich die Daten
8
  };

soweit so gut.
Die einzelnen Felder dürften alle eindeutig sein. Oder gibt es dazu 
Fragen?

Angenommen du sollst ein 'A' ausgeben. Was gibt es zu tun.
Zunächst mal muss man die dazu nötigen Bytes (mit den Pixel) in der Data 
Tabelle finden.
Dazu benötigst du:
   Aus wievielen Bytes besteht denn 1 Zeichen (=width)?
   Denn klarerweise fängt die Beschreibung dieses Zeichens
       an der Zeichen*width Position an.

Das ist aber noch nicht alles. Die Fontbeschreibung erlaubt auch noch, 
dass am Anfang der Tabelle Zeichen für die keine Pixel vorhanden sind 
aus der Tabelle ausgelassen werden.

Das erste Byte für die Pixel von 'A' findet sich daher bei

    data [ ( 'A' - firstchar ) * width ];

'A' hat den ASCII Code 0x41 (= dez 65), firstchar ist in diesem Fall 0. 
width ist laut Struktur 6. D.h. das erste Byte mit codierenden Pixel für 
ein 'A' findet sich in data bei
   ( 65 - 0 ) * 6   ->  390   oder hex 0x186

also in data[390]

Sehen wir mit diesem Index in der Data Tabelle nach, so erhalten wir von 
diesem Index ausgehend die nächsten 6 Bytes (6, weil in der Struktur 
steht, dass wir 6 Bytes pro Buchstabe haben)

  0x00, 0x7E, 0x11, 0x11, 0x11, 0x7E

Das sind die 6 Bytes, die die Pixel kodieren. Wie tun sie das? Jedes Bit 
in je 1 Byte steht für ein Pixel und jedes Byte steht für eine komplette 
Spalte. Das weiss ich deswegen, weil in der Strukturbeschreibung steht, 
dass eine Zeichenhöhe von 8 vorliegt und in einem Byte gibt es nun mal 8 
Bits.

Schlüsseln wir die 8 Bytes mal nach Bits auf (in Spaltenform) dann 
erhalten wir:


       00  7E  11  11 11  7E

     +---+---+---+---+---+---+
 7   | 0 | 0 | 0 | 0 | 0 | 0 |
     +---+---+---+---+---+---+
 6   | 0 | 1 | 0 | 0 | 0 | 1 |
     +---+---+---+---+---+---+
 5   | 0 | 1 | 0 | 0 | 0 | 1 |
     +---+---+---+---+---+---+
 4   | 0 | 1 | 1 | 1 | 1 | 1 |
     +---+---+---+---+---+---+
 3   | 0 | 1 | 0 | 0 | 0 | 1 |
     +---+---+---+---+---+---+
 2   | 0 | 1 | 0 | 0 | 0 | 1 |
     +---+---+---+---+---+---+
 1   | 0 | 1 | 0 | 0 | 0 | 1 |
     +---+---+---+---+---+---+
 0   | 0 | 0 | 1 | 1 | 1 | 0 |
     +---+---+---+---+---+---+



lass ich mal die Tabellenlinien weg und schreibe die 0 mit einem 
Leerzeichen (für nicht gesetztes Pixel) und die 1 mit einem # (für 
gesetztes Pixel), dann kommt da raus


     #   #
     #   #
     #####
     #   #
     #   #
     #   #
      ###


Ich weiß nicht wie's dir geht. Aber für mich sieht das ganz stark nach 
einem auf dem Kopf stehenden 'A' aus.

D.h. die Pixel werden so ausgegeben: das 0-te BIt kommt oben hin, das 
7te Bit kommt unten hin.

D.h. die Grundstruktur wird (zunächst mal) so aussehen:

   berechen Anfangsadresse im data Array

   for( i = 0; i < width des Zeichens; ++i ) {
     byte = data[ anfangsadresse + i ];

     gib alle 8 Bit (genauer height bits) dieses Bytes aus
     beginnend beim Bit 0 {
       Bit i extrahieren
       if Bit ist 1
         set_pixel( x + i, y + Bitnummer des Pixels );
       else
         clear_pixel( x + 1, y + Bitnummer des Pixels );
     }
   }

x und y sind die Koordinaten, an der der Text ausgegeben werden soll. Wo 
du im Text dann deinen 0 Punkt haben willst, musst du noch festlegen. 
Übernimmst du x und y einfach so, dann ist der logischerweise links oben 
im Eck (weil ja x von dort mit jeder Spalte um 1 wächst, bzw. die Pixel 
mit steigender Pixelnummer um 1 nach unten gehen.


Ehe dich um all die möglichen Sonderfälle kümmerst, solltest du erst mal 
mit dem einfachst möglichen Font anfangen: 8 Pixel pro Spalte, 
Zeichenbreite fix. Daher: keine Sonderfälle, das Kochrezept kann man 
mehr oder weniger 1:1 von da oben übernehmen. Einzig die 6 kommen aus 
der Strukturbeschreibung (und natürlich der Pointer auf den Anfang des 
data Feldes)

Wenn du den erst mal auf dem Schirm hast, schaust du dir die anderen 
Fonts genauer an und versuchst erst mal auf dem Papier nachzuvollziehen, 
wie man zu den 0/1 für die Pixel kommt. Das wird sehr ähnlich sein wie 
beim einfachst möglichen Fall, nur muss man einige Sonderfälle mehr 
berücksichtigen.

von Martin G. (hb9tzw)


Lesenswert?

Vielen Dank Karl Heinz für die ausführliche Antwort! Das klappte mit 
dieser Hilfe soweit mit dieser Funktion:
1
uint8_t lcd_put_char_xy(FONT_P font, uint8_t style, char character, uint8_t page, uint8_t col) {
2
3
   uint8_t row  = 0;                             //current row of char
4
   uint8_t hc   = (style & DOUBLE_HEIGHT)?1:0;   //height changed
5
   uint8_t wc   = (style & DOUBLE_WIDTH)?1:0;    //width changed
6
   uint8_t ul   = (style & UNDERLINE)?0x80:0x00; //underline
7
   uint8_t inv  = (style & INVERT)?0xFF:0;       //inverted
8
   uint8_t tmp;
9
10
   //load information about character
11
   uint8_t char_width    = font_get_char_width(font,character); 
12
   uint8_t font_height   = font_get_height_bytes(font);
13
   uint8_t free_space    = font_get_add_space(font,character);
14
   PGM_P   tableposition = font_get_char_position(font,character); 
15
16
   //final size of character
17
   uint8_t char_final_width  = (uint8_t)(char_width+free_space) << wc;
18
   uint8_t char_final_height = (uint8_t)font_height << hc; 
19
20
   for(uint8_t i = 0; i < char_width; ++i) {
21
22
      uint8_t byte = pgm_read_byte(tableposition+i);
23
24
      for(uint8_t j = 0; j < 8; j++) {
25
26
         if(((byte >> j) & 0x01) == 1) {
27
28
            lcd_set_pixel(i+col, j+(page*font_height*8), 1);
29
30
         } else {
31
32
            lcd_set_pixel(i+col, j+(page*font_height*8), 0);
33
34
         }
35
      }
36
   }
37
   return char_final_width;
38
}

Den Anfang der Funktion habe ich stehen lassen weil es das alles früher 
oder später wohl wieder brauchen wird. Jetzt muss ich als nächsten 
Schritt die Funktion lcd_put_string_xy dazu schreiben, was ich ganz 
ähnlich der normalen Stringfunktion so versucht habe:
1
uint16_t lcd_put_string_xy(FONT_P font, uint8_t style, char* str, uint8_t page, uint8_t col) {
2
3
   unsigned char t;
4
   uint16_t length = 0;
5
   while((t = *str++)) {
6
      length += lcd_put_char_xy(font, style, t, page, col);
7
      col += 6;
8
   }
9
   return length;
10
}

Das klappt irgendwie nicht. Mit col += 6 erhöhe ich den Wert doch um die 
korrekte Characterbreite von 6 für diesen Font? Auch wenn das klappen 
würde, irgendwie müsste ich die Characterbreite ja bestimmen können um 
die Funktion universell zu benutzen. Wie könnte das geschehen?

von Olaf (Gast)


Lesenswert?

> Also entweder ein Abbild im RAM anlegen oder geschickt planen.

Da bereut man es bestimmt wenn man keinen fetten Microcontroller mit
20kB internem Ram hat oder? :-)

Ich hatte in der Elektor mal einen Artikel ueber ein Grafikdisplay am 
R8C13. Dort habe ich einen mitlaufenden Puffer verwendet. Man hat also 
nur die zuletzt geschriebenen 10, 20, 50 usw (beliebig einstellbar) 
Pixel im internen Ram. So konnte man den Rambedarf passend auf seine 
Anwendung zuschneiden. Normalerweise bedeutet das das man mit 25 bis 50% 
des Rams auskommt den ein Display sonst braucht. Gerade bei so mickrigen 
Controllern mit nur 1kByte Ram ist das eine grosse Hilfe.

Olaf

von Karl H. (kbuchegg)


Lesenswert?

Martin Geissmann schrieb:

>
1
> uint16_t lcd_put_string_xy(FONT_P font, uint8_t style, char* str,
2
> uint8_t page, uint8_t col) {
3
> 
4
>    unsigned char t;
5
>    uint16_t length = 0;
6
>    while((t = *str++)) {
7
>       length += lcd_put_char_xy(font, style, t, page, col);
8
>       col += 6;
9
>    }
10
>    return length;
11
> }
12
>
>
> Das klappt irgendwie nicht.

Kannst du das konkretisieren? Was klappt nicht?

> Mit col += 6 erhöhe ich den Wert doch um die
> korrekte Characterbreite von 6 für diesen Font? Auch wenn das klappen
> würde, irgendwie müsste ich die Characterbreite ja bestimmen können um
> die Funktion universell zu benutzen. Wie könnte das geschehen?

Die Characterbreite steht ja in der Font Struktur drinnen, auf die du 
freundlicherweise einen Pointer mitbekommen hast

    col += font->width;

Bzw. ich sehe gerade, dass es ja eine Funktion gibt, die für ein Zeichen 
die Breite bestimmt

     col += font_get_char_width( font, t );

NOch geschickter ist es allerdings, wenn du dir einmal ansiehst, was 
denn die Funktion lcd_put_char_xy eigentlich als Returnwert 
zurückliefert :-)
Genau den Wert, den du brauchst :-)

     while((t = *str++)) {
        col += lcd_put_char_xy(font, style, t, page, col);
     }

von Martin G. (hb9tzw)


Lesenswert?

Vielen Dank, in der Zwischenzeit hatte ich auch noch ein wenig 
Gelegenheit, damit rumzuprobieren, und es hat auch so geklappt, aber auf 
genau die Weise wie du schreibst:
1
uint16_t lcd_put_string_xy(FONT_P font, uint8_t style, char* str, uint8_t page, uint8_t col) {
2
3
   uint8_t t, i = 0;
4
   uint16_t length = 0;
5
   while((t = *str++)) {
6
      uint8_t width = font_get_char_width(font,t);
7
      length += lcd_put_char_xy(font, style, t, page, col+(i*width));
8
      i++;
9
   }
10
   return length;
11
}

Jetzt muss ich mich an die anderen Fonts wagen...

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.