mikrocontroller.net

Forum: Compiler & IDEs T6963 Geschwindigkeitsprobleme bei benutzderdef. Fonts


Autor: Philipp Putzer (putzer_philipp)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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

Autor: Jens PICler (picler)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Philipp Putzer (putzer_philipp)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du beschäftgist mit diesem hier
void glcd_cput(int byte) // write command byte to LCD module
{  do 
  {  ;
  } while ((0x03 & glcd_sget()) != 0x03); // wait until display ready

  glcd_DATA_PORT = byte;  // present data to LCD on PC's port pins

  glcd_cd_high();         // control/status mode
  glcd_rd_high();         // make sure LCD read mode is off
  glcd_wr_low();         // activate LCD write mode
  glcd_ce_low();         // pulse ChipEnable LOW, > 80 ns, enables LCD I/O
  glcd_ce_high();         // disable LCD I/O
  glcd_wr_high();         // deactivate write mode

}

void glcd_pixel(int column, int row,char show)
{  int addr;       // memory address of byte containing pixel to write
  if( (column>=glcd_XMAX) || (row>=glcd_YMAX) )return;
  addr =  glcd_G_BASE + (row*glcd_BYTES_PER_ROW)  + (column/glcd_FONT_WIDTH);
  glcd_set_address(addr);   // set LCD addr. pointer
  if(show)  glcd_cput(0xf8 | ((glcd_FONT_WIDTH-1-column%glcd_FONT_WIDTH)) );  // set bit-within-byte command
  else    glcd_cput(0xf0 | ((glcd_FONT_WIDTH-1-column%glcd_FONT_WIDTH)) );  // set bit-within-byte command
}

void lcd_print2_p(unsigned int x,unsigned int y, const char *in, const struct FONT_DEF *strcut1, unsigned char invers)
{
  register unsigned int offset,width;
  register unsigned char i,j,map,ertefah,allwidth=0;

  while((map = pgm_read_byte(in++))) 
  {
    map = pgm_read_byte(&strcut1->mapping_table[map]);

    width = strcut1->glyph_width;
    if(width == 0)
      width = pgm_read_byte(&strcut1->width_table[map]);

    offset = pgm_read_word(&strcut1->offset_table[map]);
    ertefah = strcut1->glyph_height;

  
    for(j=0 ; j<ertefah * (((width-1)/8)+1) ; j+=(((width-1)/8)+1)    )
    {   // ertefah
      for(i=0 ; i<width  ; i++)
      {   //  width
        if( pgm_read_byte(&strcut1->glyph_table[ offset+j+(i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) )
          glcd_pixel(  x+i+allwidth , y+j/ (((width-1)/8)+1), !invers  );
         else
           glcd_pixel(  x+i+allwidth , y+j/ (((width-1)/8)+1), invers  );
        }//End i
      }// End j
    allwidth+=width;
  }// End K
}

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.

Autor: Philipp Putzer (putzer_philipp)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Allerdings kann man in dieser Schleife wirklich noch viel machen.
   for(j=0 ; j<ertefah * (((width-1)/8)+1) ; j+=(((width-1)/8)+1)    )
    {   // ertefah
      for(i=0 ; i<width  ; i++)
      {   //  width
        if( pgm_read_byte(&strcut1->glyph_table[ offset+j+(i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) )
          glcd_pixel(  x+i+allwidth , y+j/ (((width-1)/8)+1), !invers  );
         else
           glcd_pixel(  x+i+allwidth , y+j/ (((width-1)/8)+1), invers  );
        }//End i
      }// 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

   NrBytes = ((width-1)/8)+1;

   for(j = 0; j < ertefah * NrBytes; j += Nrbytes )
   {   // ertefah
     for( i = 0; i < width; i++ )
     {   //  width
       if( pgm_read_byte( &strcut1->glyph_table[ offset + j + (i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) )
         glcd_pixel(  x+i+allwidth , y + j / NrBytes, !invers  );
       else
         glcd_pixel(  x+i+allwidth , y + j / NrBytes, invers  );
     }//End i
   }// 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.

   NrBytes = ((width-1)/8)+1;

   for(j = 0, jBytes = offset; j < ertefah; j += Nrbytes, jBytes += NrBytes )
   {   // ertefah
     for( i = 0; i < width; i++ )
     {   //  width
       if( pgm_read_byte( &strcut1->glyph_table[ jBytes + (i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) )
         glcd_pixel(  x+i+allwidth , y + j, !invers  );
       else
         glcd_pixel(  x+i+allwidth , y + j, invers  );
     }//End i
   }// 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.

   NrBytes = ((width-1)/8)+1;

   for(j = 0, jBytes = offset; j < ertefah; j += Nrbytes, jBytes += NrBytes )
   {   // ertefah
     PixelMask = 1 << 7;
     for( i = 0; i < width; i++ )
     {   //  width
       if( pgm_read_byte( &strcut1->glyph_table[ jBytes + (i/8) ] ) & PixelMask )
         glcd_pixel(  x+i+allwidth , y + j, !invers  );
       else
         glcd_pixel(  x+i+allwidth , y + j, invers  );
       PixelMask >>= 1;
     }//End i
   }// 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)

Autor: holger (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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.

Autor: Philipp Putzer (putzer_philipp)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
hey, nicht schlecht, respekt!

Ich werds nachm Essen gleich mal ausprobiern, vielein vielen Dank

Autor: holger (Gast)
Datum:

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

Autor: holger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mit -O2 statt -Os und

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

komme ich jetzt auf 130ms.

Autor: Philipp Putzer (putzer_philipp)
Datum:

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


vielen Dank noch mal!


Gruß

Philipp

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.