Hallo Forumsgemeinde,
folgendes System:
AVR Mega 64,
Schrittmotorsteuerung,
Der Schrittmotortreiber ist ein Trinamic mit SPI. Es wird ein 12 Bit
Wert übergeben, der die Werte von Spule A und Spule B beinhaltet. Diese
Werte stehen in einer Tabelle
IAR Compiler (jetzt bitte nicht die Augen rollen, es ist ein C-Problem)
struct
{
unsigned int sollwert;
unsigned int istwert;
unsigned char richtung;
...
unsigned int spulenwert;
...
} dimmer;
Folgende Konstruktion funktioniert:
dimmer.spulenwert = sinus_tab[dimmer.mikro_schritt];
cs_dimmer_low;
SPDR = dimmer.spulenwert >> 8; //Highbyte ausmaskieren u. senden
while (!(SPSR & (1 << SPIF)));
SPDR = (unsigned char) dimmer.spulenwert; //Lowbyte senden
while (!(SPSR & (1 << SPIF)));
cs_dimmer_high;
folgendes aber nicht:
struct
{
unsigned int sollwert;
unsigned int istwert;
unsigned char richtung;
...
union
{
unsigned cvhar low_byte;
unsigned char dimmer;
unsigned int spulenwert;
}
motor;
...
} dimmer;
dimmer.motor.spulenwert = sinus_tab[dimmer.mikro_schritt];
cs_dimmer_low; //Dimmer-CS einschalten
SPDR = dimmer.motor.high_byte;
while (!(SPSR & (1 << SPIF)));
SPDR = dimmer.motor.low_byte;
while (!(SPSR & (1 << SPIF)));
cs_dimmer_high; //Dimmer-CS ausschalten
Das High und Lowbyte habe ich schon getauscht, aber das war der Fehler
nicht. Der Motor läuft unsauber und ruckelig. Die Tabelle mit den
Spulenwerten ist die gleiche.
Hat einer von euch eine Idee?
MW
Hast du hier nicht einen Tippfehler gemacht?
> union> {> unsigned cvhar low_byte;> unsigned char dimmer;> unsigned int spulenwert;> }> motor;
die union enthält kein high_byte.
Bitte mach immer ein Copy&Paste um Quelltext
zu posten. Sonst suchen wir uns die Hacken nach
Fehlern ab, die in deinem wirklichen Program gar
nicht vorhanden sind.
Trotzdem. Ich rate mal:
Deine union sieht in Wirklichkeit so aus:
union
{
unsigned char low_byte;
unsigned char high_byte;
unsigned int spulenwert;
}
motor;
Dann ist klar, dass das nicht gehen kann.
high_byte und low_byte teilen sich ein und
denselben Speicher. Dazu kommt dann noch
spulenwert, der auch noch drüber liegt.
Im Speicher spielt sich also folgendes ab:
+---+---+
| | |
+---+---+
^
hier liegt low_byte
^
hier liegt high_byte
^ ^
hier liegt spulenwert (2 Bytes)
Ansonsten bleibt nur noch das Problem der Endianess.
Abhängig vom Prozessor kann der int mit dem
High Byte oder mit dem Low Byte zuerst im Speicher
abeglegt sein. Es ist also Prozessorbahängig, welches
Byte vom int du tatsächlich über low_byte zu Gesicht
bekommst.
Du könntest folgendes machen:
struct Bytes {
unsigned char low;
unsigned char high;
}
...
struct {
...
union {
struct Bytes bytes;
unsigned int spulenwert;
}
motor;
} dimer;
Der Zugriff wäre dann:
dimmer.motor.spulenwert = ....
SPDR = dimmer.motor.bytes.low
SPDR = dimmer.motor.bytes.high
Jetzt muss nur noch der Compiler mitspielen:
* Ob die Bytes tatsächlich in der Reihenfolge
low / high im Speicher liegen
* Ob der Compiler nicht zwischen Bytes.high und Bytes.low
im Speicher noch sog. 'Padding Bytes' unterbringt.
Kein Mensch zwingt den Compiler nämlich, dass struct
Member im Speicher direkt aufeinanderfolgend sein müssen :-)
Danke Karl Heinz,
das war der entschidende Wink. So gehts:
unsigned char speed;
unsigned char richtung;
union
{
unsigned int spulenwert;
struct
{
unsigned char low_byte;
unsigned char high_byte;
}bytes;
}motor;
} dimmer;
dimmer.motor.spulenwert = sinus_tab[dimmer.mikro_schritt];
cs_dimmer_low; //Dimmer-CS einschalten
SPDR = dimmer.motor.bytes.high_byte;
while (!(SPSR & (1 << SPIF)));
SPDR = dimmer.motor.bytes.low_byte;
while (!(SPSR & (1 << SPIF)));
cs_dimmer_high; //Dimmer-CS ausschalten
Beim Copy und Paste ist die Formatierung nicht so dolle. Liegt aber an
den Einstellungen meines Editors.
MW
Michael Wilhelm wrote:
> Beim Copy und Paste ist die Formatierung nicht so dolle. Liegt aber an> den Einstellungen meines Editors.>> MW
Hm, sorry, aber das sieht immer n och nicht nach C&P aus:
>Kein Mensch zwingt den Compiler nämlich, dass struct>Member im Speicher direkt aufeinanderfolgend sein müssen :-)
doch zumindest beim gcc mit __attribute__((_packed_)) für die Struktur
bei anderen compilern meist mit irgendwelchen #pragma
Solange du beim AVR bleibst wirst du ueber padding bytes nicht stolpern,
nur denk dran wenn du den Code wo anders verwnedest.
Gemäss meinem C-Buch dürfen keine Padding Bytes vom Compiler eingesetzt
werden. Wenn doch, wäre die Union als solche nicht zu gebrauchen,
jedenfalls nicht mit dem Merkmal, dass sich verschiedene Datentypen
einen Speicherbereich teilen.
MW
Eine union fuegt keine padding bytes ein, eine struct kann das aber
schon. Die gewuenschte union war ja eine aus struct + int.
Allerdings duerfen selbst hier nur dann vom Compiler padding bytes
eingefuegt werden, wenn "unsigned char" groeßer als 8 bits waere
(was beim AVR nicht der Fall ist). Abhilfe waere die Benutzung
von uint8_t (aus <stdint.h>), da es diesen Datentyp auf einer
Maschine einfach nicht gibt, die keine 8-Bit-Integers beherrscht.
> Allerdings duerfen selbst hier nur dann vom Compiler padding bytes> eingefuegt werden, wenn "unsigned char" groeßer als 8 bits waere> (was beim AVR nicht der Fall ist).
Klar. Beim AVR kommt das nicht vor.
Bei anderen Prozessoren (ab 16 Bit) ist es aber durchaus
nicht ungewöhnlich, dass der Compiler zwischen 2 unsigned
char ( mit je 8 Bit) ein Padding Byte einfügt.
Wenn ich mich recht erinnere war es doch zb. bei einer 68000
so, dass Speicherzugriffe auf ungerade Adressen zu einer
Exception führt. Also hat der Compiler speziell unsigned
char so ausgerichtet, dass jeder der beiden auf jeweils
einer geraden Adresse zu liegen kommt.
Bei einer
struct xyz {
unsigned char a;
unsigned char b;
};
führt daher der Zugriff auf xyz.b ohne Padding Byte zu
einem kräftigen Penalty. Mit einem Padding Byte ist
der Zugriff aber 'straight forward'.
> Wenn doch, wäre die Union als solche nicht zu gebrauchen,> jedenfalls nicht mit dem Merkmal, dass sich verschiedene Datentypen> einen Speicherbereich teilen.
Wenn wir ganz streng C programmieren, dann ist obige
Verwendung der union eigentlich gar nicht erlaubt. Na, ja
nicht erlaubt ist der falsche Ausdruck. Es ist undefiniert
was passiert. Laut C Standard darf man aus einer union
nur über denselben Member lesen über den der letzte Schreib-
zugriff erfolgt ist.
Damit ist aber das reinschreiben in einen int und das
rauslesen über 2 unsigned char schon im Bereich undefiniertes
Verhalten.
Klar: Ich weiss auch, dass das in der Praxis überall funktioniert.
Aber ganz streng genommen muss es das nicht.
So Leute,
noch einmal Dank an alle, die sich mit dem Problem befasst haben.
Simulation im AVR-Studio:
Version 1 mit der ganz normalen Struktur und dem "hinschieben" des
INT-Wertes für die Motorausgabe: 92 Taktzyklen.
Version 2 mit der union 90 Taktzyklen.
Also, ich hatte mir mehr versprochen.
Aber, Erkenntnisse hat es trotzdem gbracht.
MW
D.h. Dir geht es um die Geschwindigkeit?
Die ist doch hauptsächlich durch das Warten auf den SPI gegeben.
Wenn Du etwas Zeit sparen willst, dann lade während der ersten Wartezeit
den 2. Wert schonmal vor:
1
cs_dimmer_low;
2
SPDR=dimmer.spulenwert>>8;//Highbyte ausmaskieren u. senden
Statt faul auf SPIF zu warten, kann der mc ruhig schonmal vorarbeiten
...
Übrigens setzt gcc diesen Ausdruck meines Wissens nicht mit 8*Shiften
um, sondern kopiert das High- ins Low-Byte:
1
SPDR=dimmer.spulenwert>>8;
Einziger Unterschied zum Byte-Zugriff per union: es wird ein 16-Bit-Wert
geladen und das High- ins Low-Byte kopiert, anstatt direkt das
8-Bit-Lowbyte zu verwenden. Das kommt auch gut an Deine 2 Zyklen
Unterschied hin.
Schau mal in den Assembler-Output.
Viele Grüße, Stefan
Und Dein ATmega hat einen 16Mhz-Quarz? Dann wartest Du min. 64 Takte auf
den SPI von den gemessenen 90.
>P.S.>Ich habe keine Ahnung von Assembler.
Musst Du auch nicht. Schau einfach mal den Quellcode bei verschiedenen
Programmversionen an. Ob z.B. irgendwo geshiftet wird, siehst Du auch
ohne viel Assembler-Ahnung. Aber Du erhälst mit der Zeit ein Gefühl
dafür, welcher C-Code aufwendigen und welcher Code effizienten Output
erzeugt. Allein die Länge des Assembler-Outputs ist ein guter
Anhaltspunkt.
Gruß, Stefan
>Na ja, was ich im Studio simuliert und getestet habe, waren die>Taktzyklen und nicht die Zeit. AVR-Studio löst die Stoppuhr ja nur mit>0,5 µs auf und das ist bei taktgenauer Analyse doch etwas grob.
Ich habe wegen dem Teiler-Verhältnis für den SPI nach dem Takt gefragt,
4 CPU-Takte -> ein SPI-Clock.
>Ich kopiere mal den schnellsten C-code rein und das, was der Assembler>macht. Du wirst staunen, wie der mit den X,Y und Z Registern am>jonglieren ist.
Der Code sieht doch garnicht so schlecht aus. Viel besser schafft man es
auch von Hand nicht.
Viele Grüße, Stefan
Die allerschnellste Variante geht nicht über ein union
sondern durch direkten Byte-Zugriff:
int i;
davon wollen wir jetzt das low Byte:
unsigned char low = *((unsigned char*)&i);
und das High Byte
unsigned char high = *(((unsigned char*)&i)+1);
Also nichts mit vorher umkopieren oder dergleichen.
Man nimmt einfach die Adresse der Speicherstelle an
der der Wert gespeichert ist und teilt dem Compiler
mit, das mal als die Adresse eines Bytes aufzufassen und
das Byte zu holen. Dann dieselbe Adresse, wieder als
Adresse auf ein Byte umgecastet, 1 dazu (weil ja das nächste
Byte gefragt ist) und eben dieses Byte geholt.
Wenn du also den Spulenwert eigntlich überhaupt
nicht brauchst, dann probiere mal
> Karl Heinz, deine Lösung benötigt 118 Takte.
Ehrlich? (Ich habs nicht ausprobiert).
Das hätte ich nicht gedacht.
Welchen Code hast du konkret probiert.
Das interessiert mich jetzt, möchte in bischen
mit dem Compiler spielen. Die Vorgabe von 90
Takten steht ja.
Vor allem die Zuweisungen an SPDR kriegt man nicht mehr
kürzer hin.
Die Variante über die union ist deutlich länger (und komplizierter).
Bedingt durch das rel. komplizierte Holen des Wertes in
den Zwischenspeicher der union.
@Karlheinz,
die Sinustabelle scheint im Flash zu liegen. Michael benutzt den
IAR-Compiler, nicht den gcc, deswegen sind seine Ergebnisse nicht ganz
vergleichbar.
Gruß, Stefan
@Karlheinz,
die Pointer Variante ist nicht immer die günstigste, da das immer übers
Ram abgewickelt wird. Besser ist meist ein direkter Cast auf eine Union,
den kann der Compiler dann unter Umständen direkt über Register
optimieren.
z.B. so:
> Michael benutzt den IAR-Compiler, nicht den gcc,
Ah, ok. Das habe ich verpennt.
> die Pointer Variante ist nicht immer die günstigste, da das immer> übers Ram abgewickelt wird. Besser ist meist ein direkter Cast auf> eine Union, den kann der Compiler dann unter Umständen direkt über> Register optimieren.
Netter kleiner Trick. Muss ich mir merken.
Danke