- Akku Tests und Balkonkraftwerk Speicher         
Seite 2 von 3 ErsteErste 123 LetzteLetzte
Ergebnis 11 bis 20 von 22

Thema: Hilfe! Programm zu groß trotz aktivierter Optimierung!

  1. #11
    Benutzer Stammmitglied
    Registriert seit
    12.06.2007
    Beiträge
    42
    Anzeige

    LiFePo4 Akku selber bauen - Video
    Ich habe iTest von unsigned long nach unsigned int geändert (wird beim erreichen der 500 zurückgesetzt). Das Ergebnis ist (zumindest für mich) verblüffend!
    Der Code wird nach dieser Minimal-Änderung tatsächlich 64 Bytes kleiner!
    Vielen Dank an ogni42 für den Hinweis!

    Wenn mir nun jemand erklären könnte, wie der Wechsel von einer 32-Bit-Variable zu einer (denke ich) 16-Bit-Variable einen Unterschied von 64 Bytes ausmachen kann, wäre ich um eine wertvolle Erfahrung reicher.

  2. #12
    Benutzer Stammmitglied
    Registriert seit
    12.06.2007
    Beiträge
    42
    Der Versuch, aus iTest nun eine unsigned short zu machen, hat keine Besserung gebracht.

    Daraus schließe ich, dass es sich bei beiden Typen um 16-Bit-Variablen handelt.

    Ist es nicht so, dass die Größe eine int plattformabhängig ist, eine short aber immer 16 Bit groß ist, und dass es dann doch sinnvoll wäre, immer short zu nutzen, so dass man plattformunabhängig immer sicher sein kann, einen 16-Bit-Wert zu nutzen?

  3. #13
    Benutzer Stammmitglied
    Registriert seit
    12.06.2007
    Beiträge
    42
    Nun habe ich aus iTest eine unsigned char (8 Bit) gemacht und somit die Code-Größe um weitere 34 Bytes gedrückt.

    Für Anfänger wie mich, die sich erst an platzsparendes Programmieren gewöhnen müssen, doch sicherlich ein guter Tipp, oder? Bei kleinen Zählschleifen (<255) einfach unsigned char verwenden. Oder spricht irgendetwas dagegen?

    Mir kommt es allerdings vor, dass die Schleife mit unsigned char bis 250 NICHT doppelt so schnell Punkte sendet (s.o., Code-Block), wie die Variante mit unsigned short bis 500. Ich vermute, dass das Inkrementieren einer 8-Bit-Variable einfach schneller geht. In meinem Fall (Testprogramm) etwas ärgerlich, ansonsten aber doch ein positiver Effekt.

  4. #14
    Erfahrener Benutzer Roboter Experte
    Registriert seit
    15.11.2006
    Beiträge
    463
    Hallo,

    die Codeeinsparung durch die Verwendung kleinerer Variablen kann man damit erklären, dass es sich um einen 8 bit Prozessor handelt. Wenn man mit 16 oder 32 Bit Variablen arbeitet, dann sind bei jeder Operation auf diese Variablen (Addition, Zuweisung, Vergleich etc.) mehrere Assembler Befehle notwendig.

    Gruss
    Jakob

  5. #15
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Einen erstaunlichen Code-Schrumpf wirst du beobachten, wenn du auf (re)alloc etc verzichtest. Diese Funktionen bringen einen ziemlichen Overhead mit sich, sowohl was RAM, Laufzeit und auch Flash angeht.

    Du kannst vermutlich darauf verzichten. Ich hab deinen Code nicht genau angeschaut, wozu du das brauchst. Aber es ist besser, ein statisches Array zu nehmen, daß so groß ist wie deine maximale Puffergröße, anstatt ständig malloc/free zu machen!

    Evtl genügt schon eine FIFO, wenn die Zeichen Stoßweise ankommen.

    https://www.roboternetz.de/wissen/in...herallokierung
    https://www.roboternetz.de/wissen/in...FO_mit_avr-gcc
    https://www.roboternetz.de/wissen/in...pps_.26_Tricks
    Disclaimer: none. Sue me.

  6. #16
    Benutzer Stammmitglied
    Registriert seit
    12.06.2007
    Beiträge
    42
    Ja, ich bin gerade dabei realloc() rauszuschmeissen. Ich denke auch, dass die dynamische Speicherverwaltung der Grund dafür ist, warum das Programm irgendwann immer abschmiert (vor allem bei großen Strings)
    .
    Moderne PCs mögen mit den Gigabytes nur so um sich schmeissen, auf meinem Tiny2313 habe ich jedoch nur 128 Bytes SRAM, die ja schon teilweise belegt sind.

    Interessanter ist da die Variante, den freien Speicher zu berechnen, so dass ich mein Array danach ausrichten kann, wieviel Platz bereits durch andere Variablen belegt ist. Legt man eine solche Berechnung auf einen Taster, der das Ergebnis per UART ans Terminal schickt, hat man ständig den Überblick über den restlichen Speicher.

    Wer auch Anfänger ist, wie ich, und an der Berechnung des freien Speichers interessiert ist, sollte sich folgenden Link mal ansehen:

    https://www.roboternetz.de/wissen/in...en_mit_avr-gcc

    Später poste ich nochmal die wahrscheinlich deutlich reduzierte Programmgröße.

    Weitere Tipps zur Code-Optimierung sind natürlich willkommen!

    Gruß Matze

  7. #17
    Benutzer Stammmitglied
    Registriert seit
    12.06.2007
    Beiträge
    42
    So, das Programm ist trotz deutlich erweiterter Funktionalität auf ca. die Hälfte der Original-Größe geschrumpft (von 2032 auf 1088 Bytes)! Und vor allem läuft es jetzt fehlerfrei!

    realloc() ist einem statischen Array gewichen, was den Löwenanteil der Optimierung ausmacht. Ansonsten habe ich in der Hinsicht nur kleinere Verbesserungen vorgenommen (Variablen).

    Mit einem Taster kann ich mir nun den restlichen Speicher (SRAM) anzeigen lassen.

    Die wohl größte Veränderung ist aber beim Beschreiben des EEPROM zu finden. Da dieser Speicher nur ca. 100000 mal beschreibbar ist, habe ich zwei Funktionen geschrieben, die ihn fortlaufend (immer im Kreis) beschreiben. Den Tipp hat mir ein arbeitskollege gegeben. So wird das EEPROM geschont und der AVR hält länger. Beim Testen vielleicht egal, im produktiven Betrieb aber doch schon von großem Vorteil.

    Da ich mir vorstellen kann, dass es noch andere AVR-Anfänger wie mich gibt, denen das Programm vielleicht eine Hilfe sein kann, poste ich hier nochmal den aktuellen Code:

    Code:
    /*  T E S T P R O G R A M M  (USART+EEPROM)
     *  ---------------------------------------
     *  
     *  Dieses Testprogramm (geschrieben für ATTiny2313) empfängt und sendet Zeichen über die
     *  RS232-Schnittstelle (UART/USART) und ermöglicht die folgenden Funktionen:
     *
     *  SW0 (Taster 0, bei mir an PIND2): empfangene Zeichen im EEPROM speichern
     *
     *  SW1 (Taster 1, bei mir an PIND3): gespeicherte Zeichen wieder auslesen und ans Terminal senden
     *
     *  SW2 (Taster 2, bei mir an PIND4): Anzahl aktuell eingelesener Zeichen ans Terminal senden
     *
     *  SW3 (Taster 3, bei mir an PIND5): freien Speicher (SRAM) ans Terminal senden
     *
     *  Zum Testen dieses Programm benötigt man ein Terminal-Programm (ich nutze HTerm), mit dem man
     *  Daten zum AVR schicken und von ihm empfangen kann.
     *
     *  Wenn das Terminal-Programm verbunden ist, sieht man, dass kontinuierlich ein Punkt ('.') gesendet
     *  wird. Dies ist als Lebenszeichen des AVR zu verstehen. Bricht der Datenstrom plötzlich ab, ist
     *  das Programm wahrscheinlich hängengeblieben (z.B. wegen Speicherüberlauf o.ä.).
     *
     *  Da das SRAM in seiner Größe sehr beschränkt ist (beim ATTiny2313 nur 128 Bytes), habe ich eine
     *  maximale Größe für den empfangenen String festgelegt. Sie kann über IUSART_INP_MAX geändert werden.
     *
     *  Das Beschreiben des EEPROM ist in der Hinsicht kritisch, als dass der Speicher nur
     *  ca. 100000 mal beschreibbar ist. Deshalb ist es nicht sinnvoll, jedesmal wieder ab
     *  Byte 0 zu schreiben. Deshalb habe ich die Funktionen eeprom_get_last_byte() und
     *  eeprom_get_first_written_byte() geschrieben. Mithilfe dieser Funktionen kann man so
     *  navigieren, dass das EEPROM kontinuierlich beschrieben wird, so dass alle Bytes
     *  gleichmäßig beansprucht werden und die Lebensdauer deutlich erhöht wird. Erreicht man
     *  das Ende des EEPROM (beim ATTiny2313 128 Bytes, änderbar über EEPROM_MAX_BYTE), so
     *  beginnt man wieder bei Byte 0, wobei ein String auch überlappen kann (z.B. ein
     *  5-Zeichen-String in den Bytes 126, 127, 0, 1 und 2).
     *
     *  Bei meinem Testboard (STK500) habe ich die Taster nicht nach Standardkonfiguration angeschlossen,
     *  wie oben an der Funktionsübersicht zu sehen ist (SW0 auf PIND2 statt PIND0 usw.). Wer ganz normal
     *  SW0 an PIND0 hat, muss lediglich diese wenigen Stellen im Code abändern.
     *
     *  Beispiel:
     *  ---------
     *    
     *  if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
     *                       -
     *  ändern in
     *
     *  if( !(PIND & (1<<PIND1)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
     *                       -
     *
     *  Tipp: Wer HTerm benutzt, sollte in der oberen Ansicht (vom AVR gesendete Zeichen) nur die ASCII-
     *        Ausgabe zulassen (also Häkchen weg bei Hex, Dec und Bin). So behält man den Überblick.
     *
     *
     *  Wer (wie ich gerade) in die AVR-Programmierung einsteigt und an der Kommunikation zwischen AVR
     *  und PC sowie am Beschreiben des EEPROM interessiert ist, wird vielleicht (hoffentlich) mithilfe
     *  dieses Programms weiterkommen.
     *
     *
     *  Matthias Marschhausen (2007-07-20)
     *
     */
    #ifndef F_CPU
    	#define F_CPU 4000000
    #endif
    #ifndef UART_BAUD_RATE
    	#define UART_BAUD_RATE 9600
    #endif
    //
    #define IUSART_INP_MAX 20   //maximale Anzahl an Zeichen, die vom AVR empfangen werden darf
    #define EEPROM_MAX_BYTE 127 //letztes Byte des EEPROM (beim ATTiny2313 128 Byte großer Speicher)
    //
    extern unsigned char __heap_start;
    //
    #include <avr/io.h>
    #include <stdlib.h>
    #include <avr/eeprom.h>
    #include <util/delay.h>
    /*
    #include <avr/interrupt.h>
    #include <stdio.h>
    #include <stdint.h>
    */
    //
    //PROTOTYPEN
    //----------
              void USART_init(unsigned int baud);
              void USART_transmit(unsigned char cData);
              void USART_transmit_str(char *cStr);
              void long_delay_ms( unsigned long ms );
              void clearArray(char cArray[],int iMax);
              void __attribute__ ((naked, section (".init8"))) __init8_mem (void);
           uint8_t eeprom_get_next_byte(uint8_t iLastByte);
           uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes);
    
    //GLOBALE VARIABLEN
    //-----------------
    uint8_t i = 0;
    uint8_t iEEPROM_lastByte = 0;
    
    
    /***************/
    int main(void) {
    /***************/
                    char cUSART_inp[IUSART_INP_MAX];  //Array für das Zwischenspeichern von über USART empfangenen Zeichen
          unsigned short iUSART_inp = 0;  //Index für das Array cUSART_inp[], entspricht der Anzahl der empfangenen Zeichen
          unsigned short iDot = 0;  //zum ständigen Senden eines Punktes (als Lebenszeichen)
                    char cTmpZahl[5] = {0};  //Temp-Variable für Konvertierungen mit itoa()
           unsigned char cBoolSaved = 0;  //wurden die empfangenen Zeichen im EEPROM gespeichert?
    	//
    	DDRB = 0xff;	//Port B Pins als Ausgänge definieren
    	PORTB = 0xff;
    	//DDRD = 0x00;	//Port D Pins als Eingänge definieren. Wohl nicht notwendig, da die Bits standardmäßig sowieso 0 sind
    	//
    	USART_init(UART_BAUD_RATE);   //USART initialisieren
      //
    	for(;;) {   //Endlosschleife (bei AVR-Programmierung gewollt/nötig)
        if( ++iDot % 1000 == 0 ) { //alle 1000 Durchläufe einen Punkt ans Terminal senden
          USART_transmit('.');  //Punkt senden
          iDot = 0;  //iTest natürlich zurücksetzen, um einen Überlauf zu verhindern
        }
        if( iUSART_inp > IUSART_INP_MAX ) {  //string wäre zu lang, also alles zurücksetzen
          USART_transmit_str(" WARNUNG: >");                     //Warnung/Information ans
          USART_transmit_str(itoa(IUSART_INP_MAX,cTmpZahl,10));  //Terminal senden
          USART_transmit_str(" Zeichen - RESET ");            //
          clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
          iUSART_inp = 0;   //Array-Index zurücksetzen
          long_delay_ms(500);   //kurze Pause...
        }
        //
    		if( UCSRA & (1<<RXC) ) {	//Zeichen werden empfangen und im char-array gespeichert
          //USART_transmit(UDR);  //sendet jedes empfangene Zeichen direkt wieder and Terminal
                                  //(kann zum grundsätzlichen Testen der Kommunikation aktiviert werden)
          cUSART_inp[++iUSART_inp-1] = UDR; //empfangenes Zeichen im Array speichern
          cBoolSaved = 0;   //"gespeichert"-Status auf 0 (false) setzen
    		}
        //
        if( !(PIND & (1<<PIND2)) && iUSART_inp>0) {    //SW0 gedrückt
          for(i=0;i<iUSART_inp;i++) {
            //char-array (cUSART_inp) in EEPROM speichern
            eeprom_write_byte((uint8_t*)(unsigned int)eeprom_get_next_byte(iEEPROM_lastByte),cUSART_inp[i]);
          }
          //
          clearArray(cUSART_inp,IUSART_INP_MAX);  //Array zurücksetzen
          //
          USART_transmit_str(" SAVED ");  //damit man was sieht...
          cBoolSaved = 1;   //"gespeichert"-Status auf 1 (true) setzen
          long_delay_ms(500);   //kurze Pause...
        }
        //
        if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && cBoolSaved) {    //SW1 gedrückt
          //
          for(i=0;i<iUSART_inp;i++) {
            //Inhalt des EEPROM rurück in das char-array schreiben
            cUSART_inp[i] = eeprom_read_byte((uint8_t*)(unsigned int)eeprom_get_first_written_byte(iEEPROM_lastByte,iUSART_inp-1-i));
          }
          //
          USART_transmit_str(cUSART_inp);  //Array ans Terminal senden
          //
          iUSART_inp = 0; //Array-Index zurücksetzen
          cBoolSaved = 0; //"gespeichert"-Status auf 0 (false) setzen
          long_delay_ms(500);   //kurze Pause...
        }
        if( !(PIND & (1<<PIND4)) ) {  //SW2 gedrückt: iUSART_inp (Anzahl eingegebener Zeichen) ausgeben
          USART_transmit_str(" Anzahl Zeichen: ");                //ans Terminal
          USART_transmit_str(itoa(iUSART_inp,cTmpZahl,10));   //senden
          USART_transmit(32);                                 //
          long_delay_ms(500);   //kurze Pause...
        }
        if( !(PIND & (1<<PIND5)) ) {          //SW3 gedrückt: freien Speicher (SRAM) ermitteln
          USART_transmit_str(" FREE MEM: ");                //und ans Terminal senden
          USART_transmit_str(itoa(SP - (uint16_t) &__heap_start,cTmpZahl,10));
          USART_transmit_str(" Bytes ");
          long_delay_ms(500);   //kurze Pause...
        }
    	}
    	//
    	return(0);  //wird nie erreicht, da Endlosschleife...
    }
    
    /***************/
    void long_delay_ms( volatile unsigned long ms ) {
    /***************/
      while( ms-- )
        _delay_ms( 1 );
    }
    
    /***************/
    void USART_init(unsigned int baud) {    //nochmal genau ansehen und schöner formulieren!
    /***************/
    /*
    UBRRH = (unsigned char)(baud>>8);		//Baudrate setzen
    UBRRL = (unsigned char)baud;				//
    //
    UCSRB = (1<<RXEN)|(1<<TXEN);		//Receiver und Transmitter aktivieren
    //
    UCSRC = (1<<USBS)|(3<<UCSZ0);	//8 Datenbits, 2 Stopbits (?)
    */
    UCSRA=0x00;
    //UCSRB=0x08;
    UCSRC=0x86;
    UBRRH=0x00;
    UBRRL=0x17;
    //
    UCSRB = (1<<RXEN)|(1<<TXEN);		//Receiver und Transmitter aktivieren
    }
    
    /***************/
    void USART_transmit(unsigned char cData) {  //sendet ein Zeichen ans Terminal
    /***************/
    	while( !(UCSRA & (1<<UDRE)) ) {
    		;//warten, bis der transmit buffer leer ist, so dass wieder übertragen werden kann
    	}
    	UDR = cData;
    }
    
    /***************/
    void USART_transmit_str(char *cStr) {   //sendet einen String (char-Array) ans Terminal
    /***************/
      while(*cStr) {
        USART_transmit(*cStr++);
      }
    }
    
    /***************/
    void clearArray(char cArray[],int iMax) { //ersetzt alle Werte in einem Array durch binäre Nullen ('\0')
    /***************/
      for(i=0;i<iMax;i++) {
        cArray[i]='\0';
      }
    }
    
    /***************/
    uint8_t eeprom_get_next_byte(uint8_t iLastByte) {
    /***************/
      uint8_t iNextByte = iLastByte + 1;
      //
      if(iNextByte > EEPROM_MAX_BYTE) {
        iNextByte = 0;
      }
      //
      iEEPROM_lastByte = iNextByte;
      return(iNextByte);
    }
    
    /***************/
    uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes) {
    /***************/
      uint8_t iFirstByte;
      if( iLastByte - iWrittenBytes < 0 ) {
        iFirstByte = EEPROM_MAX_BYTE + iLastByte - iWrittenBytes;
      } else {
        iFirstByte = iLastByte - iWrittenBytes;
      }
      return(iFirstByte);
    }
    Danke nochmal an alle, die mir mit ihren Tipps geholfen haben!

    Weitere Anregungen zur speicheroptimierten Programmierung sind erwünscht!

    Gruß Matze

  8. #18
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Zitat Zitat von _matze
    Weitere Anregungen zur speicheroptimierten Programmierung sind erwünscht!

    Gruß Matze

    Code:
    void clearArray(char cArray[],int iMax)
    Schau mal nach memset bzw memclr, diese libc-Funktionen tun das, was du willst

    Code:
    void long_delay_ms( volatile unsigned long ms )
    *räusper* Lokale Variablen brauchen nie volatile zu sein, dazu gehören auch Funktionsargumente. Wenn ein Prog *unbedingt* dieses volatile an der Stelle braucht, hat man recht sicher Hack produziert...

    Code:
    char cUSART_inp[IUSART_INP_MAX];  //Array für das
    Dieses Feld macht man (vermutlich) besser statisch:
    Code:
    static char cUSART_inp[IUSART_INP_MAX];
    Während das lokale Array im Frame der Funktion lebt, wird das statische Feld im Heap angelegt. Da du ansonsten recht wenige lokale Variablen brauchst, kommt die Funktion vielleicht sogar ohne Frame(pointer) aus, was kleineren Code gibt.

    Hinderlich ist hier allerdings
    Code:
    char cTmpZahl[5] = {0};  //Temp-Variable für
    weil dieses Array nicht in einem Register leben kann (Größe ist keine Zweierpotenz). Evtl ist's also besser, auch dieses Array statisch zu machen. Ausserdem übernimmt dann der init-Code die Initialisierung zu 0 (auch dann, wenn das Feld statisch lokal ist!)

    Code:
    USART_transmit_str (" WARNUNG: >");
    Der String " WARNUNG: >" ist im Flash gespeichert und wird zur init-Zeit (vor Aufruf von main) in den RAM kopiert. Das Ding belegt also Platz im Flash *und* im RAM. Den Ramverbrauch kann man reduzieren, indem man den String im Flash lässt. Wie's geht, steht im avr-gcc-Artikel "String im Flash lassen" oder so.

    Code:
    void foo (...);
    Solche Funktionen werden von GCC implementiert und aufgerufen. Falls eine solche Funktion nur 1x gebraucht wird und nur der Übersichtlichkeit und Programmstrukturierung dient, ist es evtl (vor allem bei kleinen Funktionen!) besser, statt dessen
    Code:
    static void foo (...);
    zu deklarieren. Wenn foo nur 1x gebraucht wird, dann wird GCC es inlinen und nicht mehr als eigenständige Funktion umsetzen. Dazu braucht foo nicht als inline gekennzeichnet zu sein! Das spart dann Platz und Zeit -- Ausnahmen gibt es wie immer auch bei diesem Thema.

    So, das recht vorerst mal...
    Disclaimer: none. Sue me.

  9. #19
    Benutzer Stammmitglied
    Registriert seit
    12.06.2007
    Beiträge
    42
    WOW!

    Deine Tipps haben den Code von 1100 Bytes nochmal auf 928 Bytes gedrückt (Data ist von 78 auf 102 gestiegen)! Zur Laufzeit habe ich im SRAM auch ca. 10 Bytes mehr Platz. Und es läuft sogar noch! Nicht schlecht, Herr Specht!

    Und den Ansatz, konstante Strings im Flash zu lassen, habe ich noch gar nicht umgesetzt, werde ich aber machen. Da ist bestimmt auch noch was drin.

    Vielen Dank, SprinterSB! Eine solche Vorgehensweise war mir bislang unter Clipper bzw. VB einfach fremd und weder nötig noch in diesem Umfang möglich.

    Gruß Matze

  10. #20
    Erfahrener Benutzer Robotik Einstein Avatar von SprinterSB
    Registriert seit
    09.06.2005
    Ort
    An der Saar
    Beiträge
    2.802
    Daß .data gewachsen ist, ist darauf zurückzuführen, daß cUSART_inp jetzt zu .data (bzw .bss) gehört und nicht mehr aufm Stapel angelegt wird.

    Mit 102 Bytes bist du fast hackedicht! Du hast nur 128 Bytes in dem Ding! Du solltest also auf jeden Fall Sings im Flash lassen, wo es geht. Ansonsten läüft dir irgendwann der Stapel über, zB wenn ISRs dazu kommen.
    Disclaimer: none. Sue me.

Seite 2 von 3 ErsteErste 123 LetzteLetzte

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •  

fchao-Sinus-Wechselrichter AliExpress