Forum: Compiler & IDEs T6963 Geschwindigkeitsprobleme bei benutzderdef. Fonts


von Philipp P. (putzer_philipp)


Angehängte Dateien:

Lesenswert?

Servus Leute,

zur Darstellung von benutzerdefinnierten Schriftarten auf einem 
Grafik-LCD T6963c verwende ich folgende lib:

"GLCD T6963C Font-Tool" Beitrag "GLCD T6963C Font-Tool"

Funktioniert auch sehr gur, nur leider dauert ein aufbau eines 
5-Zeilingen Textes mit Schriftgröße 13 MS Sans Serif ca. 1 Sekunde.

Der Prozessor ist ein M64, mit 16MHz Takt. Meiner Meinung nach ist das 
ganze so langsam, da die Table mit den Zeichen erst aus dem Flash geholt 
werden muss und dann Pixel für Pixel ans LCD geschrieben wird.

Hat jemand eine Idee wie ich das ganze beschleunigen kann?? Ich kann die 
Schriftart nicht in den RAM legen, der ist dafür zu klein.

Ich hab mein kleines Besispielprogramm mal angehängt, bin für jede Hilfe 
dankbar


Gruß

Philipp

von Jens P. (picler)


Lesenswert?

Beschreibst du das Display pixelweise (mit SetPixel oder so ähnlich) 
oder schreibst du direkt Byte für Byte in den RAM?

Ich habe ein 240x64px-Display mit dem T6963 an einem PIC 16F876 mit 4MHz 
hängen. Ein komplettes Bild darzustellen, dauert ca. 80ms. Schriftgrößen 
sind eine Zeile mit 40x24px und darunter eine mit 16x12px. Die Fontdaten 
lade ich auch direkt aus dem Flash des Controllers. Programmiert habe 
ich allerdings in Assembler. Möglicherweise sind die C-Routinen zu 
langsam.

von Philipp P. (putzer_philipp)


Lesenswert?

die Routine die fürs schreiben ans LCD zuständig ist verwendet 
set_pixel.

Wenn ich einen normalen Text mit z.B. glcd_print_ram schreibe sehe ich 
keine Verzögerung, allerdings ist die Schriftart auf 8x8 beschränkt. 
Geschickt wäre es, wenn ich dem LCD die Font einprogrammieren könnte und 
die Texte dann nur mehr mit glcd_print schreiben kann.

von Karl H. (kbuchegg)


Lesenswert?

Du beschäftgist mit diesem hier
1
void glcd_cput(int byte) // write command byte to LCD module
2
{  do 
3
  {  ;
4
  } while ((0x03 & glcd_sget()) != 0x03); // wait until display ready
5
6
  glcd_DATA_PORT = byte;  // present data to LCD on PC's port pins
7
8
  glcd_cd_high();         // control/status mode
9
  glcd_rd_high();         // make sure LCD read mode is off
10
  glcd_wr_low();         // activate LCD write mode
11
  glcd_ce_low();         // pulse ChipEnable LOW, > 80 ns, enables LCD I/O
12
  glcd_ce_high();         // disable LCD I/O
13
  glcd_wr_high();         // deactivate write mode
14
15
}
16
17
void glcd_pixel(int column, int row,char show)
18
{  int addr;       // memory address of byte containing pixel to write
19
  if( (column>=glcd_XMAX) || (row>=glcd_YMAX) )return;
20
  addr =  glcd_G_BASE + (row*glcd_BYTES_PER_ROW)  + (column/glcd_FONT_WIDTH);
21
  glcd_set_address(addr);   // set LCD addr. pointer
22
  if(show)  glcd_cput(0xf8 | ((glcd_FONT_WIDTH-1-column%glcd_FONT_WIDTH)) );  // set bit-within-byte command
23
  else    glcd_cput(0xf0 | ((glcd_FONT_WIDTH-1-column%glcd_FONT_WIDTH)) );  // set bit-within-byte command
24
}
25
26
void lcd_print2_p(unsigned int x,unsigned int y, const char *in, const struct FONT_DEF *strcut1, unsigned char invers)
27
{
28
  register unsigned int offset,width;
29
  register unsigned char i,j,map,ertefah,allwidth=0;
30
31
  while((map = pgm_read_byte(in++))) 
32
  {
33
    map = pgm_read_byte(&strcut1->mapping_table[map]);
34
35
    width = strcut1->glyph_width;
36
    if(width == 0)
37
      width = pgm_read_byte(&strcut1->width_table[map]);
38
39
    offset = pgm_read_word(&strcut1->offset_table[map]);
40
    ertefah = strcut1->glyph_height;
41
42
  
43
    for(j=0 ; j<ertefah * (((width-1)/8)+1) ; j+=(((width-1)/8)+1)    )
44
    {   // ertefah
45
      for(i=0 ; i<width  ; i++)
46
      {   //  width
47
        if( pgm_read_byte(&strcut1->glyph_table[ offset+j+(i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) )
48
          glcd_pixel(  x+i+allwidth , y+j/ (((width-1)/8)+1), !invers  );
49
         else
50
           glcd_pixel(  x+i+allwidth , y+j/ (((width-1)/8)+1), invers  );
51
        }//End i
52
      }// End j
53
    allwidth+=width;
54
  }// End K
55
}

den Prozessor enorm.
Von der Theorie her ist da im Grunde nichts auszusetzen, ist alles 
logisch aufgebaut und nachvollziehbar richtig. Aber eben naiv und ohne 
Rücksicht auf Verluste programmiert.

Da wirst du wohl in den sauren Apfel beissen müssen und dir ein anderes 
Verfahren ausdenken müssen.
Grundsatz: Nicht einzelpixelweise, sondern soviele Pixel in einem 
Aufwasch, wie nur geht. Im Idealfall willst du komplette Bytes 
schreiben.

Bei dem Code kann man zwar den einen oer anderen Takzyklus noch 
einsparen, aber Wunder darf man keine erwarten. Klar kann man ein 
Schwimmbecken mit einem Teelöffel und ständigem Laufen zum Wasserhahn 
auch voll kriegen, aber egal wie gut du deine Gehtechnik verbesserst, 
mit einem Schlauch gehts einfach schneller.

von Philipp P. (putzer_philipp)


Lesenswert?

ok, ja so was hab ich befürchtet. Du meinst die lcd_print2_p(...) frist 
zu viel Leistung oder? Ich werd mir die for-Schleife mal genauer 
anschauen, vielleich kann man die verbessern.

Glaubst du, dass die Berechnung in set_pixel
addr =  glcd_G_BASE + (row*glcd_BYTES_PER_ROW)  + 
(column/glcd_FONT_WIDTH);

viel ausmacht?

von Karl H. (kbuchegg)


Lesenswert?

Das ganze Verfahren, wie der Text in Pixel aufgelöst und jedes Pixel 
einzeln zum GLCD übertragen wird, ist Müll.
Es ist das Verfahren an sich, nicht die Details.

Du kannst an den Details schrauben soviel du willst, das wird nie 
richtig schnell.

wenn du so willst:
Was geht schneller?
Einen Getränke-LKW abladen, indem man mit jeder Flasche einzeln geht 
oder denselben LKW abladen, indem man sich möglichst volle 
Getränkekisten schnappt?
Dein Code geht mit jeder Flasche einzeln.

von Karl H. (kbuchegg)


Lesenswert?

Allerdings kann man in dieser Schleife wirklich noch viel machen.
1
   for(j=0 ; j<ertefah * (((width-1)/8)+1) ; j+=(((width-1)/8)+1)    )
2
    {   // ertefah
3
      for(i=0 ; i<width  ; i++)
4
      {   //  width
5
        if( pgm_read_byte(&strcut1->glyph_table[ offset+j+(i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) )
6
          glcd_pixel(  x+i+allwidth , y+j/ (((width-1)/8)+1), !invers  );
7
         else
8
           glcd_pixel(  x+i+allwidth , y+j/ (((width-1)/8)+1), invers  );
9
        }//End i
10
      }// End j

AUch wenn ich als ein starker Verfechter des Optimizers bekannt bin, war 
dir das nicht selber zu blöd, da ständig ((width-1)/8)+1) zu schreiben?

Der Wert ändert sich nie, schaon alleine aus Übersichtsgründen lohnt es 
sich die Berechnung vor die Schleife zu holen und sich das Ergebnis in 
einer Variablen aufzuheben
1
   NrBytes = ((width-1)/8)+1;
2
3
   for(j = 0; j < ertefah * NrBytes; j += Nrbytes )
4
   {   // ertefah
5
     for( i = 0; i < width; i++ )
6
     {   //  width
7
       if( pgm_read_byte( &strcut1->glyph_table[ offset + j + (i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) )
8
         glcd_pixel(  x+i+allwidth , y + j / NrBytes, !invers  );
9
       else
10
         glcd_pixel(  x+i+allwidth , y + j / NrBytes, invers  );
11
     }//End i
12
   }// End j

Sehr sinnig ist es auch, das j immer um NrBytes zu erhöhen, also in 
Vielfachen von NrBytes zu operieren, nur damit beim glcd_pixel Aufruf 
der NrBytes Anteil wieder herausdividiert werden muss. Da das j beim 
pgm_readByte direkt benutzt wird, bietet es sich an 2 Variablen zu 
benutzen, einmal ein j welches um 1 erhöht wird und eine 2.te die das 
NrBytes-Vielfache davon enthält.
1
   NrBytes = ((width-1)/8)+1;
2
3
   for(j = 0, jBytes = offset; j < ertefah; j += Nrbytes, jBytes += NrBytes )
4
   {   // ertefah
5
     for( i = 0; i < width; i++ )
6
     {   //  width
7
       if( pgm_read_byte( &strcut1->glyph_table[ jBytes + (i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) )
8
         glcd_pixel(  x+i+allwidth , y + j, !invers  );
9
       else
10
         glcd_pixel(  x+i+allwidth , y + j, invers  );
11
     }//End i
12
   }// End j

Die Shift Operation ist eine von der schlimmen Sorte. Man macht auf 
einem AVR keinen Shift mit einer variablen Anzahl von Bits. Der AVR kann 
das nicht, sondern diese Operation muss intern mit einer Schleife 
abgehandelt werden.

Die Idee ist an dieser Stelle offensichtlich, sich eine Maske 
zurechtzulegen, bei der ein 1 Bit nacheinander an den Positionen 7, 6, 
5, 4, ... auftaucht.
1
   NrBytes = ((width-1)/8)+1;
2
3
   for(j = 0, jBytes = offset; j < ertefah; j += Nrbytes, jBytes += NrBytes )
4
   {   // ertefah
5
     PixelMask = 1 << 7;
6
     for( i = 0; i < width; i++ )
7
     {   //  width
8
       if( pgm_read_byte( &strcut1->glyph_table[ jBytes + (i/8) ] ) & PixelMask )
9
         glcd_pixel(  x+i+allwidth , y + j, !invers  );
10
       else
11
         glcd_pixel(  x+i+allwidth , y + j, invers  );
12
       PixelMask >>= 1;
13
     }//End i
14
   }// End j


Der Wert von i/8 ändert sich nur nach jedem 8-ten Durchlauf durch die 
i-Schleife. Es würde sich daher lohnen, die i-Schleife in 2 Schleifen 
aufzuteilen, damit der pgm_read_byte nicht bei jedem Schleifendurchlauf 
gemacht werden muss. Eine äussere Schleife, die bis width/8  (gemeint 
ist an dieser Stelle offenbar die Anzahl der Bytes, die haben wir aber 
schon, steht in NrBytes) läuft. In dieser Schleife wird der 
pgm_read_byte gemacht und eine darin eingeschalchtelte Schleife, die das 
auf die Art gelesene Byte in die einzelnen Bits zerpflückt. Mit der 
Obergrenze der inneren Schleife beim letzten Durchlauf durch die äussere 
Schleife muss man ein wenig aufpassen, aber das geht schon.

Die y + j kann man noch herausfakturieren, indem ganz eifnfach y nach am 
Ende jedes Schleifendurchlaufs um 1 erhöht wird. Selbiges, allerdings 
mit einer Hilfsvariablen, kann man auch mit x und x + i + allwidth 
machen.

Nach dem Muster

   for( i = 0; i < irgendwas; ++i )
     machwas_mit( y + i );

wird zu

   for( i = 0; i < irgendwas; ++i, ++y )
     machwas_mit( y );

oder noch besser

   grenze = y + irgendwas;
   for( i = y; i < grenze; ++i )
     machwas_mit( i );

(Obwohl: die letzten Optimierungen können unter Umständen auch vom 
Compiler erledigt werden, Kommt drauf an, ob er davon ausgehen kann ob 
der Funktionsaufruf machwas_mit irgendwas bzw. y verändert oder nicht)

Aufpassen muss man auch noch auf die Datentypen.

Fast schon putzig hingegen macht sich die 'register' Definition von 
offset und width aus. Bloss gut, dass praktisch alle Compiler so eine 
Definition mitlerweile sowieso ignorieren.

Damit hat man schon mal die meisten Berechnungen aus den inneren 
Schleifen herausgezogen und so verteilt, dass der Compiler nicht ständig 
Register umladen muss.

Aber wie gesagt: erwarte keine Wunder. Das ist Kleinkosmetik.

(Und ich hab jetzt hoffentlich beim editieren keine Fehler eingebaut)

von holger (Gast)


Angehängte Dateien:

Lesenswert?

So, braucht jetzt statt 400ms nur noch 150ms.
Das meiste konnte aus der T6963.c rausgeholt werden. Zeit halbiert.
Der Rest so ungefähr nach den Vorschlägen von Karl Heinz.

von Philipp P. (putzer_philipp)


Lesenswert?

hey, nicht schlecht, respekt!

Ich werds nachm Essen gleich mal ausprobiern, vielein vielen Dank

von holger (Gast)


Lesenswert?

Upps, nimm dein eigenes makefile! Hab den Prozessor und die Pfade 
geändert;)

von holger (Gast)


Lesenswert?

Mit -O2 statt -Os und

static inline unsigned char glcd_sget(void)  // get LCD display status 
byte

komme ich jetzt auf 130ms.

von Philipp P. (putzer_philipp)


Lesenswert?

hey super, läuft einwandfrei! nicht schlecht! Von der Geschwindigkeit 
auch ausreichend, sieht jetzt super aus!


vielen Dank noch mal!


Gruß

Philipp

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.