Hallo! Ich arbeite an einem Projekt mit einem AT90CAN128 AVR Controller. Da der Controller mit den bisherigen Aufgaben (SPI-Datenausgabe und CAN-Datentransfer) schon an seine Leistungsgrenze gerät, habe ich mich mal gefragt, ob man das Ganze mit Assembler effektiver Programmieren kann. Leider habe ich keine Ahnung von Assembler. Ich möchte auf keinen Fall das ganze Projekt von vorne in Assembler neu programmieren, sondern vielleicht einige Teile, die ständig abgearbeitet werden, durch Inline-Assembler-Code ersetzen. Mir stellt sich aber die Frage, ob das überhaupt was bringt? Übersetzt der AVR-GCC schlecht? Hier mal ein Beispiel: Ich gebe per SPI Daten aus. Es gibt in der spi_drv.h das Macro Spi_send_byte(ch) #define Spi_send_byte(ch) { (SPDR=ch); Spi_wait_spif(); } mit #define Spi_wait_spif() { while ((SPSR & (1<<SPIF)) == 0); } wenn ich nun den Disassembler von AVR-Studio nutze, so wird mit für einen Aufruf Spi_send_byte(u_data.b[1]); der folgende Code angezeigt: LDI R30, 0x4E LDI R31, 0x00 LDD R24, Y+3 STD Z+0, R24 LDI R30, 0x4D LDI R31, 0x00 LDD R24, Z+0 TST R24 BRGE PC-0x04 Ist der erzeugte Code sinnvoll? Gehts kürzer, schneller? Warum wird hier etwas in die Register R30 und R31 geladen und dann nicht verwendet? Bringt es was, mich in das Thema Inline-Assembler einzuarbeiten, oder hol ich damit nicht viel raus, weil der AVR-GCC gut genug ist? Vielen Dank für eure Hilfe!
>Da der Controller mit den bisherigen Aufgaben (SPI-Datenausgabe und >CAN-Datentransfer) schon an seine Leistungsgrenze gerät, Woher weisst du das? Warteschleifen wie
1 | #define Spi_wait_spif() { while ((SPSR & (1<<SPIF)) == 0); }
|
deuten darauf hin, daß der Prozessor doch den einen oder anderen Takt damit verbringt, mit voller Leistung nichts zu tun. Oliver
Hazel wrote: > CAN-Datentransfer) schon an seine Leistungsgrenze gerät, habe ich mich > mal gefragt, ob man das Ganze mit Assembler effektiver Programmieren > kann. Selten. Generell: Um einen Compiler zu schlagen muss man schon ziemlich gut in Assembler sein. Beim AVR hat der gcc zwar noch Potential um zuzulegen. Das bewegt sich aber im kleinen einstelligen Prozentbereich. > Leider habe ich keine Ahnung von Assembler. Ich möchte auf keinen > Fall das ganze Projekt von vorne in Assembler neu programmieren, sondern > vielleicht einige Teile, die ständig abgearbeitet werden, durch > Inline-Assembler-Code ersetzen. Das bringt selten was. Wenn schon, dann solltest du versuchen auf algorithmischer Ebene Zeit einzusparen. Da ergibt sich meist das größte Potential. > #define Spi_send_byte(ch) { (SPDR=ch); Spi_wait_spif(); } > mit > #define Spi_wait_spif() { while ((SPSR & (1<<SPIF)) == 0); } > > wenn ich nun den Disassembler von AVR-Studio nutze, so wird mit für > einen Aufruf Spi_send_byte(u_data.b[1]); der folgende Code angezeigt: > > LDI R30, 0x4E > LDI R31, 0x00 > LDD R24, Y+3 > STD Z+0, R24 > LDI R30, 0x4D > LDI R31, 0x00 > LDD R24, Z+0 > TST R24 > BRGE PC-0x04 > > Ist der erzeugte Code sinnvoll? Gehts kürzer, schneller? Warum wird hier > etwas in die Register R30 und R31 geladen und dann nicht verwendet? Allein mit dieser Aussage solltest du die Idee von Assembler gleich wieder aufgeben. R30 + R31 werden verwendet. Sie bilden das Z-Register und das wird hier STD Z+0, R24 benutzt. > Bringt es was, mich in das Thema Inline-Assembler einzuarbeiten, oder > hol ich damit nicht viel raus, weil der AVR-GCC gut genug ist? Den einen oder anderen Takt wirst du damit sicherlich einsparen können. Alleine der Aufwand steht bei einem größeren Projekt selten dafür. Was man aber machen kann: Man kann sich ansehen, was der Compiler mit bestimmten Konstrukten macht und dann nach Wegen suchen, den Compiler dazu zu bringen, schnellere Konstrukte zu verwenden. Gerade wenn es um die Verwendung von unterschiedlichen Datentypen (int - uint8_t - long) in ein und demselben Ausdruck geht, hat der gcc noch Potential. Aber auch hier wieder: Ein gewisses Verständnis für Assembler ist unabdingbar. Genauso wie man analysieren muss, warum der Compiler welche Codesequenz genauso übersetzt hat, wie er das hat.
Ja, da hast du schon Recht. Hier muss er wohl warten. Hab aber auch keine Idee, wie ich das sonst machen soll. Es folgt ja danach sofort ein weiteres Byte, welches per SPI übertragen wird, da muss er vorher das eine fertiggestellt haben. Aber CAN hab ich z.B. mit Interrupts programmiert.
Hazel wrote: > Ja, da hast du schon Recht. Hier muss er wohl warten. > Hab aber auch keine Idee, wie ich das sonst machen soll. Es folgt ja > danach sofort ein weiteres Byte, welches per SPI übertragen wird, da > muss er vorher das eine fertiggestellt haben. Richtig. Aber du musst ja nicht nach dem Start der Übertragung warten bis die Übertragung fertig ist. Das Übertragen macht doch die Hardware ganz von alleine. Der Proz kann in der Zwischenzeit ja ganz was anderes machen (zb. das nächste Byte besorgen). Was du doch in Wirklichkeit machen musst ist: Vor der eigentlichen Übertragung sicherstellen, dass die SPI Einheit auch dazu bereit ist. void Spi_send_byte(ch) { while ((SPSR & (1<<SPIF)) == 0) ; SPDR = ch; } und schon wartet der Proz nur noch dann, wenn es unbedingt notwendig ist. Wird Spi_send_byte betreten und die vorhergehende Übertragung ist schon eredigt, gibt es auch nichts zu warten.
Das ist gut. Und ich habe einfach dem vorgefertigten Macro aus der spi_drv.h vertraut. werde das gleich mal ändern. Danke. Also wenn das so ist, dann werde ich die Idee mit Inline-Assembler doch wieder verwerfen.
Hazel wrote: > Das ist gut. Und ich habe einfach dem vorgefertigten Macro aus der > spi_drv.h vertraut. Vorsicht mit dem Makro. Wenn du dort die Reihenfolge umdrehst ergibt sich nicht unbedingt das was du haben willst! Eine Funktion ist besser. #define Spi_send_byte(ch) { Spi_wait_spif(); (SPDR=ch); } #define Spi_wait_spif() { while ((SPSR & (1<<SPIF)) == 0); } Spi_send_byte(u_data.b[1]); Werden die Makros aufgelöst, ergibt sich: { Spi_wait_spif(); (SPDR=u_data.b[1]); } Und damit erfolgt die Array-Adress Berechnung erst recht wieder nach der Warte-Abfrage. Ich möchte die Adressberechnung auch noch vor die Abfrage haben. Eine Funktion gewährleistet mir das (selbst wenn sie inline aufgelöst wird): Argumente müssen vor Betreten der Funktion bestimmt werden. Inline ändert daran nichts. Wenns unbedingt ein Makro sein muss (wovon ich eigentlich abrate, dann so: #define Spi_send_byte(ch) { char c = (ch); Spi_wait_spif(); SPDR = c; } Das zwingt wieder den Compiler den Ausdruck u_data.b[1] zu bestimmen, bevor die Warterei begonnen wird.
Hazel wrote: > Ich arbeite an einem Projekt mit einem AT90CAN128 AVR Controller. Da der > Controller mit den bisherigen Aufgaben (SPI-Datenausgabe und > CAN-Datentransfer) schon an seine Leistungsgrenze gerät Pauschal kann man dazu garnichts sagen. Du mußt erstmal feststellen, wo der Flaschenhals ist. Hast Du ne hohe Peaklast oder Dauerlast. Ne Peaklast kann man oft durch Pufferung entschärfen. Hast Du Routinen, wo die CPU >10% verbräht? Muß man die so oft aufrufen oder kann man die seltener aufrufen? Kann man Ergebnisse zwischenspeichern, die oft neu berechnet werden? Wenn Du nicht genau weißt, ob eine Routine ein Flaschenhals ist, setze eine Portpin am Anfang und lösche ihn am Ende. Dann häng ein Multimeter dran und dann entspricht VCC = 100% CPU-Last. Peter
Danke nochmals an Karl Heinz. Ich wär genau wieder in die Falle getappt. :-) Es ist so: Der Controller kommuniziert mit einem FPGA über ein Dual-Port-RAM. Das FPGA liefert mit einer Taktrate von 12,5kHz neue Daten und legt diese in das Dual-Port-RAM. Der AVR soll nun diese Daten vom Dual-Port-RAM holen und dann über seine CAN-Schnittstelle ausgeben (oder aber einen Datensatz über die SPI-Schnittstelle). Es handelt sich jeweils um drei Datensätze á 16 Bit. Also 6 Byte, die sogar über eine CAN-Botschaft übertragen werden können. Den Zugriff auf das Dual-Port-RAM, welches übrigens auch im FPGA realisiert ist, erledige ich mit einfachem Setzen und Rücksetzen von Pins (Steuerleitungen) sowie dem Setzen eines Adressbusses und dem Lesen von einem 8 bit Datenbus. Dann werden die Daten per CAN übertragen. Einfach alle 6 Byte in ein Datenpaket und mit einem Identifier versehen. Der CAN ist mit 1MBit/s konfiguriert. Leider schafft der AVR diese Abarbeitung nicht in den 80µs, die er dafür Zeit hat und ich kann nur jeden zweiten Datensatz per CAN übertragen. Sind meine Erwartungen zu hoch, oder sollte er das locker schaffen?
@ Hazel (Gast) >Dann werden die Daten per CAN übertragen. Einfach alle 6 Byte in ein >Datenpaket und mit einem Identifier versehen. Der CAN ist mit 1MBit/s >konfiguriert. Leider schafft der AVR diese Abarbeitung nicht in den >80µs, die er dafür Zeit hat und ich kann nur jeden zweiten Datensatz per >CAN übertragen. >Sind meine Erwartungen zu hoch, oder sollte er das locker schaffen? 80us sind bei 16 MHz 1280 Takte. Das sollte reichen. MFG Falk
Hazel wrote: > Dann werden die Daten per CAN übertragen. Einfach alle 6 Byte in ein > Datenpaket und mit einem Identifier versehen. Der CAN ist mit 1MBit/s > konfiguriert. Leider schafft der AVR diese Abarbeitung nicht in den > 80µs, die er dafür Zeit hat und ich kann nur jeden zweiten Datensatz per > CAN übertragen. > Sind meine Erwartungen zu hoch, oder sollte er das locker schaffen? Der CAN-Bus hat nicht 1MBit Nutzdatentrate, da geht noch was für Identifier und Protokoll drauf. 6 Byte sollten etwa 120µs brauchen (ohne andere CAN-Sender und ohne Retry!). Und selbst eine 10GHz CPU kann 120µs nie in 80µs schaffen! Umgekehrt ist aber auch schon heftig knack auf knirsch. Peter
Kurz gefast: Nein es geht nicht !! Ich erkläre - den AVR schaft es vielleicht den Daten zu holen und bereitzustellen, dein CAN-bus aber nicht! Selbst bei 1Mb/s ist jeder bit 1us lang. Also 6 data bytes = CAN frame länge 88 bits (44+8n, n=6bytes). das heist mit 100% buslast (was sowieso nicht auf dauer funktioniert) ist es nür möglich jeder 91us (88 + 3bit IFS, Bit-stuffing habe ich noch ignoriert) ein neue DataFrame zu senden. Um dieser datenmenge zu senden muss du eine andere kommunikationsmedium benutzen, welche überlass ich dir. *********************************************************** "Make it idiot-proof, and someone will make a better idiot"
Das ist eine klare Antwort. Danke. Warum hab ich das eigentlich noch nicht selbst berechnet?! Jetzt weiß ichs ja. Also mit Optimieren des Codes geht also nichts mehr.
>Wenn Du nicht genau weißt, ob eine Routine ein Flaschenhals ist, setze >eine Portpin am Anfang und lösche ihn am Ende. >Dann häng ein Multimeter dran und dann entspricht VCC = 100% CPU-Last. Geniale Idee, danke! Hmm analoges VU-Meter dran gefiele mir auch ;) Hat dann was von 'nem Drehzahlmesser.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.