Forum: Mikrocontroller und Digitale Elektronik Atmega SPI Geschwindigkeit (Arduino schneller als C?)


von Simon T. (narfinus)


Angehängte Dateien:

Lesenswert?

Grüße allerseits,

Ich habe ein etwas merkwürdiges Problem bei der Ansteuerung meines 
RGB-Cubes. Der 8x8x8-Cube wird 1-zu-8 zeitmultiplext und je eine Ebene 
per Schieberegister angesprochen. Dazu bietet sich natürlich die 
Hardware-SPI des AVR-Controllers an.

Ich bin auf einen sehr gut kommentierten Quellcode für einen Cube auf 
Arduino-Basis gestoßen. Dort wird die Interruptroutine zum Setzen der 
LEDs alle 124us aufgerufen. Ich habe den Code auf normales C angepasst, 
allerdings dauert die Abarbeitung dieser Routine bei mir mehr als 1ms 
und  ist damit viel zu langsam.

Link zum Arduino-Code:
http://www.kevindarrah.com/download/8x8x8/RGB_CubeV12_BitwiseFix.ino
©Kevin Darrah

Mein Code ist im Anhang (ATMega644, 8Mhz, no ClockDivider). Im Prinzip 
ist nur die Funktion refreshCube() von Bedeutung. Sowohl bei mir als 
auch beim Arduino-Orginal kommt eine Bit-Angle-Modulation zum Einsatz.

Hinweise:
Ich verwende zu Testzwecken einen 8MHz-Quarz. Aber auch bei doppelter 
Frequenz bleibt das Problem bestehen.
Die SPI-Frequenz habe ich per Oszi überprüft. Sie entspricht exakt der 
halben CPU-Taktung (4MHz).


Irgendwo muss ich einen kapitalen Fehler gemacht haben, die 
Arduino-Version funktioniert ja offensichtlich.

Ich hoffe jemand kann mich erleuchten.
Narf

von Herbert (Gast)


Lesenswert?

Hallo Narf,

kann es sein, dass du die Variable "level" nicht hochzählst.
Im Original wird sie im Funktionsaufruf bedient, bei dir meines 
Erachtens nicht.

von Karl H. (kbuchegg)


Lesenswert?

> Irgendwo muss ich einen kapitalen Fehler gemacht haben

Es sind zwar ein paar kleinere Probleme erkennbar, aber so richtig 
kapital sehe ich nichts.

Ein Problemkreis ist der Aufruf einer Funktion aus einer ISR.
Das ist nicht gut, weil du dadurch den Compiler zwingst, in der ISR erst 
mal alle Register zu sichern und danach wieder herzustellen. Das sind 
schon einige Taktzyklen, erklärt aber noch nicht die Millisekunde.

EIne Operation 1<<z willst du nicht haben, weil sie der Compiler in eine 
SChleife auflösen muss. Da muss man noch was anderes finden.

Auch ist die Ausgabe auf SPI noch suboptimial. Ein wesentlicher Punkt 
bei SPI ist es ja, dass SPI alleine läuft. D.h. während das Byte 
rausgetaktet wird, kann die CPU bereits was anderes machen. Wenn du aber 
nach dem STart des raustaktens durch Zuweisung an SPDR auf die 
Fertigstellung dieses Raustaktens wartest, dann verschenkst du diese 
Möglichkeit.

Summa-summarum sind das noch ein paar Ansatzpunkte. Sie erklären aber 
IMHO nicht, warum der ganze Vorgang sich im Millisekundenbereich 
abspielt.


Den Optimizer hast du auf -Os stehen?

von Karl H. (kbuchegg)


Lesenswert?

Ach.

HIer
1
for(int i=level; i < level+8; i++)

i muss kein int sein. Du zwingst den µC hier zu 16 Bit Arithmetik, die 
du gar nicht brauchst. Ein uint8_t tuts auch.

: Bearbeitet durch User
von Rudolph (Gast)


Lesenswert?

refresh_cube(); wird sowohl aus der ISR als auch von der Hauptschleife 
aus aufgerufen, das dürfte sich ein wenig in die quere kommen.

von Karl H. (kbuchegg)


Lesenswert?

Rudolph schrieb:
> refresh_cube(); wird sowohl aus der ISR als auch von der Hauptschleife
> aus aufgerufen, das dürfte sich ein wenig in die quere kommen.



Habs  zu spät gesehen: Der Timer ist noch gar nicht aktiv. Im Moment 
sieht es so aus, als ob er nur die Laufzeit der Funktion feststellen 
will.

von Simon T. (narfinus)


Lesenswert?

Herbert schrieb:
> Hallo Narf,
>
> kann es sein, dass du die Variable "level" nicht hochzählst.
> Im Original wird sie im Funktionsaufruf bedient, bei dir meines
> Erachtens nicht.

Danke, das ist in der Tat falsch. Auf der Suche nach dem Fehler habe ich 
die Zeile wohl versehentlich raus genommen. Allerdings ändert dies 
nichts am eigentlichen Zeitproblem da "level" nur beschreibt welche 
Daten rausgeschickt werden sollen.

Karl Heinz schrieb:
> EIne Operation 1<<z willst du nicht haben, weil sie der Compiler in eine
> SChleife auflösen muss. Da muss man noch was anderes finden.

Das sollte eigentlich das simple Bitverschiebung sein. Einfach den Pin z 
des Ports setzen.

> Auch ist die Ausgabe auf SPI noch suboptimial. Ein wesentlicher Punkt
> bei SPI ist es ja, dass SPI alleine läuft. D.h. während das Byte
> rausgetaktet wird, kann die CPU bereits was anderes machen. Wenn du aber
> nach dem STart des raustaktens durch Zuweisung an SPDR auf die
> Fertigstellung dieses Raustaktens wartest, dann verschenkst du diese
> Möglichkeit.

Darüber habe ich mir auch Gedanken gemacht. Um den Prozessor nicht 
ständig auf die SPI warten zu lassen bleibt in meinen Augen nur wieder 
die Interrupt-Funktion. Ein Interrupt alle 16 Clock-Zyklen würde aber 
wohl kaum möglich sein.

> Summa-summarum sind das noch ein paar Ansatzpunkte. Sie erklären aber
> IMHO nicht, warum der ganze Vorgang sich im Millisekundenbereich
> abspielt.
>
>
> Den Optimizer hast du auf -Os stehen?

Gute Frage. Ich überprüfe dies sobald ich zu Hause bin.


Karl Heinz schrieb:
> i muss kein int sein. Du zwingst den µC hier zu 16 Bit Arithmetik, die
> du gar nicht brauchst. Ein uint8_t tuts auch.

Gut erkannt. Da hab' ich wohl in alte Schemeta verfallen ;)


Sind die Arduiono-Standard-Funktionen irgendwo in ihren 
C-Implementierung einsehbar? Mich würde interessieren wie SPI.transfer() 
intern umgesetzt ist.

Danke euch allen für eure Hinweise, sobald ich zu Hause bin setze ich 
sie um und messe erneut.


Grüße
Narf

Karl Heinz schrieb:
> Habs  zu spät gesehen: Der Timer ist noch gar nicht aktiv. Im Moment
> sieht es so aus, als ob er nur die Laufzeit der Funktion feststellen
> will.

Exakt ;)

von Sebastian W. (wangnick)


Lesenswert?

Simon T. schrieb:
> Sind die Arduiono-Standard-Funktionen irgendwo in ihren
> C-Implementierung einsehbar? Mich würde interessieren wie SPI.transfer()
> intern umgesetzt ist.

https://github.com/arduino/Arduino/tree/master/libraries/SPI

LG, Sebastian

von Alex W. (a20q90)


Lesenswert?

Simon T. schrieb:
> Arduino schneller als C?

Arduino verwendet C!
Aber da C ja nicht auf dem uC läuft (genausowenig wie Asm, Basic, 
Pascal)...

: Bearbeitet durch User
von Leo C. (rapid)


Lesenswert?

Simon T. schrieb:
> Karl Heinz schrieb:
>> i muss kein int sein. Du zwingst den µC hier zu 16 Bit Arithmetik, die
>> du gar nicht brauchst. Ein uint8_t tuts auch.
>
> Gut erkannt. Da hab' ich wohl in alte Schemeta verfallen ;)

Welche Compilerversion benutzt Du denn?
Integer Promotion wäre auch mein Tip gewesen. Aber der avr-gcc 4.1.8 
optimiert alles gnadenlos weg. Die (noch) nicht benutzte Variable level 
sowieso, aber auch i in der for-Schleife. Ich komme auf ca. 800 Takte 
(oder weniger) pro Durchlauf, also ca. 100us.

Momentan sehen die for-Schleifen so aus:
1
        for(int i=level; i < level+8; i++) {
2
            SPDR = red0[i];
3
            while(!(SPSR & (1<<SPIF)));
4
        }
5
Ergebnis:
6
.L9:
7
  ldi r30,lo8(red0)
8
  ldi r31,hi8(red0)
9
.L15:
10
  ld r24,Z+
11
  out 0x2e,r24
12
.L13:
13
  in __tmp_reg__,0x2d
14
  sbrs __tmp_reg__,7
15
  rjmp .L13
16
  ldi r24,hi8(red0+8)
17
  cpi r30,lo8(red0+8)
18
  cpc r31,r24
19
  brne .L15

3 * (2 Takte Pointer laden + 8 * (3 T Wert laden und out + 16 T 
Schieberegister + 5 T Schleifenende-Test)))
= Ungefähr ca. 600 Takte.


Du könntest "warten auf SPDR leer" und Ausgabe vertauschen, um die 
Wartezeit sinnvoll zu nutzen:
1
        for(int i=level; i < level+8; i++) {
2
            while(!(SPSR & (1<<SPIF)));
3
            SPDR = red0[i];
4
        }
5
Ergebnis:
6
.L9:
7
  ldi r30,lo8(red0)
8
  ldi r31,hi8(red0)
9
.L13:
10
  in __tmp_reg__,0x2d
11
  sbrs __tmp_reg__,7
12
  rjmp .L13
13
  ld r24,Z+
14
  out 0x2e,r24
15
  ldi r24,hi8(red0+8)
16
  cpi r30,lo8(red0+8)
17
  cpc r31,r24
18
  brne .L13

Noch besser wirds so:
1
   ... uint8_t c; 
2
   ...
3
        for(int i=level; i < level+8; i++) {
4
            c = red0[i];
5
            while(!(SPSR & (1<<SPIF)));
6
            SPDR = c;
7
        }
8
Ergebnis:
9
.L9:
10
  ldi r30,lo8(red0)
11
  ldi r31,hi8(red0)
12
.L15:
13
  ld r24,Z+
14
.L13:
15
  in __tmp_reg__,0x2d
16
  sbrs __tmp_reg__,7
17
  rjmp .L13
18
  out 0x2e,r24
19
  ldi r24,hi8(red0+8)
20
  cpi r30,lo8(red0+8)
21
  cpc r31,r24
22
  brne .L15

Unmittelbar nachdem das SPI-Schieberegister frei wird, wird es auch neu 
geladen. In den 16 Takten, die das SR beschäftigt ist, wird der nächste 
Wert aus dem RAM geholt, und auf Schleifenende getestet, und es bleiben 
sogar noch ein paar Takte Luft.

von Peter D. (peda)


Lesenswert?

Wenns schnell sein muß, nimm die UART als SPI. Dann hast Du einen 
Sendpuffer, kannst also schon vorher das nächster Byte reinstellen.

von Falk B. (falk)


Lesenswert?

@ Simon T. (narfinus)

>Das sollte eigentlich das simple Bitverschiebung sein. Einfach den Pin z
>des Ports setzen.

macht man auf dem AVR besser so.

http://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Schiebeoperationen

>Darüber habe ich mir auch Gedanken gemacht. Um den Prozessor nicht
>ständig auf die SPI warten zu lassen bleibt in meinen Augen nur wieder
>die Interrupt-Funktion.

Nö. Man muss nur die Dinge in der richtigen Reihenfolge tun.
1
    for (i=0; i<24; i++) {
2
       tmp = gs_data[i];
3
       while(!(SPSR & (1<<SPIF))); 
4
       SPDR = tmp;
5
    }
6
    while(!(SPSR & (1<<SPIF)));

Damit wird nach dem Schreiben der Daten ins SPI die CPU damit 
beschäftigt, neue Daten aus dem RAM in ein Register zu lesen. Damit 
laufen SPI und CPU paralell und die CPU muss nur noch minimal warten, 
wenn die Adressberechnung und das Daten lesen weniger als 16 Takte 
gebraucht haben.

>Ein Interrupt alle 16 Clock-Zyklen würde aber
>wohl kaum möglich sein.

Das ist vollkommen unsinnig.

>> Den Optimizer hast du auf -Os stehen?

>Gute Frage. Ich überprüfe dies sobald ich zu Hause bin.

DAS ist entscheidend für _delay_us();

http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Warteschleifen_.28delay.h.29

von Simon T. (narfinus)


Angehängte Dateien:

Lesenswert?

Okey, das ist mir jetzt peinlich...

Auf meinem alten Oszi war der Punkt vor der Millisekunden-Anzeige 
verblasst. Ich habe 0.1ms gemssen und 1ms abgelesen ...

Nicht desto trotz bin ich euch für die ganzen Anregungen unglaublich 
dankbar. Der Hinweis, die Wartezeit mit dem Laden des nächsten Wertes zu 
nutzen war Gold wert. Damit habe ich nochmal 40us gewonnen und bin nun 
mit 75us dem theoreischen Optimum von 48us deutlich näher.

Der überarbeitete Code befindet sich im Anhang. Falls ihr noch 
Vorschläge hat, immer her damit ;)

Fürs Protokoll:
Compilerversion: 3.4.2 gcc
Optimization Level: -02 (ursprünglich 01, ändert aber nichts)

von Falk B. (falk)


Lesenswert?

@ Simon T. (narfinus)

>Der überarbeitete Code befindet sich im Anhang. Falls ihr noch
>Vorschläge hat, immer her damit ;)

Hier fehlt ein Warten auf das Ende der Übertragung. Du setzt das Chip 
Select mitten drin!
1
  while(!(SPSR & (1<<SPIF)));
2
  SPDR = 1<<anode[z];
3
  while(!(SPSR & (1<<SPIF)));   // das fehlte!!!
4
  
5
  //all drivers stored their data. Generate pulse for sending data to the driver-outputs
6
  SPI_PORT |= (1<<SS);
7
  _delay_us(5);                // brauchst du nicht, die ICs sind deutlich schneller als du denkst
8
  SPI_PORT &= ~(1<<SS);

von Leo C. (rapid)


Lesenswert?

Simon T. schrieb:
> Compilerversion: 3.4.2 gcc

Ist das die Ausgabe von avr-gcc -v
?

> Optimization Level: -02 (ursprünglich 01, ändert aber nichts)
-Os ist ein Versuch wert. Kann durchaus schneller werden.

von Simon T. (narfinus)


Angehängte Dateien:

Lesenswert?

Falk Brunner schrieb:
> Hier fehlt ein Warten auf das Ende der Übertragung. Du setzt das Chip
> Select mitten drin!

Autsch. So kann das nix werden. Danke für den Hinweis!


>> Compilerversion: 3.4.2 gcc
> Ist das die Ausgabe von *avr-gcc -v*

Ich bin mir nicht ganz sicher was du meinst: Hier der Build-Aufruf mit 
allen Parametern. Hoffe das beantwortet deine Frage.

"E:\Atmel Toolchain\AVR8 
GCC\Native\3.4.2.939\avr8-gnu-toolchain\bin\avr-gcc.exe" 
-funsigned-char -funsigned-bitfields -DDEBUG  -O2 -ffunction-sections 
-fdata-sections -fpack-struct -fshort-enums -g2 -Wall -mmcu=atmega644 -c 
-std=gnu99 -MD -MP -MF "LED-Controller.d" -MT"LED-Controller.d" 
-MT"LED-Controller.o"   -o "LED-Controller.o" ".././LED-Controller.c"


> -Os ist ein Versuch wert. Kann durchaus schneller werden.

Ebenfalls getestet. Kein messbarer Unterschied zu sehen.


Ich habe gerade die refreshCube()-Funktion im AVR-Simulator nochmal 
durchlaufen lassen und festgestellt dass sich ihre Durchlaufzeit nicht 
konstant ist sondern sich 8 Aufrufe lang verdoppelt und dann wieder beim 
geringsten Wert anfängt (siehe Anhang). Momentan bin ich mir nicht ganz 
sicher ob es ein Bug oder Feature daher schlafe ich erstmal darüber.

Danke nochmals für das tolle Feedback!

von c-hater (Gast)


Lesenswert?

Simon T. schrieb:

uint8_t anode[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};  //Current 
anode

[...]

  SPDR = 1<<anode[z];


Das ist doch ein ziemlicher Unsinn. Wennschon Lookuptabelle mit 
vorberechneten Werten, dann muß die Operation, die vorberechnet wurde 
natürlich verschwinden, sonst kommt Müll raus, nämlich in 5/8 der Fälle 
schlicht eine 0. Also richtig:

  SPDR = anode[z];

Aber mal abgesehen davon, wäre es viel sinnvoller, z einfach statt mit 
++ mit << zu behandeln. Die Operation kostet nur einen einzigen Takt, 
Index-Increment und Table-Lookup sind sehr viel teurer. Die acht Byte 
RAM für die Tabelle werden obendrein noch eingespart.

Also:

static uint8_t z = 0x01;

bleibt so

[...]

und dann statt

  SPDR = 1<<anode[z];

[...]

  if(z<8)
  {
     Z++;
  }
  else
  {
    z=0;
    level =level + 8;
  }

einfach

  SPDR = z;

  z = z << 1;

  if (z == 0)
  {
     z = 1;
  }



oder natürlich sehr viel eleganter in einer richtigen Sprache:

 lds zreg,z    ; 2
 out SPDR,zreg ; 1
 lsl zreg      ; 1

 brne done     ; 1  2
 ldi zreg,1    ; 1

done:
 sts z,zreg    ; 2
               ;--
               ; 8

Wo man sehr schön sehen kann, daß das ganze Z-Handling in konstant 8 
Takten zu erledigen ist. Noch schneller geht es, wenn ein Register frei 
ist, um z dauerhaft zu halten, dann halbiert sich das nochmal, weil die 
teueren Lade- und Speicheroperationen wegfallen.

Übrigens kann der ganze Code noch etliche algorithmische Verbesserungen 
vertragen, das ist alles ziemlich suboptimal. Wenn ich mich taktmäßig 
nicht verzählt habe, ist es durchaus möglich, den SPI konstant so 
schnell zu beschicken, wie er die Daten überhaupt entgegennehmen kann.

Allerdings: in C geht das entweder garnicht oder es kommt etwas sehr 
unleserliches heraus, das nur noch rein formal C ist. Besser gleich in 
Asm schreiben.

von Peter D. (peda)


Lesenswert?

Wenn die Ausgabe schnell sein soll, dann schreib doch schon alles in der 
richtigen Reihenfolge in ein Array.
Dann sparst Du Dir das ganze Pointer-Rumgerechne im Interrupt.
Und natürlich die UART im SPI-Mode, damit kommen die Bytes lückenlos 
raus:
1
{
2
  uint8_t *ptr = array;
3
  
4
  UCSR1A |= ~(1<<UDRE1);                // clear TX complete flag
5
  for( uint8_t i = 3 * 8; i; i-- ){
6
    while( (UCSR1A & 1<<UDRE1) == 0 );  // buffer empty
7
    UDR1 = *ptr++;
8
  }
9
  while( (UCSR1A & 1<<TXC1) == 0 );     // shift register empty 
10
}

von Karl H. (kbuchegg)


Lesenswert?

c-hater schrieb:

> oder natürlich sehr viel eleganter in einer richtigen Sprache:

Mach dir da mal keinen Kopf drüber.
Die C-Version ist ok.
1
  SPDR = z;
2
3
  z = z << 1;
4
  if (z == 0)
5
    z = 1;

 Compiler sind ja auch keine Trottel.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Muß natürlich:
1
  UCSR1A &= ~(1<<UDRE1);                // clear TX complete flag
heißen.

von Falk B. (falk)


Lesenswert?

@ Karl Heinz (kbuchegg) (Moderator)

>>Besser gleich in Asm schreiben.

> Compiler sind ja auch keine Trottel.

Was man von einigen missionarischen Forumsteilern nicht so leicht 
behaupten kann . . . ;-)

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Falk Brunner schrieb:

>>>Besser gleich in Asm schreiben.
>
>> Compiler sind ja auch keine Trottel.
>
> Was man von einigen missionarischen Forumsteilern nicht so leicht
> behaupten kann . . . ;-)

Tja, im Unterscheid zu eurem heißgeliebten und von euch zum Fast-Gott 
hochstilisierten C-Compiler ist es mir möglich, in schlichtem Asm eine 
Routine zu schreiben, die die SPI-Ausgabe bei diesem Problem durchgehend 
mit Vmax beschicken kann und zwar so, daß der Code, der das tut, lesbar 
bleibt.

Es geht dabei nicht um den offensichtlichen Bug der geposteten C-Routine 
beim Z-Handling (den ich in C korrigiert habe), auch nicht nicht um 
meinen Vorschlag für eine wesentlich effizienteres Handling der 
Z-Geschichte insgesamt (die ich sogar in beiden Sprachen korrekt 
vorgeschlagen habe), sondern um die ganze verschissene Routine 
insgesamt, die vor allem WEGEN der systemimmanenten Nachteile von C so 
erheblich suboptimal ist.

Das war nämlich der springende Punkt der Darstellung in Assembler. Die 
Darstellung der Möglichkeit zum taktgenauen Timing. Das haben die Herren 
Opponenten entweder garnicht begriffen oder bewußt verdrängt (letzteres 
halte ich für sehr viel wahrscheinlicher, ich kenne meine Gegner).

Ich habe allerdings auch keine große Lust, mich von solchen 
Realitätsverweigerern öffentlich herabwürdigen zu lassen. 
Interessanterweise verstößt sogar der Herr Moderator damit gegen die 
Nutzungsregeln des eigenen Forums. Und das sogar in verschärfter Form, 
denn die Fakten stehen gegen ihn und ich bin ziemlich sicher: Er weiß 
das nur zu genau.

Ich bin jedenfalls jederzeit bereit, eine ASM-Routine zu posten, die 
wirklich das tut, was ich behaupte, also in Summe: alle 16 Takte das 
nächste Byte per SPI entsprechend dem in C geposteten Algorithmus. 
Einzige Bedingung:

Zuvor postet einer der Herren eine C-Routine, die das Gleiche zu leisten 
vermag! Sie sollte natürlich noch lesbar und als C erkennbar sein und 
sie muß natürlich portabel sein, denn das ist ja der so überaus wichtige 
Grund, warum man so dringend unbedingt C verwenden sollte...

OK, ich mache es euch etwas einfacher bezüglich der Portabilität: Das 
Zielsystem ist konstant, der Code muß sich einfach nur mit _mehr als 
einem_ C-Compiler mit gleichem Ergebnis übersetzen lassen...


Nimmst du die Herausforderung an, Karl-Heinz? Oder du, Falk? Sagt jetzt 
ja oder schweigt für immer!

von Falk B. (falk)


Lesenswert?

@ c-hater (Gast)

>Tja, im Unterscheid zu eurem heißgeliebten und von euch zum Fast-Gott
>hochstilisierten C-Compiler ist es mir möglich, in schlichtem Asm eine
>Routine zu schreiben, die die SPI-Ausgabe bei diesem Problem durchgehend
>mit Vmax beschicken kann und zwar so, daß der Code, der das tut, lesbar
>bleibt.

Mann, bist DU GUT!!!!

C-Programmierer können das auch, sei es als Inline ASM oder einzelen 
ASM-Funktion/Datei.

>insgesamt, die vor allem WEGEN der systemimmanenten Nachteile von C so
>erheblich suboptimal ist.

Sie erfüllt ihre Funktion, sowohl logisch wie zeitlich.

-> Keine Notwedigkeit für Optimierungen (jenseits des sportlichen 
Ehrgeizes)

>Das war nämlich der springende Punkt der Darstellung in Assembler. Die
>Darstellung der Möglichkeit zum taktgenauen Timing.

Die keiner bestritten hat, die aber in den wenigsten Fällen, nicht mal 
hier, benötigt wird. Selbst eine Videogenerierung geht heute in C!

Beitrag "ATmega8 erzeugt Video in C!"
Beitrag "Video Erzeugung mit LPC213x in C"

Das kann selbst der AVR, wenn man den UART im SPI-Modus nutzt, dank 
Sendepuffer ist taktgenaues Timing nicht nötig!

>Ich habe allerdings auch keine große Lust, mich von solchen
>Realitätsverweigerern öffentlich herabwürdigen zu lassen.

Was EIN Geisterfahrer? NEIN, HUNDERTE!!!

>Interessanterweise verstößt sogar der Herr Moderator damit gegen die
>Nutzungsregeln des eigenen Forums.

Merkst dus noch? Oder ist das deine ganz normale Paranoia?

>Und das sogar in verschärfter Form,
>denn die Fakten stehen gegen ihn und ich bin ziemlich sicher: Er weiß
>das nur zu genau.

Is schon gut . . .

>Ich bin jedenfalls jederzeit bereit, eine ASM-Routine zu posten, die
>wirklich das tut, was ich behaupte, also in Summe: alle 16 Takte das
>nächste Byte per SPI entsprechend dem in C geposteten Algorithmus.

Wie langweilig. Solche Spielchen waren vor 10 Jahen hipp, heute ist das 
Usus.

BTW. Du hast dir noch nie die SPI-Ausgabe WIRKLICH angeschaut, ich meine 
mit dem Oszi. Denn dann wüßtest du, dass der AVR NICHT lückenlos 
SPI-Daten senden kann. Es ist IMMER mindests 1 CPU-Takt Pause zwische 
den Datenpaketen. Mit dem UART im SPI-Modus kann das anders sein, ich 
meine das klassische SPI-Modul.

>Einzige Bedingung:

Keine Lust auf Schattenboxen. Das war in dem Thread keine Sekunde das 
Thema und ist HIER auch gar nicht nötig.

>Nimmst du die Herausforderung an, Karl-Heinz? Oder du, Falk? Sagt jetzt
>ja oder schweigt für immer!

Ich glaube kaum, dass DU das festlegst.

Ausserdem steht die Lösung schon in den Links. Und ein voll portable 
Schreibweise bringt wenig, denn eine andere CPU wie PIC, MSP430 & Co hat 
andere Register und ggf. Logik.

Aber kämpfe ruhig weiter an deiner ASM-Front. Dein Urgroßvater hat das 
gleiche in Stalingrad gemacht, wenn gleich eher unfreiwillig.

von c-hater (Gast)


Lesenswert?

Falk Brunner schrieb:

> Aber kämpfe ruhig weiter an deiner ASM-Front. Dein Urgroßvater hat das
> gleiche in Stalingrad gemacht, wenn gleich eher unfreiwillig.

Tja, irgendwer muß ja die für diverse Zielsysteme optimierten libs und 
Codegeneratoren schreiben...

Alles Stalingradkämpfer? Bedauernswerte Ewiggestrige? Uhuh, das werden 
die aber garnicht gerne hören. Diese Leute halten schließlich deinen 
Fetisch am Rande der Benutzbarkeit. (Was auch immer ihr Motiv dafür sein 
mag...)

;o)

von Leo C. (rapid)


Lesenswert?

Simon T. schrieb:
> "E:\Atmel Toolchain\AVR8
> GCC\Native\3.4.2.939\avr8-gnu-toolchain\bin\avr-gcc.exe"

D.h. 3.4.2 war die Version der Atmel AVR Toolchain. Laut deren Release 
Notes ist dort ein avr-gcc Version 4.7.2 drin. Mit 4-er Versionen bis 
4.6 würdest Du wahrscheinlich wesentlich schlechtere Ergebnisse 
bekommen. Wenn Du in einem Windows Kommandozeilenfenster folgendes 
eingibst:
1
"E:\Atmel Toolchain\AVR8 GCC\Native\3.4.2.939\avr8-gnu-toolchain\bin\avr-gcc.exe" -v
bekommst Du das, was ich wissen wollte.


Die Klemmschwester hat in Ihrem Eifer noch einen kleinen Fehler 
übersehen, den Du aber inzwischen sicher auch selber gefunden hast:
1
  if(z<8)
2
  {
3
     Z++;
4
  }
5
  else
6
  {
7
    z=0;
8
    level =level + 8;
9
  }
Wahrscheinlich sollte z (klein) an der Stelle nicht bis 8 laufen. 
Vielleicht besser so oder ähnlich:
1
  if(++z >= 8) {
2
    z=0;
3
    level =level + 8;
4
  }

von Falk B. (falk)


Lesenswert?

@ c-hater (Gast)

>Tja, irgendwer muß ja die für diverse Zielsysteme optimierten libs und
>Codegeneratoren schreiben...

Aber das bist ganz sicher nicht du, wenn du nicht mal zwischen Anwendung 
und Lib unterscheiden kannst.

>Alles Stalingradkämpfer? Bedauernswerte Ewiggestrige?

Könner, die wissen wo ASM angebracht ist und wo nicht.

von Simon T. (narfinus)


Lesenswert?

So, da bin ich wieder.

Ich danke euch allen für die guten Verbesserungsvorschläge. Was die 
Assembler-Umsetzung angeht halte ich mich jedoch für absolut 
unqualifiziert Routinen zu entwickeln die denen des C-Compilers 
überlegen sind. Des Weiteren ist das Setzen der Schieberegister nur ein 
Aspekt des LED-Cubes, d.h. ich habe noch genug andere Baustellen. Womit 
wir auch schon beim Grund sind weshalb ich auf eine UART-Umsetzung 
verzichten möchte. Diese Schnittstelle soll im kompletten System dazu 
dienen, LED-Daten vom PC aus entgegen zu nehmen (ein möglicher 
Betriebsmodus).

Nochmals danke für die ausgezeichneten Ratschläge. Wer nun persönlich 
welche Progammmiersprache bevorzugt ist doch Geschmackssache. Jede hat 
ihre Vor- und Nachteile.

@Leo C.

"E:\Atmel Toolchain\AVR8 
GCC\Native\3.4.2.939\avr8-gnu-toolchain\bin\avr-gcc.exe" -v
Using built-in specs.
COLLECT_LTO_WRAPPER=e:/atmel\ toolchain/avr8\ 
gcc/native/3.4.2.939/avr8-gnu-toolchain/bin/../libexec/gcc/avr/4.7.2/lto 
-wrapper.exe
Target: avr
Configured with: 
/data2/home/toolsbuild/jenkins-knuth/workspace/avr8-gnu-toolchain/src/gc 
c/configure 
LDFLAGS=-L/home/toolsbuild/jenkins-knuth/workspace/avr8-gnu-toolchain/av 
r8-gnu-toolchain-win32_x86/lib  CPPFLAGS= --target=avr 
--host=i686-pc-mingw32 --build=x86_64-pc-linux-gnu 
--prefix=/home/toolsbuild/jenkins-knuth/workspace/avr8-gnu-toolchain/avr 
8-gnu-toolchain-win32_x86 
--libdir=/home/toolsbuild/jenkins-knuth/workspace/avr8-gnu-toolchain/avr 
8-gnu-toolchain-win32_x86/lib  --enable-languages=c,c++ --with-dwarf2 
--enable-doc --disable-shared --disable-libada --disable-libssp 
--disable-nls 
--with-mpfr=/home/toolsbuild/jenkins-knuth/workspace/avr8-gnu-toolchain/ 
avr8-gnu-toolchain-win32_x86 
--with-gmp=/home/toolsbuild/jenkins-knuth/workspace/avr8-gnu-toolchain/a 
vr8-gnu-toolchain-win32_x86 
--with-mpc=/home/toolsbuild/jenkins-knuth/workspace/avr8-gnu-toolchain/a 
vr8-gnu-toolchain-win32_x86  --enable-win32-registry=avrtoolchain 
--with-pkgversion=AVR_8_bit_GNU_Toolchain_3.4.2_939 --with-bugurl=h
Thread model: single
gcc version 4.7.2 (AVR_8_bit_GNU_Toolchain_3.4.2_939)

Ich hoffe das dir das hilft, denn ehrlich gesagt habe ich momentan keine 
Ahnung was du meinst ;).

Grüße
Narf

von Bastler (Gast)


Lesenswert?

Ich würde nicht bestreiten, das Programmteile mit solchen Anforderungen 
wie Taktgenau am besten (und wohl auch einzig) in ASM zu schreiben sind. 
Nur, wenn man 32..128kb mit weiterem sinnvollem Code füllen will, dann 
ist das via GCC einfacher und schneller. Die Optimierungs-Algorithmen 
mögen viel simpler sein, aber die Geschwindigkeit und Ausdauer mit der 
der GCC sie runterspult sind manuell nicht erreichbar. Nicht immer nur 
Einzelteile anschauen. BTW, ich habe schon AVR-ASM gesehen, wo ein 
Switch über ein Byte als 256Worte lange Sprungtabelle gebaut war. Zwar 
konstante Laufzeit, aber riesig und kaum wartbar. Sowas lass ich gerne 
den Compiler sortieren. Ein Werkzeugmacher benutzt ja oft eine 
Fräsmaschine, obwohl er auch feilen gelernt hat.

von Karl H. (kbuchegg)


Lesenswert?

c-hater schrieb:

> Nimmst du die Herausforderung an, Karl-Heinz?

Welche Herausforderung?
Eine SPI Routine zu schreiben?
Das ist doch pipifax.

Machen wir doch Nägel mit Köpfen! Schreieben wir ein richtiges Programm! 
Schön umfangreich und mit schöner Funktionalität.

Und dann sehen wir mal, wessen Programm schneller fertig ist und wessen 
Programm weniger Fehler aufweist. Denn das ist es, was letzten Endes in 
der Mehrheit der Masse aller Programme zählt.

Ich bräuchte zb eine Regelung für meine Gasthermen-Heizung (2-Punkt 
Regler genügt). Komplett mit mindestens 3 Heizprogrammen (natürlich 
konfigurierbar), die Tageszeit und Wochentag abhängig das Heizprogramm 
abspulen, Bedienung am Gerät mit Drehencoder und LCD, Temperaturaufnahme 
mit einem DS1820, integriert mit einem kleinen HTML-Server auf ENC28J60 
Basis, der mir eine Fernbedienung bzw. Abfrage des Status aus dem Web 
erlaubt. Im Moment studiere ich gerade den Radig-Webserver, aber mit dem 
bin ich nicht so glücklich.

Na, was ist?

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

> denn die Fakten stehen gegen ihn

Die Fakten sind, dass hier kein Mensch taktgenaues Timing benötigt. 
Schnell genug ist völlig ausreichend.
So siehst aus.

: Bearbeitet durch User
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.