PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Hilfe! Programm zu groß trotz aktivierter Optimierung!



_matze
18.06.2007, 09:32
Hallo!

Ich möchte in meinem Progamm zu Testzwecken die Funktion sprintf() verwenden. Jedoch habe ich auf dem tiny2313 nur 2K Programmspeicher zur Verfügung, und nach Einbau von sprintf() wird diese Grenze gesprengt und der Compiler verweigert den Dienst.

Ich poste einfach mal meinen ganzen Code. Vielleicht hat ja irgend jemand noch eine Idee, wie ich das Programm kleiner bekomme, ohne Funktionalität einbüßen zu müssen. Mir fällt da leider nichts mehr ein.

Ich bin sicher, dass man für die Konvertierung von int nach char[] auch andere Wege als sprintf() gehen könnte, aber es geht mir eher um die Optimierung des restlichen Codes. Schließlich ist das Programm ohne sprintf() schon bei ca. 1800 Bytes. Das ist zuviel und müsste doch irgendwie verkleinert werden können.

Die Compiler-Option zur Optimierung ist natürlich eingestellt ("-0s", habe alle ausprobiert).

Falls sich jemand wirklich die Qual antut, den kompletten Code nach Möglichkeiten zu durchforsten, dann VIELEN, VIELEN DANK!



#ifndef F_CPU
#define F_CPU 4000000
#endif
#ifndef UART_BAUD_RATE
#define UART_BAUD_RATE 9600
#endif
//
#include <avr/io.h>
#include <avr/eeprom.h>
#include <stdio.h>
#include <util/delay.h>
/*
#include <avr/interrupt.h>
#include <stdint.h>
#include <stdbool.h>
*/
//
//PROTOTYPEN
void myWait(unsigned int iX);
unsigned int myPot2(unsigned int iExp);
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 );


/***************/
int main(void) {
/***************/
unsigned short int i = 0;
/*
unsigned short int iBin = 0;
unsigned int iWait = 12000;
unsigned int iIncr = 100;
unsigned int iWaitMax = 8000;
unsigned int iWaitMin = 2000;
bool bWaitGoesDown = true;
bool bDirIsLeft = true;
unsigned int iEEPROM_index = 0;
*/
char *cUSART_inp = NULL;
unsigned short int iUSART_inp = 0;
unsigned long iTest=0;
unsigned char cZahl[3] = {0};
//
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);
//
for(;;) {
//
if( iTest++ % 500 == 0 ) {
USART_transmit('.');
}
//
if( UCSRA & (1<<RXC) ) { //Zeichen werden empfangen und im char-array gespeichert
cUSART_inp = (char *)realloc( cUSART_inp , (++iUSART_inp) * sizeof(char) );
cUSART_inp[iUSART_inp-1] = UDR;
}
//
if( !(PIND & (1<<PIND2)) && iUSART_inp>0) { //SW0 gedrückt
for(i=0;i<iUSART_inp;i++) {
eeprom_write_byte(i,cUSART_inp[i]); //char-array (cUSART_inp) in EEPROM speichern
}
//
free(cUSART_inp); //char-array löschen, aber nicht den Index iUSART_inp, da dieser
cUSART_inp = NULL; //beim Zurücklesen gebraucht wird.
//
USART_transmit_str(" SAVED ");
long_delay_ms(200); //wartet 1 Sek, nicht 200 MSek... warum auch immer...
}
//
if( !(PIND & (1<<PIND3)) && iUSART_inp>0) { //SW1 gedrückt
cUSART_inp = (char *)realloc( cUSART_inp , (iUSART_inp) * sizeof(char) );
//
sprintf(cZahl,"%i",iUSART_inp);
USART_transmit_str("iUSART_inp: " + *cZahl);
//
for(i=0;i<iUSART_inp;i++) {
cUSART_inp[i] = eeprom_read_byte(i); //Inhalt des EEPROMs rurück in das char-array schreiben
}
//
USART_transmit_str(cUSART_inp);
//
free(cUSART_inp); //string wurde auf Terminal ausgegeben, daher char-array
cUSART_inp = NULL; //und Index zurücksetzen
iUSART_inp = 0;
}
//
//
// Hier folgen ein Lauflicht und diverse Möglichkeiten, dieses zu beeinflussen.
// Zum Testen von USART- und EEPROM-Funktionen ist dieser Teil auskommentiert.
//
/*
// SW0:
while( !(PIND & (1 << PIND2)) ) {
;// Lauflicht anhalten, wenn SW0 gedrückt ist
}
//
// SW1:
if( !(PIND & (1 << PIND3)) ) { //Laufrichtung ändern, wenn SW1 gedrückt wird
if( bDirIsLeft ) {
bDirIsLeft = false;
iBin = 8;
}else {
bDirIsLeft = true;
iBin = 0;
}
myWait(1000);
}
//
// SW2:
while( !(PIND & (1 << PIND4)) ) { //perfektes Disco-Licht auf SW2 !
PORTB = 0x00;
myWait(2500);
PORTB = 0xff;
myWait(2500);
}
//
// SW3:
if( !(PIND & (1 << PIND5)) ) { //auf SW3 soll per USART ans Terminal gesendet werden
//
USART_transmit('U');
USART_transmit('S');
USART_transmit('A');
USART_transmit('R');
USART_transmit('T');
USART_transmit(' ');
USART_transmit('T');
USART_transmit('E');
USART_transmit('S');
USART_transmit('T');
}
//
if( bDirIsLeft ) { //hier wird die LED verschoben, abhängig von der Laufrichtung
PORTB = 0xff - myPot2(iBin++);
if( iBin==8 ) {
iBin=0;
}
}else {
PORTB = 0xff - myPot2(--iBin);
if( iBin==0 ) {
iBin=8;
}
}
//
myWait(iWait);
//
if( bWaitGoesDown ) {
if( iWait < iWaitMin+500 ) {
iIncr = 20;
}
if( iWait > iWaitMin ) {
iWait -= iIncr;
}else {
bWaitGoesDown=false;
}
}else {
if( iWait > iWaitMin ) {
iIncr = 100;
}
if( iWait < iWaitMax ) {
iWait += iIncr;
}else {
bWaitGoesDown=true;
}
}
*/
}
//
return 0;
}

/***************/
unsigned int myPot2(unsigned int iExp) { //potenziert die Basis 2 mit dem Exponenten iExp
/***************/
unsigned short int i,iErg;
iErg = 1;
for(i=0;i<iExp;i++) {
iErg = iErg * 2;
}
return iErg;

}

/***************/
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) {
/***************/
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) {
/***************/
while(*cStr) {
USART_transmit(*cStr++);
}
}

zumgwadrad
18.06.2007, 09:35
Hi,
probiers mal mit itoa anstatt sprintf!
Musst halt mal nach itoa googlen da steht wie dus anwendest!

_matze
18.06.2007, 10:03
Ja, mit itoa() bin ich so gerade im grünen Bereich (2032 von 2048 Bytes belegt).

Interessanter wäre für mich, wie ich grundsätzlich meinen Code optimieren kann. Ein kleines Programm, dass lediglich eine Eingabe in einem Array und im EEPROM speichert und per Tastendruck wieder ausgibt, sollte doch mit deutlich weniger als 2K zu realisieren sein, oder?

Das dynamische Array habe ich übrigens enigebaut, da das direkte Schreiben (Zeichen für Zeichen, direkt nach der Eingabe) ins EEPROM nicht wirklich funktioniert hat (es wurden Zeichen ausgelassen...). Nur, damit nicht jemand vorschlägt, eben dieses wegzulassen.

zumgwadrad
18.06.2007, 12:33
Hi,
Speicheroptimiert Programmieren ist net so einfach, aber wo sich immer was sparen lässt ist bei variablendeklarationen!
Beispiel: Brauch ich wirklich float für ne Variable die nach True und false abfragt?
Wenn du am PC Programmierst kann dir des Wurst sein da ist speicherplatz kein Problem!!
Aber hier musst du etwas aufpassen!
Viel Erfolg noch

_matze
18.06.2007, 12:38
Vielen Dank für den Tipp! Ich bin zwar der Meinung, bei den Variablen darauf geachtet zu haben, aber ich lasse mich gerne belehren und werde mich mal eingehend mit dem Thema beschäftigen.

Gibt's vielleicht einen guten Link zu der Thematik?

Gruß Matze

zumgwadrad
18.06.2007, 12:54
Hi
,
unter,https://www.roboternetz.de/wissen/index.php/C-Tutorial#Datentypen stehen bei Datentypen die Variablen mit Speichergröße und zulässigem Wertebereich!
Sieht auf den Ersten Blick klein aus wenn man bei ner Variable ein oder 2 Byte spart, aber die spart man pro Aufruf und das summiert sich dann schon!

_matze
18.06.2007, 13:31
Ok, werd ich mir direkt mal ansehen, vielleicht sind ja noch ein paar Bytes drin.

VIELEN DANK@zumqwadrad !!

Jakob L.
18.06.2007, 14:20
Hallo,

vielleicht kannst du noch etwas einsparen, indem man auf die dynamische Speicherverwaltung mit realloc und free verzichtet und statt dessen ein Array mit statischer Grösse verwendet. Da du im gesamten Programm sowieso nur ein Array brauchst, kannst du dafür einfach den gesamten freien Speicher verwenden.

Gruss
Jakob

_matze
18.06.2007, 14:26
Du hast natürlich Recht. Ich habe gedacht, dass ich nicht weiß, wieviele Zeichen der User eingibt, also muss es ein dynamisches Array sein. Bei einem Maximum von 128 Zeichen (EEPROM beim 2313 ist nicht größer) kann ich auch ein passendes statisches Array verwenden. Danke für den tipp, Jakob, ich werd's gleich mal umbauen!

Gruß Matze

ogni42
18.06.2007, 15:59
iTest brauch nicht long sein. unsinged int reicht (muss ja nur bis 501 zählen können)

realloc ersetzen wurde ja schon gesagt

Deklariere die Variablen im kleinstmöglichen scope. Dann kann der Compiler besser optimieren.

_matze
19.06.2007, 07:18
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.

_matze
19.06.2007, 07:23
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?

_matze
19.06.2007, 07:45
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.

Jakob L.
19.06.2007, 08:49
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

SprinterSB
20.06.2007, 07:56
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/index.php/Avr-gcc/Interna#Dynamische_Speicherallokierung
https://www.roboternetz.de/wissen/index.php/FIFO_mit_avr-gcc
https://www.roboternetz.de/wissen/index.php/Avr-gcc#Optimierungen.2C_Tipps_.26_Tricks

_matze
20.06.2007, 08:37
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/index.php/Speicherverbrauch_bestimmen_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

_matze
20.06.2007, 14:16
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:



/* 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

SprinterSB
21.06.2007, 16:31
Weitere Anregungen zur speicheroptimierten Programmierung sind erwünscht!

Gruß Matze




void clearArray(char cArray[],int iMax)

Schau mal nach memset bzw memclr, diese libc-Funktionen tun das, was du willst :-)



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... ;-)



char cUSART_inp[IUSART_INP_MAX]; //Array für das

Dieses Feld macht man (vermutlich) besser statisch:


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


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!)



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.



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


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...

_matze
22.06.2007, 07:56
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

SprinterSB
22.06.2007, 14:29
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.

_matze
22.06.2007, 14:53
Ich weiß, aber zumindest bei diesem Testprogramm ist das kein Problem. ISRs gibt es nicht, und ich kann mir per Taster den restlichen Speicher im SRAM anzeigen lassen (momentan noch 17 Bytes frei). Nächste Woche gehts an richtige Projekt, mal sehen wie gut ich da (plötzlich ohne Testboard...) voran komme.

In jedem Fall schönen Dank, SprinterSB, und schönes Wochenende!

_matze
26.06.2007, 07:13
So, hier nochmal der aktuelle Code. Die Delay-Funktionen habe ich entfernt und durch eine eigene Funktion zum Tastenentprellen ersetzt. Außerdem gibt es noch weitere kleine Verbesserungen (dank der Hilfe von SprinterSB).

Perfekt ist es noch nicht, aber ich muss mich jetzt meiner produktiven Aufgabe widmen und kann leider nicht weiter am Testprogramm werkeln.

Die funktion long_delay_ms() wird nicht mehr benutzt. Ich habe sie aber drinnengelassen, da ich finde, dass eine solche Funktion für Anfänger zum Testen doch sehr nützlich ist. Bitte nicht schlagen! Ich weiß ja, dass delays keinen guten Ruf haben, aber wir reden hier von den ersten Gehversuchen in der AVR-Welt...


/* 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>
*/
//
typedef struct {
unsigned char D2:1;
unsigned char D3:1;
unsigned char D4:1;
unsigned char D5:1;
}swBit;
//
//
//FUNKTIONS-PROTOTYPEN
//--------------------
static void USART_init(unsigned int baud);
void USART_transmit(unsigned char cData);
void USART_transmit_str(char *cStr);
void USART_clear(void);
void clearArray(char cArray[],int iMax);
void __attribute__ ((naked, section (".init8"))) __init8_mem (void);
static uint8_t eeprom_get_next_byte(uint8_t iLastByte);
static uint8_t eeprom_get_first_written_byte(uint8_t iLastByte,uint8_t iWrittenBytes);
void myDebounce(swBit *bitfeld,uint8_t iPin);
void long_delay_ms( unsigned long ms );
//
//
//GLOBALE VARIABLEN
//-----------------
uint8_t i = 0;
uint8_t iEEPROM_lastByte = 0;
swBit swPressed;


/***************/
int main(void) {
/***************/
static 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)
static char cTmpZahl[4] = {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
//
clearArray(cUSART_inp,IUSART_INP_MAX);
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 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
USART_clear();
}
//
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 && !swPressed.D2 && !cBoolSaved) { //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
}
//
myDebounce(&swPressed,PIND2);
//
if( !(PIND & (1<<PIND3)) && iUSART_inp>0 && !swPressed.D3 && 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));
cUSART_inp[i] = eeprom_read_byte((void*)eeprom_get_first_written_b yte(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
}
//
myDebounce(&swPressed,PIND3);
//
if( !(PIND & (1<<PIND4)) && !swPressed.D4 ) { //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); //
}
//
myDebounce(&swPressed,PIND4);
//
if( !(PIND & (1<<PIND5)) && !swPressed.D5 ) { //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 ");
}
//
myDebounce(&swPressed,PIND5);
//
}
//
return(0); //wird nie erreicht, da Endlosschleife...
}


/***************/
static 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++);
}
}

/***************/ //Liest solange empfangene Zeichen ein (und verwirft sie), bis
void USART_clear(void) { //keine mehr da sind.
/***************/
uint8_t cTmp;
while( UCSRA & (1<<RXC) ) {
cTmp = UDR;
}
while( UCSRA & (1<<RXC) ) {
cTmp = UDR;
}
return;
}
/***************/
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';
}
}

/***************/
static 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);
}

/***************/
static 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);
}

/***************/ //damit beim Tastendruck nur einmal der
void myDebounce(swBit *bitfeld,uint8_t iPin) { //entsprechende Programmteil ausgeführt wird.
/***************/ //Die übergebene Struktur speichert, welche
switch(iPin) { //Tasten gerade gedrückt sind oder nicht
case(2): //(0=nicht gedrückt, 1=gedrückt).
if( !(PIND & (1<<iPin)) && !(bitfeld->D2) ) { //Wird eine Taste gedrückt, wird ein weiteres
bitfeld->D2 = 1; //Drücken bzw. Gedrückthalten solange ignoriert,
}else { //bis sie wieder losgelassen wurde.
if( PIND & (1<<iPin) ) {
bitfeld->D2 = 0;
}
}
break;
case(3):
if( !(PIND & (1<<iPin)) && !(bitfeld->D3) ) {
bitfeld->D3 = 1;
}else {
if( PIND & (1<<iPin) ) {
bitfeld->D3 = 0;
}
}
break;
case(4):
if( !(PIND & (1<<iPin)) && !(bitfeld->D4) ) {
bitfeld->D4 = 1;
}else {
if( PIND & (1<<iPin) ) {
bitfeld->D4 = 0;
}
}
break;
case(5):
if( !(PIND & (1<<iPin)) && !(bitfeld->D5) ) {
bitfeld->D5 = 1;
}else {
if( PIND & (1<<iPin) ) {
bitfeld->D5 = 0;
}
}
break;
//
}
}


/***************/ //hält für ms Millisekunden die Programmausführung an
void long_delay_ms( unsigned long ms ) { //(nicht mehr im Programm verwendet)
/***************/
while( ms-- )
_delay_ms( 1 );
}


Gruß Matze