Forum: Compiler & IDEs Optimieren? Thermostat mit Tiny26


von Andreas Böhme (Gast)


Angehängte Dateien:

Lesenswert?

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

von Jörg Wunsch (Gast)


Lesenswert?

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

von Andreas Böhme (Gast)


Lesenswert?

Oh, hatte ich vergessen...

Ich verwende die LCDLib:
http://homepage.sunrise.ch/mysunrise/pfleury/lcdlibrary.zip

von Oryx (Gast)


Lesenswert?

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

von Andreas Böhme (Gast)


Angehängte Dateien:

Lesenswert?

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

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

Was steht in $(OPT)?

von Oryx (Gast)


Lesenswert?

Hi,
compilier mal mit -Os

entweder
OPT = s
CFLAGS = -g -O$(OPT) \

oder
CFLAGS = -g -Os \


Oryx

von Jörg Wunsch (Gast)


Lesenswert?

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.

von Andreas Böhme (Gast)


Lesenswert?

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!

von Jörg Wunsch (Gast)


Lesenswert?

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

von Andreas Böhme (Gast)


Angehängte Dateien:

Lesenswert?

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

von Peter D. (peda)


Lesenswert?

@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

von Andreas Böhme (Gast)


Lesenswert?

Bei mir compiliert das Programm ohne Errors..

von Jörg Wunsch (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.