32-Bit Arithmetik mit dem AVR

OP #975577
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
Gast
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.
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.
OP
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.
> 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.
Gast
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.
Gast
> 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.
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?
OP
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!
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
Gast
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.
Persönliche Seite
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.
Gast
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.
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
Gast
> 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.
@ 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
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
}
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.

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über 18 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.