Forum: Mikrocontroller und Digitale Elektronik MPLAB C18 Compiler Codeoptimierung??


von Roman (Gast)


Angehängte Dateien:

Lesenswert?

Guten Abend

Ich programmiere gerade ein Programm zur Frequenzmessung auf einem 
PIC18F1220. In meinem Code brauche ich relativ viele Variablen (auch 
unsigned long) und Zeichenketten.
Ich arbeite mit der Student Version des C18 Compilers.
Leider habe ich nun keinen Speicherplatz mehr obwohl ich erst 3/4 meines 
Programms fertig habe.

Nun meine Frage:
Kann mir jemand ein paar Tipps geben (evt. auch MPLAB Einstellungen) um 
meinen Code zu optimieren?
Diese Zeichenketten brauchen einfach abartig viel Platz -.-

Bin für jeden Rat dankbar. Der Code findet ihr im Anhang.

p.s Ich weiss der Code ist noch eine Baustelle aber im grossen und 
ganzen sollte man schnell einen Überblick bekommen.

Vielen Dank schon mal!! :)

von (prx) A. K. (prx)


Lesenswert?

ROM voll oder RAM? Wenns das RAM ist, dann leg die Strings ins ROM.

Ausserdem braucht
  char*init1 = "Frequenzmesser"; - 2 Bytes RAM für Pointer extra
mehr Platz als
  char init1[] = "Frequenzmesser";
(ggf. ROM Kennzeichnung ergänzen)

von Kein Name (Gast)


Lesenswert?

Ist wirklich der Speicher zu klein, oder nur ein Array größer als 256 
Bytes? Bin da mal auf eine irreführende Fehlermeldung reingefallen.

von Roman (Gast)


Lesenswert?

Kein Name schrieb:
> Ist wirklich der Speicher zu klein, oder nur ein Array größer als 256
>
> Bytes? Bin da mal auf eine irreführende Fehlermeldung reingefallen

Nein es ist schon der Speicher... Jedenfalls Laut .map File.
A. K.: Auch mit dieser Variante geht es leider nicht.

Habe jetzt schon alles mögliche ausprobiert. Im Moment kommentiere ich 
einfach alles aus was schon läuft. So kann ich immerhin weitermachen.
Aber wäre schade wenn später nicht alles zusammen laufen würde.

p.s. Ein anderer PIC kommt leider nicht in Frage.

Vielen Dank

von Frank K. (fchk)


Lesenswert?

Poste mal das komplette Projekt.

fchk

von Holger W. (holgerw)


Lesenswert?

schalte doch mal unter view die Memory Usage Gauge an.
Da kann man sehr schön beobachten wie die Speicherauslastung ist.
Vorausgesetzt das Projekt ist ordentlich angelegt.
Holger

von Roman (Gast)


Lesenswert?

Ich habe mir nochmals alles genau angeschaut und der Speicher ist 
tatsächlich voll...
Schon komisch bei einem PIC mit solch einer Peripherie. So kann man ja 
nicht mal alles verwenden.
Naja zum Glück gibt es noch den 1330er. Der ist Pinkompatibel und hat 
doppelt so viel Speicher.

von MiWan (Gast)


Lesenswert?

Roman schrieb:
> Ich arbeite mit der Student Version des C18 Compilers.

Du könntest auch die "Standard-Eval Version" Version des Compilers 
nehmen, da sind alle Optimierungen aktiviert, zumindest 60 Tage lang. 
Und selbst danach findet sich auch ein Weg den Zeitraum zu verlängern 
(BackUp, Date, VM, Patch)...

von Christian K. (Firma: Atelier Klippel) (mamalala)


Lesenswert?

Wie andere schon gefragt haben: Welcher Speicher ist voll? Flash oder 
das RAM? Zu vermuten ist das es sich um RAM handelt, denn ein:

char mystring[] = "bla";

landet in beidem. Einmal im Flash, da der Inhalt "bla" für die 
Intialisierung ja irgendwo stehen muss, und dann einmal im RAM, weil er 
eben beim Init von der clib dorthin kopiert wird. Je nachdem wie deine 
Strings angeordnet sind kommt es dann auch noch zu weitrem 
Flashspeicher-Verbrauch, wenn die Strings nich als ein Block umkopiert 
werden können, sondern getrennter Code zum umkopieren der einzelnen 
Strings nötig ist.

Am besten ist es die Strings gleich im Flash zu haben:

const rom char rom mystring[] = "bla";

erzeugt den String im Flash, und nur dort, und ohne "Overhead". 
Natürlich muss dann bei der Ausgabe ebenfalls beachtet werden das der 
String im Flash liegt. Eine Ausgabefunktion für Zeichenketten aus dem 
ROM auf ein LCD sieht dann z.B. so aus:
1
void lcd_write_string_rom(unsigned char x, unsigned char y, static const rom char *ldata)
2
{
3
  lcd_set_cursor(x, y);
4
5
  while(*ldata)
6
  {
7
    lcd_write_data(*ldata++);
8
  }
9
}

Die lcd_write_data wiedrum arbeitet dann mit normalen unsigned char:
1
void lcd_write_data(unsigned char dbyte)
2
{
3
  LCD_RW        = LCD_RW_WRITE;
4
  LCD_DATA_DIR  = PORT_OUT;
5
  LCD_RS        = LCD_RS_DATA;
6
  LCD_DATA_OUT  = dbyte;
7
8
  LCD_E         = 1;
9
10
  pause(4);
11
12
  LCD_E         = 0;
13
14
  pause(4);
15
}

Grüße,

Chris

von Michael G. (let)


Angehängte Dateien:

Lesenswert?

> Wie andere schon gefragt haben: Welcher Speicher ist voll? Flash oder
> das RAM? Zu vermuten ist das es sich um RAM handelt, denn ein:
>
> char mystring[] = "bla";

Macht er ja nicht. Er schreibt:
1
char*init1     = "Frequenzmesser";
Der Typ von init1 ist zwar falsch aber solange er den Zeiger nicht 
dereferenziert ist alles in Ordnung. Und er dereferenzieren tut er ihn 
nicht. Das macht sein write_string(), wo dann 'rom char*' steht.

Ich habe den Code mal durch den C18 gejagt und laut map-file ist es 
erwartungsgemäß der Flashspeicher der fast voll ist. Die Variablen 
belegen ja nur gut 30 Bytes.

Mir ist das allerdings alles etwas unheimlich. Aus dem Bauch heraus 
würde ich annehmen dass der AVR-GCC für den gleichen Code nur die Hälfte 
an Flash brauchen würde. Auffällig ist die Funktion 'Write_Zahl()' die 
mit 1.2kB erstaunlich groß ist.
Würde das gerne auf einen AVR portieren und bei der Gelegenheit auch auf 
einen M3 aber ich habe gerade etwas zu routen das bis Montag fertig sein 
muss.

von Christian K. (Firma: Atelier Klippel) (mamalala)


Lesenswert?

Ah, OK, so genau hatte ich mir das noch nicht angeschaut.

Jetzt habe ich dann mal Write_Zahl angesehen. Kein Wunder das die so 
groß wird. 4 int plus 2 char als Parameter, das erzeugt schon mal 
fleissig viel Code für den Zugriff auf den Stack. Dazu dann noch die 
ganzen Deklarationen plus Initilaisierungen am Anfang der Routine. Sehr 
schlecht das ganze. Auch die Berechnung/Zerlegung der Zahl in einzelne 
Ziffern, nicht gut. Da wird sehr viel gemacht was so einfach unnötig 
ist, da gibt es erheblichen Optimierungs-Spielraum.

So kann man die Berechnung einer einzelnen Ziffer der Zahl erheblich 
optimieren. Zuerstmal definiert man sich zwei int oder long int, je nach 
gewünschtem Zahlenbereich, plus einen char. Dann baut man sich eine 
divmod Funktion, die eine Art Modulo plus Restwert macht. Hier mal ein 
Beispiel aus dem Kopf für positive Zahlen bis max. einschliesslich 
1000000:
1
unsigned long value;
2
unsigned long divisor;
3
unsigned char number;
4
5
void divmod(void)
6
{
7
    number = 0;
8
    while(value >= divisor)
9
    {
10
        number++;
11
        value -= divisor;
12
    }
13
}
14
15
void print_value(unsigned long value_arg)
16
{
17
    value = value_arg;
18
19
    // Ausgabe Million
20
    divisor = 1000000;
21
    divmod();
22
    put_char_lcd('0' + number);
23
24
    // Ausgabe Hunderttausender
25
    divisor = 100000;
26
    divmod();
27
    put_char_lcd('0' + number);
28
29
    // Ausgabe Zehntausender
30
    divisor = 10000;
31
    divmod();
32
    put_char_lcd('0' + number);
33
34
    // Ausgabe Tausender
35
    divisor = 1000;
36
    divmod();
37
    put_char_lcd('0' + number);
38
39
    // Ausgabe Hunderter
40
    divisor = 100;
41
    divmod();
42
    put_char_lcd('0' + number);
43
44
    // Ausgabe Zehner
45
    divisor = 10;
46
    divmod();
47
    put_char_lcd('0' + number);
48
49
    // Ausgabe Einer, keine weitere divmod nötig, da "value" bereits < 10
50
    put_char_lcd('0' + value);
51
}

Wenn man auch negative Zahlen will, hat man als Argument dann halt eine 
signed long, signed int, oder was auch immer. Als erstes packt man dann 
anstelle der einfahcen Zuweisung "value = value_arg;" folgendes in die 
Funktion:
1
    if(value_arg < 0)
2
    {
3
        value = -value_arg;
4
        put_char_lcd('-');
5
    }
6
    else
7
    {
8
        value = value_arg;
9
        put_char_lcd('+'); // oder was auch immer man bei pos. Zahlen will
10
    }

Für der Nummernausgabe der einzelnen Stellen kann man anstelle einer 
einfache put_char_lcd() auch eine andere Funktion bauen die dann z.B. 
führende Nullen normal ausgibt, oder als Leerzeichen, oder unterdrückt. 
Auch dort sollte man das ganze sehr einfach halten, und lediglich die 
Ziffer als Argument übergeben. Weitere Parameter wie z.B. ausgabe der 
führenden Nullen, Dezimalpunkt, etc. sollte man dur eine einzelne 
Funktion einstellen, die wiederum das ganze auch über "globale" 
Variablen macht. So spart man sich diese elenden Rattenschwänze an 
Parametern. Wenn man dann eine Zahl ausgeben will muss man einfach eine 
Folge von Funktionen aufrufen, z.B.:
1
    lcd_set_cursor(cursor_x, cursor_y);
2
    print_num_style(leadingzero, decimal_at, ...);
3
    print_value(value);

Man kann natürlich auch eine "Meta-Funktion" machen die die gesamten 
Parameter annimmt und dann ihrerseits die einzelne Funktionen aufruft.

Am wichtigsten ist aber das benutzen der eigenen divmod Funktion. Das 
spart schonmal eine Menge Code und somit Platz, weil der ganze unnütze % 
und / Kram nicht mehr benutzt wird.

Grüße,

Chris

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.