Hallo Controller-Freunde,
im Moment beschäftige ich mich mit der SPI-Ansteuerung eines Attiny828,
den ich als Wandler für zusätzliche 24 AD-Kanäle an meinen SAMD21
Hauptcontroller anbinden möchte. Ich verwende die
Hardware-SPI-Schnittstelle des Attiny als Slave und eine Sercom des
SAMD21 als Master. Grundsätzlich funktioniert die Verbindung bis auf ein
kleines systematisches Problem.
Ich verwende mit beiden Controllern den Interrupt um auf
empfangene/gesendete Bytes zu reagieren. In den Interrupts der beiden
Controller toggle ich einen PIN um den zeitlichen Verlauf im Interrupt
erfassen zu können. Im Moment betreibe ich die Schnittstelle bei 1,5MHz,
ab 2MHz macht der Attiny wie im Handbuch erwähnt nicht mehr mit.
Um im Attiny als Tranceiver Bytes als Antwort zurückzuschicken, warte
ich auf den „Byte-Empfangen“-Interrupt, werte das empfangene Byte aus
und kopiere dann ein entsprechendes Byte zurück in den Puffer. Dieser
ganze Vorgang dauert im Interrupt jedoch recht lange, da ja zunächst
alle Register gesichert werden müssen. Die gemessene Zeit im Interrupt
liegt bei ca. 5µS.
Zusätzlich habe ich noch einen Timer im Hintergrund laufen, der
verschiedene Vorgänge steuert. Sobald das Slave-Signal kommt, kann ich
die weniger wichtigen Prozesse im Timer abschalten, dennoch bleiben auch
hier 7 µS als Interrupt-Zeit stehen. Kommt jetzt ein Timer-Interrupt
ungünstig daher, verzögert er, auch wegen seiner höheren Priorität den
SPI-Interrupt. Ich muss also im Master nach jedem übertragenen Byte eine
Mindestwartezeit einfügen, bis das nächste Byte übertragen werden darf.
Damit komme ich jedoch nur auf etwa 50% der möglichen Übertragungsrate.
Eine Möglichkeit sehe ich darin, den Interrupt im Attiny einfach nicht
zu verlassen, solange ein Slave-Select-Signal anliegt. Ich fühle mich
aber nie wohl, wenn ich den Prozessablauf so blokiere.
Gibt es einen richtigen Weg, den ich nur nicht sehe, oder wird das
üblicherweise so gehandhabt?
Prüfe, ob Du den Timer im Polling betreiben kannst, anstelle des
Timer-Interrupts. Wenn ein Jitter erlaubt ist, dann kann das
funktionieren und der SPI-Interrupt wird nicht verzögert. Wie schnell
läuft Dein Tiny jetzt? Und wieso soll bei 2Mhz-SPI-Frequenz Schluss
sein? Der Tiny schafft bei 20Mhz immerhin 10Mhz SPI-Takt im Double
Speed.
Hallo Knut,
der Tiny ist ein 828 ohne Quarz, der geht nur bis 8MHz. SPI geht
zuverlässig nur bis 1/4 des Prozessortaktes. Den Timer abschalten würde
ich ungern, da ich dadurch insgesamt Takte verlieren würde. Da nehme ich
dann doch lieber die langsame Übertragungsrate in Kauf. Zeitlich könnte
ich auch im SPI-Interupt zunächst die komplette Übertragung blockierend
erledigen, d.h. ich würde einfach im Interrupt bleiben, bis Slave-Select
wieder freigegeben wird. Das kommt mir aber sehr unschön vor, ich möchte
eigentlich nicht blockierend programmieren. Eine komplette Übertragung
dauert bei 50-100 Bytes ca. 1-2 ms. Wenn ich die Wartezeiten zwischen
den Bytes reduzieren könnte, auch unter 1 ms. Im speziellen Fall könnte
man eventuell auch das Timing synchronisieren. Ich wüsste jedoch gerne,
wie das allgemein gehandhabt wird, wenn im System mehrere Interrupts
auftreten können. Zudem wüsste ich gerne, wie man schnell vom Slave zum
Master die Daten bekommt, wenn man erst im Interrupt die Antwort
schreiben kann. Dadurch ergibt sich ja alleine schon ein großes Delay.
Knut B. schrieb:> Der Tiny schafft bei 20Mhz immerhin 10Mhz SPI-Takt im Double> Speed.
Double-Speed geht nur im Master-Mode, nicht als Slave. Der 828 geht nur
bis 8Mhz.
Stromverdichter schrieb:> der Tiny ist ein 828 ohne Quarz, der geht nur bis 8MHz.
Nein, geht er nicht. Über OSCCAL kommst Du auf rund 14Mhz, je nach Chip.
Stromverdichter schrieb:> SPI geht> zuverlässig nur bis 1/4 des Prozessortaktes.
Nein. Als Master und im DoubleSpeed bis fCPU/2.
Stromverdichter schrieb:> Den Timer abschalten würde> ich ungern, da ich dadurch insgesamt Takte verlieren würde.
Sollst ja nicht abschalten, sondern nur umorganisieren -> Progamm
ändern.
Hallo Knut,
über OSCAL den Takt zu erhöhen, kommt mir doch sehr experimentell vor.
Da kann ich ja nie wissen, ob mit der nächsten Charge Chips von Atmel
das noch so funktioniert. Die Verdoppelung des SPI-Taktes geht nur als
Master, der Tiny ist hier jedoch nur der Slave.
Umorganisieren des Timers ist eine tolle Idee, nur wie?
Der Timer hat unglücklicherweise eine höhere Priorität wie der SPI. Das
lässt sich beim Tiny imho auch nicht ändern. Den Timer benötige ich für
verschiedene weitere Funktionen von Takt für Sensoren über langsame PWM
bis zu einfachen Zeitmessungen. Daher kann ich den eigentlich nicht
abschalten ohne die Messwerte zu verfälschen.
Stromverdichter schrieb:> über OSCAL den Takt zu erhöhen, kommt mir doch sehr experimentell vor.
Der Tiny ist bei 5V bis 20MHz spezifiziert.
Stromverdichter schrieb:> der Tiny ist hier jedoch nur der Slave.
Aha. Eine wichtige Information!
Stromverdichter schrieb:> Der Timer hat unglücklicherweise eine höhere Priorität wie der SPI.
Beim Tiny gibt es keine wirklichen Prioritäten. Mehrfach auflaufende
Interrupte werden in der Priorität ihres Vektors abgearbeitet. Hier bei
Deinem Ansatz kann aber jeder Interrupt jeden verzögern, sowohl das SPI
den Timer, als auch umgekehrt. Wenn SPI "Ultrawichtig" ist, dann darf es
auf dem Controller auch nur einen Interrupt geben. Alles andere muss
gepollt werden.
Ich betreibe den Tiny mit 3,3V, da er wie bereits im ersten Post
geschrieben als Slave am SAMD21 hängt. Da steht also die wichtige Info
schon. Da er am SAMD21 hängt, kann man sich die 3,3V ja gut erschließen.
Den Tiny kannst du zuverlässig bis 8,8MHz betreiben, alles darüber ist
nicht mehr sicher, speziell wenn du das EEPROM oder FLASH schreibend
nutzen möchtest.
Den Timer zu pollen ist auch nicht so prickelnd. Ich hatte nur gehofft,
ich hätte etwas übersehen oder nicht richtig verstanden. Z.B.
Möglichkeiten wie preloading des SPI-Puffer oder solche Dinge.
Das mit den Interrupts habe ich aus dem Datenlatt genauso wie du
verstanden. Effektiv macht die Priorität bei 2 Interrupts dann wohl
keinen Unterschied, solange beide zu Lebzeiten aus ihrem Thread wieder
herauskommen. Man könnte eventuell den Timer-Interrupt unterbrechbar
machen, sozusagen die Interrupt-Verwaltung direkt im Timer wieder
aktivieren. Ob das Sinn macht kann ich jetzt aber garnicht einschätzen,
das kommt mir gefährlich vor.
Stromverdichter schrieb:> preloading des SPI-Puffer oder solche Dinge.
Du kannst im Datenregister die Daten schon eintragen, bevor der Master
diese abholt. Eigentlich unmittelbar im SPI-Complete Interrupt als erste
Aktion. Danach kannst Du das Datenregister lesen und gucken, was der
Master wollte. Wenn sich das mit Deinem Restprogramm vereinbaren lässt.
Knut B. schrieb:> Was wäre denn, wenn der Tiny der SPI-Master ist, dann könnte er am SPI> doppelt so schnell arbeiten.
daran hatte ich auch schon gedacht. Nur wird so einiges vom D21 aus
gesteuert, was man ungern nur pollen möchte. Klar könnte ich auch on the
fly über den SS von Master wieder auf Slave umschalten. Das sind aber
alles nur Umwege um das grundsätzliche Problem zu lösen. Der Tiny wird
auch über die Cortexe programmiert, er dient auch als EEPROM Speicher
und soll eigentlich immer erreichbar bleiben.
Wen der Tiny im Interrupt antwortet, dann natürlich erst für das nächste
Byte, dass der Master sendet. Es ist ja ein Ringpuffer, der im gleichen
Takt die Bytes vom Master zum Slave und gleichzeitig umgekehrt schiebt.
Das eigentliche Problem ist, dass der Tiny schon einige Zeit benötigt,
um die Register im Interrupt auf den Stack zu sichern. Sicher könnte man
das mit den entsprechenden Assembler-Kenntnissen optimieren, das ist mir
aber noch nicht gegeben. Zusätzlich muss ich ja berücksichtigen, dass
eventuell im gleichen Zyklus auch der Timer gefeuert haben kann, womit 2
Interrupts die Daten verzögern. Der Master kann das aber nicht erkennen.
Ich muss also den worst-case berechnen und die Pausen zwischen einzelnen
Bytes entsprechend dimensionieren, dass auch 2 ungünstige Interrups noch
dazwischenpassen. Geschrieben wird im Slave immer direkt nach dem Lesen,
also direkt im SPI-Interrupt.