Hallo zusammen, ich bin gerade dabei, mir einen kleinen digitalen Thermostat zu bauen und habe dazu einen Tiny26 verwendet. Da ich nun so viel als möglich an Funktionalität reinpacken möchte, muss ich den Code in Sachen Grösse optimieren (Nein, Assembler will ich jetzt nicht gerade benutzen). Im Anhang habe ich das C-Programm, vielleicht kann jemand mal drüber schauen und evt. Optimierungshinweise (Grösse) geben. Ich bin hier am Ende angelagt. Danke schon mal im Vorraus..
avr-gcc -c -mmcu=attiny26 -I. -g -Os -Wall -Wstrict-prototypes -std=gnu99 thermo_little.c -o thermo_little.o thermo_little.c:22:17: lcd.h: No such file or directory thermo_little.c: In function `printFormat': thermo_little.c:51: warning: implicit declaration of function `lcd_puts_P' thermo_little.c:61: warning: implicit declaration of function `lcd_putc' thermo_little.c: At top level: thermo_little.c:96: warning: function declaration isn't a prototype thermo_little.c: In function `menue': thermo_little.c:98: warning: implicit declaration of function `lcd_clrscr' thermo_little.c:99: warning: implicit declaration of function `lcd_puts' thermo_little.c:104: warning: implicit declaration of function `lcd_gotoxy' thermo_little.c: At top level: thermo_little.c:139: warning: function declaration isn't a prototype thermo_little.c: In function `init': thermo_little.c:141: warning: implicit declaration of function `lcd_init' thermo_little.c:141: error: `LCD_DISP_ON_BLINK' undeclared (first use in this function) thermo_little.c:141: error: (Each undeclared identifier is reported only once thermo_little.c:141: error: for each function it appears in.) *** Error code 1
Oh, hatte ich vergessen... Ich verwende die LCDLib: http://homepage.sunrise.ch/mysunrise/pfleury/lcdlibrary.zip
Hi, an Deinem Quellcode konnte ich auf die schnelle nicht allzuviel böses finden. Kannst Du Deine Compilerparameter und evtl. eine MAP-Datei posten? Oryx
Hallo, ich habe hier die Compiler-Flags und die MAP Datei (allerdings von einer nochmals überarbeiteten Version). CFLAGS = -g -O$(OPT) \ -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums \ -Wall -Wstrict-prototypes \ -Wa,-adhlns=$(<:.c=.lst) \ $(patsubst %,-I%,$(EXTRAINCDIRS))
Hi, compilier mal mit -Os entweder OPT = s CFLAGS = -g -O$(OPT) \ oder CFLAGS = -g -Os \ Oryx
OK, kurze Analyse zeigt, daß die Funktionen menue() printFormat() printStatus() convertTemp() die ,,Renner'' sind (zumindest von Deinen eigenen -- ich gehe davon aus, daß Peters LCD-Funktionen wohl bereits mindestens nahe am Optimum dran sind ;-). D. h., es lohnt sich am ehesten, in diesen zu optimieren. In menue() fällt auf, daß zwei fast gleiche Strukturen hintereinander stehen. Da wir schon wissen, daß die Funktion insgesamt recht groß ist, sollte sich die Optimierung in eine Schleife lohnen: void menue() { unsigned short timeout; unsigned char i; for (i = 0; i < 2; i++) { timeout = 0; lcd_clrscr(); lcd_puts(min_buffer); lcd_puts_P(" "); lcd_puts(set_buffer); while (timeout < 50) { timeout ++; lcd_gotoxy(0,1); lcd_puts(min_buffer); lcd_puts_P("="); printFormat(min); if ((PINA & 0x80) == 0x80) { min += 50; timeout = 0; } if ((PINA & 0x80) == 0x40) { min -= 50;; timeout = 0; } delay(32000); } if (i == 0) { lcd_clrscr(); lcd_puts(max_buffer); lcd_puts_P(" "); lcd_puts(set_buffer); } } } Bringt immerhin ca. 80 Bytes. Die Zusammenfassung von printFormat() in einer Schleife optimiert ca. 30 Bytes: void printFormat(int i) { unsigned char bcd; unsigned char j; static unsigned int n[] = {10000, 1000, 100, 10, 1}; if (i < 0) { lcd_puts_P("-"); i = -i; } else lcd_puts_P(" "); for (j = 0; j < 5; j++) { bcd = '0'; while (i > n[j]) { i -= n[j]; bcd++; } lcd_putc(bcd); } } Etwas trickreicher wird's jetzt, wenn man convertTemp() optimieren will. Die Funktion ist ja wirklich reizend kurz :), und die Rechnung im long-Bereich (die bekanntermaßen aufwendig ist) läßt sich ja offenbar nicht vermeiden -- die Benutzung eines skalierten Integers statt einer Gleitkommazahl hast Du ja schon völlig zu Recht angewandt. Aber: man kann natürlich am Skalierungsfaktor feilen. Es ist zu erwarten, daß der Wegfall der aufwendigen Division einiges bringen könnte. Da Du nur durch eine Konstante dividierst, kann man die Division ja in jedem Falle durch eine Multiplikation mit dem Reziproken ersetzen. Jetzt muß man eigentlich nur noch Deine willkürliche Skalierung von 200 * 10000 (die zwar für den Menschen einleuchtend ist, für die Maschine aber eher unglücklich) durch etwas Geschickteres ersetzen. ,,Geschickter'' ist eine Zweierpotenz, weil dabei die Rückskalierung (unvermeidlich eine Division) zu einer Bitverschiebung degradiert, so daß keine teure Divisionsroutine mehr notwendig ist. Wenn mich nicht alles täuscht, sollte der folgende Code zwar nicht äquivalent, aber im zu erwartenden Genauigkeitsbereich zumindest gleichwertig sein: int convertTemp(unsigned int adc_val) { // ADC 0 Grad = 589 long result; result = adc_val; #define SCALE (long)(1024.0 * 200 / 7.7125) result = (adc_val * SCALE) - (589 * SCALE); result /= 1024; return (int) result; } Durch die Benutzung des Präprozessors zur Berechung des konstanten Ausdrucks wird es sogar etwas übersichtlicher (weniger ,,magische'' Zahlen), und voilá: wir sparen über 100 Bytes ein! Schließlich noch: inp()/outp() sind `deprecated', read_adc() kann man einfacher so schreiben: unsigned int read_adc(unsigned char adc_number) { unsigned char adlow, adhigh; unsigned int adsum; ADMUX = adc_number | 0x40; // Kanal selektieren und VREF=Ext. ADCSR = 0xd7; while ((ADCSR & 0x10) != 0x10); // warten, bis ADC fertig return ADCW; } Spart auch noch 10 Bytes. Summa summarum ist (-Os als Optimierung) die Größe von 1416 Bytes ROM auf 1168 Bytes geschrumpft, also immerhin 17 % Einsparung. Was mir beim Durchrechnen Deiner Skaliererei aufgefallen ist: Du wirst wohl meiner Meinung nach wenig Freude damit haben, da ein Digit im ADC-Ergebnis bereits einen recht erheblichen Temperatursprung bedeutet, d. h. der Wertebereich des ADC wird nur wenig ausgereizt. Ich vermute zumindest, daß Dein Thermostat eher nicht im Bereich von 10000 K arbeiten soll. :-) Ich würde mir an Deiner Stelle die Benutzung des differentiellen ADC mit Vorverstärker überlegen.
Guten Morgen :) @Jörg: Vielen Dank für die ausführliche Optimierung mit Erklärungen. Hätte nicht gedacht, dass sich jemand soviel Arbeit macht und dass noch soviel Potential drin steckt. Auf jedenfalls hab ich jetzt auch einiges dazu gelernt. In der Funktion menue ist Dir allerdings ein Fehler passiert: Hier wird einmal min und einmal max geändert, Du änderst leider bei jedem Schleifendurchlauf min. Habe aber folgende Idee, vorallem da noch weitere Menüpunkte hinzu kommen. Ich könnte doch die ganzen "Settings" in ein Array packen, dann ist es ja einfach möglich bei jedem Schleifendurchlauf den Index einmal für min und einmal für max zu verwenden. Ich werde das heute alles einmal einbauen und testen, dann poste ich nochmals das Listing heute Abend. Die Skalierung ist leider wirklich relativ grob, aber ich glaube für diese Anwendung reicht dies aus. Mir geht es "nur" drum, mein Ätzbad und evt. mal noch den Kühlschrank zu temperieren :) Für genauere Messungen hatte ich mit mal ein Board mit einem 4433 gebaut und mit analoger Vorschaltung den Temperaturbereich von -30 - 130 Grad auf 0-5V aufgeteilt. Das ist mir aber für nen simplen Thermostat zu aufwendig. Also nochmals danke, das war ein grosser Beitrag zum Verständnis!
> Hätte nicht gedacht, dass sich jemand soviel Arbeit macht ... Hat mich einfach auch selbst interessiert, sonst hätte ich das sicher nicht in der Ausführlichkeit gemacht. ;-) > und dass noch soviel Potential drin steckt. Ich auch nicht. :-) Bis ich die Division im long-Bereich entdeckt habe... > In der Funktion menue ist Dir allerdings ein Fehler passiert: Hier > wird einmal min und einmal max geändert, Du änderst leider bei jedem > Schleifendurchlauf min. Hmm, gut, hatte mir noch überlegt, ob ich beide Blöcke wirklich zeilenweise vergleiche, war aber dann zu faul dazu. ;-) Das sollte ja auch eine Anregung sein, kein garantiert funktionierender Code. Das Prinzip hast Du ja verstanden: wenn man Code sparen will, sollte man einfach in Schleifen packen, was nur geht. (Wenn man Ausführungszeit sparen will, genau andersrum, auch `loop unroll' genannt.) Ein Array für die Konfiguration in der Menü-Schleife ist sicher OK. So ähnlich hab ich ja auch die Schleife in der Konvertierungsfunktion gebildet. > Die Skalierung ist leider wirklich relativ grob, aber ich glaube für > diese Anwendung reicht dies aus. Na wie geschrieben, mit dem ATtiny26 würde ich an Deiner Stelle den Differenzmodus des ADC mit eingebauter Verstärkung nochmal in Erwägung ziehen.
So, ich denke der Thermostat ist nun fast fertig :) Ich habe noch die Spannungsüberwachung dazu gepackt und die Einstellungen werden nun im EEPROM gesichert. Zur Statistik werden auch die Bootvorgänge gezählt.. Bin nun bei ca 1720 Bytes Code angelangt, vielleicht reicht der Rest ja noch, um verschiedene Profile im EEPROM zu verwalten. Auf jeden Fall wäre ohne die Optimierung von Jörg nicht mehr soviel drin gewesen.. Gruß Andreas
@Andreas, gibts dieses Programm auch ohne Warnungen und Errors ? Ich würde nämlich auch gerne mal sehen, wo die 1720 Bytes verbraucht werden. Peter
Aber mit 12 Warnungen... --- thermo_little.c~ Sat Mar 20 21:26:56 2004 +++ thermo_little.c Sat Mar 20 21:32:10 2004 @@ -32,12 +32,14 @@ char ctrl_buffer[5] = {'C', 'T','R', 'L', 0x00}; char equals_buffer[2] = {'=', 0x00}; +void printFormat(int i, unsigned char dp, unsigned char decimals); + void delay(unsigned int us) { while ( us ) us--; } -void menue() { +void menue(void) { unsigned short timeout; unsigned char i; PORTA = 0x00; @@ -77,17 +79,17 @@ } -void require_setting() { +void require_setting(void) { lcd_clrscr(); lcd_puts_P("Bitte einstellen !"); while (((PINA & 0x80) != 0x80) && ((PINA & 0x40) != 0x40)); } -void write_settings() { - eeprom_write_block(&settings,1,6); +void write_settings(void) { + eeprom_write_block(&settings,(void *)1,6); } -void intro() { +void intro(void) { lcd_clrscr(); lcd_puts_P("Thermostat v1.1"); lcd_gotoxy(0,1); @@ -96,7 +98,7 @@ delay(32000); } -void read_settings() { +void read_settings(void) { bootcount= eeprom_read_byte(0x00); if ((bootcount == 0xFF) || (bootcount == 0x00)) { bootcount = 0x00; @@ -105,7 +107,7 @@ write_settings(); } else - eeprom_read_block(&settings, 1, 6); + eeprom_read_block(&settings, (void *)1, 6); bootcount++; eeprom_write_byte(0x00, bootcount); } @@ -155,7 +157,7 @@ return result; } -void init() { +void init(void) { DDRA = 0x10; lcd_init(LCD_DISP_ON_BLINK); }
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.