Forum: Compiler & IDEs 32-Bit Arithmetik mit dem AVR


von Paul H. (powl)


Lesenswert?

Hi!

Kann es sein, dass der GCC es noch nicht so ganz drauf hat mit 32-Bit 
Arithmetik im AVR?

cplex_data ist eine 32-Bit Variable, seconds, minutes und hours sind 
8-Bit Variablen. Das ganze ist für eine Binär-Uhr und soll das 
Ausgaberegister für den Charlieplexer zusammenbauen.
1
cplex_data = ((uint32_t) hours << 12) | (minutes << 6) | seconds;

Diese Zeile jedoch wird vom Compiler mit 300(!!) Bytes an Code 
übersetzt.

Eigentlich hat der AVR da kaum Arbeit. cplex_data besteht aus 4 
einzelnen 8-bit speicherstellen. Seconds kann man einfach mit der ersten 
OR-verknüpfen. Minutes muss in zwei Stücke zerteilt werden, wovon das 
erste mit der ersten Speicherstelle OR-verknüpft und das zweite einfach 
in die zweite Speicherstelle kopiert wird. Bei Hours ist es nicht 
wesentlich komplizierter. Genauergesagt würden 24bit auch ausreichen 
aber es gibt standardmäßig keinen 24-bit Datentyp.

Kann man irgendwie auf die einzelnen Speicherstellen zugreifen und das 
ganze somit per Hand etwas optimieren?

lg PoWl

von Peter (Gast)


Lesenswert?

Welchen Optimierungs-Level hast Du eingestellt? Ich empfehle zumindest 
-O1 zu verwenden. Die anderen Stufen -O2/3/s bringen kaum noch was.

Verwendest Du die für den AVR optimierte MathLib? (Linker Option -lm) 
Defaultmässig wird nämlich die generische GCC MathLib gelinkt, die ist 
natürlich gross und langsam.

von Paul H. (powl)


Lesenswert?

-O1 bringt nochmal knapp 190 Bytes obendrauf und die Zeile wird mit 326 
Bytes übersetzt.

-lm beim Linker reinzuschreiben hatte keinerlei Auswirkungen.

von Stefan E. (sternst)


Lesenswert?

Paul Hamacher wrote:

>
1
cplex_data = ((uint32_t) hours << 12) | (minutes << 6) | seconds;
>
> Diese Zeile jedoch wird vom Compiler mit 300(!!) Bytes an Code
> übersetzt.

Sicher?
Bei mir sind es ganz ohne Optimierungen 112 Bytes und mit "-Os" 86 
Bytes.

> Eigentlich hat der AVR da kaum Arbeit.

Der AVR hat keine Asm-Befehle für mehrfaches Schieben, also muss "<< 6" 
und "<< 12" über Schleifen realisiert werden und das auch noch mit 
32-Bit Werten. Da kommt halt einiges zusammen.

Du kannst den Code etwas reduzieren, indem du den Compiler nicht dazu 
zwingst, alles in 32-Bit zu tun:
1
cplex_data = ((uint16_t)minutes << 6) | seconds;
2
cplex_data |= ((uint32_t) hours << 12);

Mit "-Os" sind es dann bei mir 50 Bytes.

von Paul H. (powl)


Lesenswert?

vielleicht liegts auch daran dass alle variablen volatile sind.
Thx ich versuche mal das aufzuteilen.

Kann man die einzelnen Speicherstellen der 32-Bit Variable irgendwie 
einzeln erreichen?
1
  cplex_data = ((uint16_t) minutes << 6) | seconds;
2
  cplex_data |= ((uint32_t) hours << 12);

Das wird bei mir bei -Os leider sogar noch 32 Bytes länger als vorher.

von Paul H. (powl)


Lesenswert?

oh tut mir leid.. hab mich wohl irgendwie verguckt?! Bei mir sinds auch 
86 Bytes. Der Zweizeiler bringt es allerdings auf 90.

von Stefan E. (sternst)


Lesenswert?

> Kann man die einzelnen Speicherstellen der 32-Bit Variable irgendwie
> einzeln erreichen?

Klar, aber was soll dir das hier konkret bringen?

> oh tut mir leid.. hab mich wohl irgendwie verguckt?! Bei mir sinds auch
> 86 Bytes. Der Zweizeiler bringt es allerdings auf 90.

Wenn cplex_data volatile ist, wird es beim Zweizeiler ja zwischendurch 
gespeichert und neu geladen. Das sind immerhin 32 Byte. Du könntest das 
mit einer temporären nicht volatile Variable umgehen. Ich frage mich 
allerdings, ob cplex_data überhaupt volatile sein muss.

von ... (Gast)


Lesenswert?

Must du unbedingt so krumme Werte für die Bit-Schieberei nehmen? Wenn du 
die Möglichkeit hast, die Daten so abzuspeichern wie du willst, dann 
kannst du jeweils ein ganzes Byte für einen Wert nehmen. Das ist 
bedeutend effizienter.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Paul Hamacher wrote:
> vielleicht liegts auch daran dass alle variablen volatile sind.

Aua!

von Falk B. (falk)


Lesenswert?

@Jörg Wunsch (dl8dtl) (Moderator)

>> vielleicht liegts auch daran dass alle variablen volatile sind.

>Aua!

Sicher ist sicher, damit die nicht weglaufen . . . ;-)

von Paul H. (powl)


Lesenswert?

sie müssen volatile sein weil mein programm noch etwas mehr als nur 
diese paar zeilen umfasst

von Chris (Gast)


Lesenswert?

> sie müssen volatile sein weil mein programm noch etwas mehr als nur
> diese paar zeilen umfasst

Was spricht dagegen, die volatile-Variablen im ersten Schritt in 
temporäre (sprich, lokale) nicht-volatile-Variablen zu kopieren und die 
shifts dann mit denen zu machen?

Denn es ist ja eh klar, dass 32-Bit-Variablen auch mit volatile nicht 
atomar bearbeitet werden können, also immer noch andere 
Synchronisationsmechanismen vorhanden sein müssen.

von Stefan E. (sternst)


Lesenswert?

Chris wrote:

> Was spricht dagegen, die volatile-Variablen im ersten Schritt in
> temporäre (sprich, lokale) nicht-volatile-Variablen zu kopieren und die
> shifts dann mit denen zu machen?

Ist gar nicht nötig (bzw vergrößert den Code nur), da nur lesend auf die 
Variablen zugegriffen wird. Nur beim cplex_data stört das volatile, 
insbesondere in der Version mit den zwei Zeilen. Lässt sich aber leicht 
"entschärfen":
1
uint32_t temp;
2
temp = ((uint16_t) minutes << 6) | seconds;
3
temp |= ((uint32_t) hours << 12);
4
cplex_data = temp;


Paul Hamacher wrote:

> sie müssen volatile sein weil mein programm noch etwas mehr als nur
> diese paar zeilen umfasst

Sorry, aber ich vertraue deinen C-Kenntnissen nicht weit genug, um das 
einfach so zu glauben. Wenn du nämlich cplex_data in einem Interrupt 
rausschiftest und im "normalen" Code zusammenstellst (oder einem anderen 
Interrupt), sehe ich nicht, warum das zwingend volatile sein muss.
"Zu viel" volatile ist meist der Anschlussfehler zu "gar kein" volatile. 
Und warst du nicht erst kürzlich beim Status "gar kein", oder verwechsel 
ich dich da?

von Paul H. (powl)


Lesenswert?

Das wird sowohl im Interrupt gelesen als auch geschrieben und muss im 
gesamten Programm verfügbar sein.

Ja, ich gebe zu, ich würde mich auch eher zu den Anfängern schätzen, 
aber Übung macht den Meister.

Danke jedenfalls für die Hilfe!

von Stefan E. (sternst)


Lesenswert?

Paul Hamacher wrote:
> Das wird sowohl im Interrupt gelesen als auch geschrieben und muss im
> gesamten Programm verfügbar sein.

Wie gesagt, das alleine macht ein volatile nicht zwingend nötig.

von Paul H. (powl)


Lesenswert?

Wenn ich das volatile weglasse erzeugt der Compiler noch 2 Bytes mehr 
code.. gibt sich wohl nicht viel.

von Peter D. (peda)


Lesenswert?

Paul Hamacher wrote:

> Eigentlich hat der AVR da kaum Arbeit. cplex_data besteht aus 4
> einzelnen 8-bit speicherstellen.

Was spricht dann dagegen, es als Array aus 4 Bytes zu definieren?

32Bit mag der AVR-GCC nicht, er macht dann immer 4 Byte-Zugriffe.

8Bit kann er wesentlich besser optimieren.


Peter

von bitfield benutzer (Gast)


Lesenswert?

sollte man normalerweise ja aus Portabilitätsgründen meiden, aber hier 
würde mal Sinn machen: bitfield benutzen á la:
1
struct cplex {
2
        uint32_t :16;
3
        uint32_t hours:4;
4
        uint32_t minutes:6;
5
        uint32_t seconds:6;
6
};
7
typedef struct cplex cplex_t;
8
9
extern cplex_t cplex_data;
10
cplex_t cplex_data;
11
12
13
void bla(uint8_t hours, uint8_t minutes, uint8_t seconds)
14
{
15
        cplex_data.hours = hours;
16
        cplex_data.minutes = minutes;
17
        cplex_data.seconds = seconds;
18
}

reicht für eine 12h Uhr. Daraus macht der gcc dann:
1
  54:   96 2f           mov     r25, r22
2
  56:   92 95           swap    r25
3
  58:   90 7f           andi    r25, 0xF0       ; 240
4
  5a:   8f 70           andi    r24, 0x0F       ; 15
5
  5c:   89 2b           or      r24, r25
6
  5e:   80 93 62 00     sts     0x0062, r24
7
  62:   62 95           swap    r22
8
  64:   6f 70           andi    r22, 0x0F       ; 15
9
  66:   44 0f           add     r20, r20
10
  68:   44 0f           add     r20, r20
11
  6a:   63 70           andi    r22, 0x03       ; 3
12
  6c:   64 2b           or      r22, r20
13
  6e:   60 93 63 00     sts     0x0063, r22

Macht 30 byte. Mit einer 24h Uhr wird es für den Compiler etwas 
komplexer und er kommt auf 52 bytes.

von Simon K. (simon) Benutzerseite


Lesenswert?

bitfield benutzer wrote:
> sollte man normalerweise ja aus Portabilitätsgründen meiden

Das ist hochgradiger Quatsch! Bitfelder sind C-definiert.

Portabilitätsbedenken gibts nur, wenn man versucht solche Bitfields 
(beispielsweise) über Netzwerk überträgt und die eine Maschine 
Big-Endian ist, und die andere Little-Endian.
Oder wenn man ein Bitfield benutzt und das dann um-castet in eine 
integrale Variable (int, long, char). Die Position der Bits ist nämlich 
nicht bei jeder Maschine gleich.

Wenn man das Bitfeld aber nur zum Speichern von Daten verwendet, die auf 
ein und der selben Maschine bleiben und auf die auch nur über die 
Elemente des Bitfields zugegriffen wird, stimmt alles.

Also dein Beispiel wäre in Ordnung.

von Stefan E. (sternst)


Lesenswert?

bitfield benutzer wrote:

> Macht 30 byte. Mit einer 24h Uhr wird es für den Compiler etwas
> komplexer und er kommt auf 52 bytes.

Also keinerlei Vorteil gegenüber der Lösung ohne Bitfelder.

von bitfield benutzer (Gast)


Lesenswert?

Simon K. wrote:
> Das ist hochgradiger Quatsch! Bitfelder sind C-definiert.

naja, nur bedingt, wie Du ja so richtig im nächsten Absatz beschrieben 
hast. Wenn man Daten mit anderen Systemen austauschen muß, sollte man 
Bitfelder meiden, da abhängig von der Kompilerimplementierungen 
(schriebst Du ja schon). Hab viel mit solchen Systemen zu tun, deswegen 
meine Bemerkung. War vielleicht etwas unvollständig, sorry.

@Stefan Ernst:
Wird schon so sein, sieht aber IMHO mit Bitfeldern etwas übersichtlicher 
aus. Und der OP hat ja explizit gefragt, ob es eine Möglichkeit gibt, 
direkt auf die Speicherstellen zuzugreifen. Klang für mich nach 
Bitfeldern.

von Michael A. (micha54)


Lesenswert?

Paul Hamacher wrote:
> Wenn ich das volatile weglasse erzeugt der Compiler noch 2 Bytes mehr
> code.. gibt sich wohl nicht viel.

Hallo,

das volatile nervt mich auch immer ein wenig. Es soll verhindern, daß 
der Wert in registern gehalten wird und im Speicher immer der alte Wert 
zu lesen ist.

Es schützt aber meines Wissens nicht wirklich dagegen, daß die Änderung 
nur teilweise durchgeführt wurde, bis der Interrupt dazwischen sprang.

Wie ist das mit nem einfachen volatile integer x, kann da der Interrupt 
bei einem x -= 100 im Hauptprogramm zwischen die Befehle 
dazwischespringen ? D.h. benötige ich nicht eigentlich ein cli() und 
sei() drumherum ?

Gruss,
Michael

von Chris (Gast)


Lesenswert?

> Wie ist das mit nem einfachen volatile integer x, kann da der Interrupt
> bei einem x -= 100 im Hauptprogramm zwischen die Befehle
> dazwischespringen ?

Ja, kann er, weil diese Zuweisung nicht mit einem Maschinenbefehl zu 
schaffen ist.

von Falk B. (falk)


Lesenswert?

@ Michael Appelt (micha54)

>Es schützt aber meines Wissens nicht wirklich dagegen, daß die Änderung
>nur teilweise durchgeführt wurde, bis der Interrupt dazwischen sprang.

Richtig.

>dazwischespringen ? D.h. benötige ich nicht eigentlich ein cli() und
>sei() drumherum ?

Ja, siehe Interrupt.

MFG
Falk

von Daniel R. (zerrome)


Lesenswert?

hallo,
wenn ich auf die stellen einer 32 bit variabel schreiben möchte, nehme 
ich immer einen gecasteten void pointer. so kann man z.b. bequem in 8 
bit häppchen schreiben.
hab mal die hier geschriebenen sachen ausprobiert und komme mit meiner 
methode auf 28 byte an code...
1
unsigned long int cplex_data;
2
unsigned char hours;
3
unsigned char minutes;
4
unsigned char seconds;
5
6
7
8
void set_Time(void){
9
10
   //28 bytes
11
   void *vcplex_data=&cplex_data;
12
   void *vhours=&hours;
13
   void *vminutes=&minutes;
14
   void *vseconds=&seconds;
15
16
   *(unsigned char*)vcplex_data++=*(unsigned char*)vseconds;
17
   *(unsigned char*)vcplex_data++=*(unsigned char*)vminutes;
18
   *(unsigned char*)vcplex_data=*(unsigned char*)vhours;
19
20
21
   /*// 90 bytes
22
   uint32_t temp;
23
   temp = ((uint16_t) minutes << 6) | seconds;
24
   temp |= ((uint32_t) hours << 12);
25
   cplex_data = temp;
26
   */
27
28
29
   //118 bytes
30
   //cplex_data = ((uint32_t) hours << 12) | (minutes << 6) | seconds;
31
32
}
33
34
35
36
void main(void){
37
   set_Time();
38
}

von Stefan E. (sternst)


Lesenswert?

Daniel Platte wrote:

> hab mal die hier geschriebenen sachen ausprobiert und komme mit meiner
> methode auf 28 byte an code...

Schön. Nur dumm, dass dein Code nicht das macht, was der ursprüngliche 
Code tut und der OP braucht.

von Daniel R. (zerrome)


Lesenswert?

ja war schon spät...
hab nur gelesen 8 bit variablen ... bla bla und gesehen das geshiftet 
wird...

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.