Hallo zusammen,
ich versuche gerade für eine größere Projekt-Idee ein SPI-Daisy-Chain
mit ATTinyX5 (also 25/45/85, je nach entgültiger Codegröße aufzubauen).
Dabei sollen zum Schluss ca. 140 Bausteine in Reihe geschalten werden.
Dabei Benötige ich PB3 als Ausgang und PB4 als analogen Eingang für mein
Projekt und habe somit alle 3 Pins der USI Schnittstelle frei für die
Kommunikation. Daher hatte ich folgende Idee:
* Dass CLK Signal wird vom SPI Master ( Arduino ) an alle Slaves
verteilt
* Jeder Slave ist DO -> DI zu nächsten verbunden
* Der SPI-Master ist MOSI -> DI vom ersten Slave und DO von letzten
Slave geht zu MISO vom SPI-Master
Dazu kommt, dass jedes Datenpaket was übertragen wird, aus 4 bytes
besteht. D.h. das SPI-Schieberegister in jedem Attiny muss 3 bytes
zwischenbuffern, bevor es wieder auf den Ausgang geht. Des weiteren habe
ich keine Resourcen für eine Chip-Select Leitung, weshalb ich mir
folgende Idee überlegt habe. Ich nutze TIMER1 des AVR mit einem Überlauf
aller ca. 1ms um die Daten aus dem SPI-Buffer zunehmen und neue
einfügen. Falls in der Zwischenzeit ein SPI Interrupt erfolgt wird der
Counter des Timers auf 0 gesetzt, was den Interrupt verhindert. So
werden 1ms nach dem das letzte Datenpaket versendet wurde, automatisch
die Daten übernommen und neue bereitgestellt. So weit die Idee. Ich
habe das nun mit 3 ATtiny45 die mit der 16MHz PLL Clock laufen und einem
Ardunino Uno als Master mit den angehängten Codes ausprobiert. Der
Master wartet nach dem er 12 Byte ( 3 AVRs je 4 Byte ) immer 40ms.
Zu erwarten wäre dass immer die Ausgabe:
1
11 22 33 44 | 11 22 33 44 | 11 22 33 44
erfolgt. Jedoch schleichen sich relativ oft (geschätzt 10% der fälle)
Fehler ein und ich erhalte zum Beipiel:
1
11 22 33 44 | 11 22 33 44 | 11 22 33 44
2
11 22 33 44 | 11 22 33 44 | 11 22 33 44
3
11 22 33 44 | 10 89 11 9A | 25 22 33 40
4
11 22 33 44 | 08 91 19 A2 | 11 22 33 44
5
11 22 33 44 | 04 44 46 68 | 91 22 33 44
6
11 22 33 44 | 11 22 33 44 | 11 22 33 44
7
11 22 33 44 | 11 22 33 44 | 11 22 33 44
Damit kann ich auch davon ausgehen, dass die je 4 Byte vom Master auch
fehlerhaft an den Slaves ankommen. Ich hab das auch mit nur einem AVR
ausprobiert und da fielen mir keine Datenfehler auf. Ab 2 gibt es
Probleme.
Nach dem ich da nun schon seit Weihnachten dran frickel und diese Fehler
nicht los werde, hoffe ich dass mir hier jemand helfen kann.
Wäre es nicht einfacher, auf dieses USI und damit das Umspeichern der 4
Bytes zu verzichten und stattdessen alles in Software zu machen?
32-bit-Variable, auf einer Seite rein-, der anderen rausschieben,
getaktet durch INT0? Noch irgendwie auf 32 zählen und, ähnlich wie
bereits vorhanden, durch Timeout überwachen.
Es empfängt jeder AVR 4 Byte und sendet 4 Byte. Also müssen insgesamt n
x 4 Byte vom Master gesendet wurden und gleichzeitig empfangen werden.
Daher hatte sich die Sache mit USI/SPI ganz gut ins Bild der Anwendung
gepasst. Ich hatte schon mal versucht Teile in Software zu
reimplementieren, aber das wird m.M.n zu langsam, da ich das ganze
relativ oft aktualisieren will.
> ... zu langsam ...
Mir scheint, genau das ist beim aktuellen Programm der Fall, offenbar
kommt der 1-ms-Interrupt dem USI dazwischen und verschiebt dessen Bits.
Ich bin der Meinung, ein reines 32-bit-Software-SPI ist auf jeden Fall
schnell genug (allerdings ohne heute abend den Beweis antreten zu
wollen).
Ich hab den Interrupt auch auf 2ms erhöht und weiter und das hat nichts
an dem Problem geändert. Den Wackelkontakt kann ich Ausschliessen, da
alles verlötet ist und ich nach dem feststellen des Problems, alle
Lötstellen noch mal kontrolliert habe.
Dann weiß ich erstmal auch nicht weiter.
Ich habe versucht, das Ganze nachzubilden, allerdings mangels Arduino
wie folgt: 3 ATtiny85 mit Ihrem Original-C-Programm, als Master ein
ATmega in Assembler programmiert - ich sehe hier keine Fehler.
Martin K. schrieb:> Dazu kommt, dass jedes Datenpaket was übertragen wird, aus 4 bytes> besteht. D.h. das SPI-Schieberegister in jedem Attiny muss 3 bytes> zwischenbuffern, bevor es wieder auf den Ausgang geht.
Du hast ganz klar die Funktionsweise von SPI im allgemeinen und des USI
als SPI-Interface im Besonderen nicht wirklich verstanden.
Woran merke ich das? Daran:
> D.h. das SPI-Schieberegister in jedem Attiny muss 3 bytes> zwischenbuffern, bevor es wieder auf den Ausgang geht.
Erstens kann (und braucht) das SPI-Schieberegister (fast) nix puffern.
Der einzige nutzbare Puffer ist eine Kopie des zuletzt empfangenen
Bytes. Mehr Support gibt's nicht in der USI-Hardware.
Zweitens braucht nix "auf Ausgang gehen", denn das Schiebergister ist
die ganze Zeit gleichzeitig Eingang und Ausgang. Wie das für
Schieberegister ganz allgemein so üblich ist.
Die einzige Eingriffsmöglichkeit in das Schieberegister ist, innerhalb
von knapp einem halben SPI-Takt das empfangene Byte im Schieberegister
durch irgendwas anderes zu ersetzen.
Wie kritisch das ist, hängt nur von einer Sache ab: Welche Zahl kommt
raus, wenn du den Systemtakt der Clients durch den SPI-Takt dividierst?
Okay, ich sehe jetzt auch sporadisch Fehler.
Das Problem liegt in diesem 1-ms-Interrupt: füge ich im ATmega nach
jedem 1-Byte-SPI-Transfer ein Warten von 500 us ein, treten keine Fehler
mehr auf.
Gute Nacht & schönen Sonntag.
@c-hater
Mir ist durchaus bewusst, wie ein Schieberegister und der SPI-Bus
funktionieren, das Problem ist halt, dass ich im Daisy-Chain Betrieb ist
und wenn jeder Slave n-Byte am stück empfangen soll, so muss sobald das
SPI Register voll ist, dieses mit einen internen Buffer/Queue getauscht
werden, sodass beim nächsten CLK Impuls die Bytes passend weiter
gereicht werden.
@S. Landolt
Ich habe das ganze bis 350µs und _DIV4 zum laufen bekommen, das Problem
dabei ist, dass die künstliche Pause die effektive Datentransferrate
sehr weit senkt und ich damit für das wo ich hin will (ca. 130 Module in
Reihe) 182ms mit Warten verbringe. Inklusive der Transfers komme ich
damit noch auf maximal 5 komplette Durchläufe beim Datenabrufen. Das
DIV64 hatte ich genutzt, da ich im objdump ausgezählt hatte wieviele
Zyklen der USI Interupt läuft und so fertig ist, bevor das nächste Byte
da ist. Damit wurde _DIV32 sehr knapp und somit habe ich mit DIV64 und
DIV128 probiert.
Ich würde einfach immer 3 Byte am Stück senden und dann eine Pause
machen. Die Slaves erkennen per Timerinterrupt, daß auf SCK nichts los
ist und lesen dann jeder sein Byte. Das Ganze dann 4-mal und eine
größere Pause, damit die Slaves erkennen, daß ein neuer Frame beginnt.
Ob auf SCK seit dem letzten Timerinterrupt Ruhe war, kann man mit dem
Pin-Change Flag erkennen.
Peter D. schrieb:> Ich würde einfach immer 3 Byte am Stück senden und dann eine Pause> machen. Die Slaves erkennen per Timerinterrupt, daß auf SCK nichts los> ist und lesen dann jeder sein Byte. Das Ganze dann 4-mal und eine> größere Pause, damit die Slaves erkennen, daß ein neuer Frame beginnt.> Ob auf SCK seit dem letzten Timerinterrupt Ruhe war, kann man mit dem> Pin-Change Flag erkennen.
Das klingt auch nach möglichen Option. Da werde ich morgen mal schauen,
wie/ob ich das irgendwie umgesetzt bekomme.
Also den USI Overflow interrupt nicht benutzen, da der PCINT ja eine
höher Priorität hat als der USI und somit der PCINT auf SCK den Overflow
vom USI blocken würde?
Du baust da m.M.n. ein Synchronclock/Ripple Data Schieberegister. Die
Clock führst du ja auf alle parallel, aber die Daten werden von Tiny zu
Tiny durchgereicht und sind bald (nach ein oder 2 Tinys) nicht mehr
synchron zur Clock.
Ich würde da vermutlich ähnliches wie I2C benutzen(oder SPI mit
Geräteadresse) und SPI Clock und Data parallel an alle Tinys legen.
> ... bis 350µs ...das Problem dabei ...
Meine 500 us sollten nur klarstellen, dass das Problem in dem
Timer-Interrupt liegt, welcher in eigenem Rhythmus den USI-Interrupt
sperrt/freigibt, ohne zum Master synchron zu sein. Es fehlt eben eine
CS- bzw. SS-Leitung zur Synchronisierung, einen Ausweg hat Peter
Dannegger aufgezeigt.
PS:
Diese 350 bzw. 500 us waren schon so eine Art Synchronisierung, aber
eben nur schlecht, von Verarbeitungszeiten im Mega und den Tinys
abhängig; das muss noch auf die Füsse gestellt werden.
Erstmal vielen Dank für paar Ideen und Denkanstöße wie ich das ganze
realisieren kann. Ich habe heute mal die Idee von @peda umgesetzt und
bin zu einem schnellen und (bisher) zufriedenstellenden Ziel gekommen.
Ich sende nun erst alle ersten Bytes, dann alle zweiten.. usw, und
zwischen den Bytes brauch ich nur eine Pause von 75µs. D.h. bei der
Übertraung von 4 Byte an alle Slaves fügt nur noch 225µs "nutzlose" Zeit
ein. Des weiteren kann ich den SPI-Takt bis auf 4MHz erhöhen.
Anbei der Code für die Slaves. Der Master sendet/empfängt mittels
1
uint8_tslaves=3;
2
3
for(uint8_tj=0;j<4;j++){
4
for(uint8_ti=0;i<slaves;i++){
5
SPI.transfer(&recv[i*4+j],1);
6
}
7
delayMicroseconds(75);
8
}
9
delayMicroseconds(200);
Und ja ich weiß, wenn ich das recv array noch umsortiere, kann ich mir
die innere Schleife auch noch schenken ;-) Aber das kommt in der
weiteren Entwicklung. Jetzt funktioniert erstmal die Datenübertragung
sauber.
Auch auf den dritten Blick: ohne USI-Interrupt, stattdessen ein
Timer-Interrupt mit festen 800 Takten? Wobei der Timer ständig per SCK
resp. INT0 zurückgesetzt wird? Pardon, ich versteh's nicht.
S. Landolt schrieb:> ich versteh's nicht
Das Problem ist doch, daß man ganz genau in 0,5 SCK-Takten dem USI das
Byte unterm Hintern wegändern muß. Also läßt man es ganz sein und den
Master einfach soviele Bytes rausschieben, wie Slaves dran hängen. In
der Pause danach kann dann jeder Slave ganz in Ruhe sein Byte lesen bzw.
schreiben.
Die Slaves müssen nur erkennen, wann die Pause ist und das macht der
Timerinterrupt. Den USI-Interrupt darf man daher nicht verwenden.
Stell Dir das USI einfach als Schieberegister (z.B. 74HC299) vor.
Was für die Funktionsweise vielleicht noch interessant ist. Der
INT0-Interrupt muss nicht für jedes übertragene Bit ausgeführt werden.
Er ist ausreichend, wenn er "oft genug" dass heißt bis spätestens 800
CPU-Takte rum sind einmal ausgeführt ist. Im Falle von 4 MHz SPI Takt,
hätte man auf dem 16 MHz AVR eh nur max. 4 Instructions im
Interrupt-Handler Zeit. Jedoch braucht der Interrupt-Handler für
1
2
TCCR1=TIMER1_...;
3
TCNT1=0;
4
recv=1;
schon 7 Takte ( 2x LDI + OUT, 1x LDI+STS). Dazu kommt noch die
Interrupt-Präambel und das Aufräumen (min. 3 Push und 3 Pop + Clear des
r0, r1 + Reti) Sind wir bei ca. 20 Takten, was bedeuten würde das der
Interrupt nur aller etwa 5 Bit auf dem SPI Bus ausgeführt wird. Dies
genügt um den TIMER oft genug zurück zu setzen und so quasi das
"Latchen" des Schieberegisters nach 800 Takten zu verhindern.
Ich hatte mich beim Ursprungsplan zu sehr an der Idee immer die
zusammengehörigen Bytes zu übertragen festgehalten. Aber da hat man wie
Peter sagte, nur 0,5 SPI Takte Zeit alle Daten in den Slaves zu
reorganisieren, was zu den niedrigen SPI-Takt führte.
Da auf PortB2 auch T0 liegt, könnte man das Zurücksetzen des Timer1 auch
über Timer0.OC0x realisieren, dann hätten die Tinies während der
Übertragung noch Zeit, etwas zu tun.
S. Landolt schrieb:> dann hätten die Tinies während der> Übertragung noch Zeit, etwas zu tun.
Der INT0 könnte sich selbst sperren und der T1 gibt ihn nach 10µs wieder
frei. Dann hat man 160 Zyklen zwischen den Interrupts und die CPU kann
während der Übertragung noch anderes tun.
Oder der T1 prüft alle 10µs, ob das INTF0 gesetzt ist und löscht es.
Weshalb einen externen int benutzten und nicht den usi overflow
interrupt? Wenn der interrupt auftritt, in 80 CLK mittels Timer dann den
Interrupt abarbeiten.
So werden die Bytes ausgeblendet welche für andere UC bestimmt sind und
nur das letzte byte wird verarbeitet. Der Master muss eine Pause nach
der Übertragung einführen.
Chris schrieb:> Weshalb einen externen int benutzten und nicht den usi overflow> interrupt?
Was mir bei dem USI Interrupt aufgefallen ist, ist, dass der USI
Interrupt der am niedrigsten priorisierte ist und so bei einem dummen
Zufall, der TIMER Interrupt dem USI zuvor kommen kann.
Ich bin mittlerweile mit dem, der Frage zu Grunde liegenden Projekt, ein
ganzes Stück weiter gekommen und hab dabei noch einen Bug im Timer-ISR
für das Update des SPI Buffers gefunden, den ich hier mal schnell
dokumentiere, falls es für jemanden anders von Nutzen sein könnte.
Die alte ISR sah wie folgt aus:
1
ISR(TIM1_COMPA_vect){
2
if(recv&&data_in_ptr<4){
3
data_in_buffer[data_in_ptr]=USIDR;
4
USIDR=data_out_buffer[data_in_ptr+1];
5
data_in_ptr++;
6
TCNT1=0;
7
}
8
}
wobei wir recv = 1 annehmen. Diese Variante funktioniert nur solange
jede SPI Nachricht immer 4 Byte lang ist. Will man jedoch flexible
Nachrichtenlängen unterstützen so ist die Routine in
Hallo,
sollte man nicht besser sie Senderoutine losgelöst vom Timer laufen
lassen?
Der Timer sollte nur aller x µs oder ms den Anstoss zum senden geben,
aber nicht selbst senden.
Bei 1 bis 2 Byte könnte man das noch im Timer machen. Erstes Byte wird
sofort gesendet und das 2. liegt im Sendebuffer. Timer ISR wird also
noch nicht blockiert. Wenn man aber längere Daten senden möchte,
blockiert man die Timer ISR, wann man ja generell vermeiden soll.
Idee wäre, Daten in einen Ringbuffer, Timer löst das senden aus,
Senderoutine bedient sich vom Ringbuffer und liest und sendet bis
fertig. Mit Ringbuffer können schon neue Sendedaten berechnet werden
ohne das aktuelle Sendepaket zu verfälschen. Wenn man Zeit hat kann der
Ringbuffer auch wegfallen.