PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [ERLEDIGT] SPI Interrupt wird ZU SPÄT ausgelöst nach Sendevorgang



NRicola
03.06.2018, 12:55
Hallo zusammen,

wie am Titel zu erkennen, habe auch ich gerade Probleme mit SPI. Es handelt sich bei mir um einen PIC18, der als SPI-Slave mit SS betrieben wird, interner Clock (16MHz).

Problem: Der interrupt wird viel zu spät ausgelöst: es vergehen 11,5µs zwischen <jetzt müsste er auslösen> und <der Ausgang RD5 wurde umgeschalten>. RD5=0 bleibt für 10µs erhalten.

Umgehe ich die interrupt-Routine und baue in der Main-Routine eine Abfrage des PIR1bits.SSPIF == 1 und schalte dann den Ausgang RD5=0, geschieht das mit einem Verzug von etwa 1,1-2,4µs (Jitter natürlich deutlich stärker). RD5 bleibt ca. 1,5µs auf low.



void spi_init(void) {

INTCON = 0; // Disable all interrupts
PIE1 = 0;
PIR1 = 0;
// ----------------------------
SSPCON1bits.SSPEN = 0; // SSPCON1[5]; Allow Programming of serial port
SSPCON1bits.CKP = 1; // SSPCON1[4]; Clock Polarity Select bit; Idle state for clock is a HIGH level
SSPCON1bits.SSPM3 = 0; // SSPCON1[3]; Synchronous Serial Port Mode Select bits
SSPCON1bits.SSPM2 = 1; // SSPCON1[2]; [3-0] 0100 = SPI Slave mode, clock = SCK pin, SS pin control enabled
SSPCON1bits.SSPM1 = 0; // SSPCON1[1];
SSPCON1bits.SSPM0 = 0; // SSPCON1[0];
// ----------------------------
SSPSTATbits.SMP = 0; // SSPSTAT[7]; Input data sampled at middle data output time
SSPSTATbits.CKE = 0; // SSPSTAT[6]; Transmit occurs on active to idle clock state
// ----------------------------
PIE1bits.SSPIE = 1;
PIR1bits.SSPIF = 0; // Master Synchronous Serial Port Interrupt Flag bit (must be cleared by Software)
// 0 = Waiting to transmit/receive
INTCONbits.PEIE = 1; // Enable Peripheral interupts
INTCONbits.GIE = 1; // Global Interupt enable
SSPCON1bits.SSPEN = 1; // End programming and Start serial port
}

ISR:


void interrupt isr(void)
{
if (PIR1bits.SSPIF == 1)
{
spi_reg_addr=SSPBUF;
SSPBUF=0x00;
RD5=0;
PIR1bits.SSPIF = 0;
}
}


Main:


void main(){
pic_init();
spi_init();
while(1)
{
PORTDbits.RD5=1;
}
}

Woran kann es liegen, dass die Interrupt-Routine so spät auslöst bzw. wie kann man es beschleunigen?

Grüß,
NRicola

Siro
03.06.2018, 20:29
Hallo NRicola,
Du hast uns nicht verraten welchen PIC der 18er Serie Du benutzt.
Es gibt beim 18F252 z.B. ein Problem im SPI Interface im SlaveMode
Für deinen PIC gibt es ganz sicher auch einen "ERRATA" Sheet, weil es gibt keine funktionierenden PICs...;)
Spass muss sein, ich liebe die Dinger trotzdem.
Schau mal dort rein, eventuell hilft es dann weiter.

hier mal der Hinweis vom PIC18F252:

Module: MSSP (SPI, Slave Mode)
In its current implementation, the SS (Slave
Select) control signal, generated by an external
master processor, may not be successfully recognized
by the PIC® microcontroller operating in
Slave Select mode (SSPM3:SSPM0 = 0100). In
particular, it has been observed that faster
transitions (those with shorter fall times) are more
likely to be missed than than slower transitions.
Work around
Insert a series resistor between the source of the
SS signal and the corresponding SS input line of
the microcontroller. The value of the resistor is
dependent on both the application system’s
characteristics and process variations between
microcontrollers. Experimentation and thorough
testing is encouraged.
This is a recommended solution. Others may exist.


zudem würde ich mir mal den Assemblercode von der Interruptfunktion ansehen.
Ich weis nicht was der Compiler dort für einen "overhead" für Register sichern usw. reinbaut.
Bei einer Free Version von Microchip hab ich hier schon haarsträubenden code erlebt....
Wenn er erstmal 50 Zeilen Code abarbeiten muss bis er deinen Zeile zum Bitsetzen erreicht
kommt da natürlich einiges an Zeit zusammen.

NRicola
06.06.2018, 19:22
Hallo Siro,

danke für den Hinweis mit den Errata-Sheets. Das hat mich wachgerüttelt. Ich hatte einen Mikrocontroller als perfektes Gerät angesehen (nennen wir es Gottesteilchen). Ist ja doch erstaunlich, was dann später noch gefummelt werden muss... :)

Dennoch habe ich für meinen PIC18f45k20 nicht wirklich den passenden aller Einträge gefunden.
Den ASM-Code anzuschauen wäre ein Ansatz. Aber zum einen ist mir nicht klar, wie ich da ran komme, wenn ich in C programmiere und mit MPLAB/XC8 arbeite. Zum anderen habe ich aber auch mal den ganz popligen Test gemacht, INT0 auszulösen. Also ein Rechtecksignal an PB0 und dann Ausgang RD5 umschalten lassen.


TRISB = 1;
ANSELH = 0x00;
INTCON = 0;
INTCON2 = 0;
INTCON2bits.INTEDG0 = 0; // Flanke ist bei mir negativ
INTCONbits.INT0IE = 1;
INTCONbits.INT0IF = 0;
RCONbits.IPEN = 0;
INTCONbits.GIE = 1;

Auch hier (mit den internen 16MHz Clock) habe ich einen Verzug zwischen den Flanken von 11µs (=176 Clock cycles). So etwas fundamentales kann ja eigentlich nicht mehr falsch zwischen C und ASM implementiert sein. Oder etwa doch??:confused:
Daher wäre meine Vermutung, dass es eher noch etwas systematisches ist. Was könnte ich vergessen haben oder welches Register könnte mich noch ausbremsen? Ist dem interrupt-Prozess noch irgendein Timer oder anderer Trigger hinterlegt? Nach dem Schaubild "9-1 Intterrupt Logic" im Datenblatt eigentlich nicht.
Ich habe bis jetzt vorwiegend mit den high/low Prioritäten des Interrupts herumgespielt. Das lief alles auf's gleiche hinaus.

Grüß
NRicola

Klebwax
07.06.2018, 08:05
Auch hier (mit den internen 16MHz Clock) habe ich einen Verzug zwischen den Flanken von 11µs (=176 Clock cycles). So etwas fundamentales kann ja eigentlich nicht mehr falsch zwischen C und ASM implementiert sein. Oder etwa doch??:confused:
Daher wäre meine Vermutung, dass es eher noch etwas systematisches ist. Was könnte ich vergessen haben oder welches Register könnte mich noch ausbremsen? Ist dem interrupt-Prozess noch irgendein Timer oder anderer Trigger hinterlegt? Nach dem Schaubild "9-1 Intterrupt Logic" im Datenblatt eigentlich nicht.
Ich habe bis jetzt vorwiegend mit den high/low Prioritäten des Interrupts herumgespielt. Das lief alles auf's gleiche hinaus.

Zwei Vorbemerkungen: Die Interruptpriorität spielt nur eine Rolle, wenn zwei Interrupte gleichzeitig eintreffen. Das ist hier aber nicht der Fall. Und der CPU-Takt ist ein Viertel des Systemtaktes. die 176 Clock cycles sind also 44 Befehlstakte.

Aber mal ganz allgemein zu Interrupten und Interrupthandlern. Ein Interrupt kann jederzeit eintreten, daher muß der Prozessor nach abbarbeiten des Handlers 100% im gleichen Zustand sein, wie davor. Ansonsten würde das Haupprogramm nicht mehr richtig funktionieren. Ein Teil wird automatisch passieren, kost aber auch seine Zeit. So muß der Programmcounter auf dem Stack gesichert werden und die Anfangsadresse des Handlers muß geladen werden. Jeder Speicherzugriff dauert mindestens einen Prozessortakt, wieviel das in Summe bei einem PIC18 sind, weiß icht jetzt nicht. Jetzt ist der Prozessor also im Interrupthandler und muß als erstes alle Prozessorflags sichern, ohne sie dabei zu verändern. Da ein Interrupthandler eine normale C-Funktion ist, müssen dann alle Register gerettet werden, die der Compiler benutzt. Dazu gehören auch die, die in irgendwelchen Systemfunktionen für die Behandlung von longs oder für Multiplikation und Division benutzt werden, der Interrupt könnte ja gerade in der Bearbeitung einer solchen Funktion aufgetreten sein.

Auch kann der Compiler nicht vorhersagen, welche Register eine Libraryfunktion benutzt, die später nur als Objectcode dazugelinkt wird. Ich halte also die 44 Prozessorzyklen für die Interruptpräambel und für den Code, der vor deinem Portbitsetzen abläuft für nichts ungewöhnliches. Das Wiederherstellen des Prozessorzustands am Ende des Interrupts wird ähnlich lange dauern.

In Assembler wird das nur marginal besser. Bei ganz simplen Aufgaben des Interrupts kann man da besser sein, man muß aber bei jedem Befehl, den man schreibt, genau überlegen, was man tut. Und das auch, wenn man (oder auch ein anderer Programmierer) das Programm Monate oder Jahre später debugged. In C ist das kein Problem, ein paar Zeilen mehr im Interrupthandler und das Hauptprogramm läuft immer noch ungestört.



zudem würde ich mir mal den Assemblercode von der Interruptfunktion ansehen.
Ich weis nicht was der Compiler dort für einen "overhead" für Register sichern usw. reinbaut.
Bei einer Free Version von Microchip hab ich hier schon haarsträubenden code erlebt....
Wenn er erstmal 50 Zeilen Code abarbeiten muss bis er deinen Zeile zum Bitsetzen erreicht
kommt da natürlich einiges an Zeit zusammen.

Mit Begriffen wie "haarsträubenden" wäre ich zurückhaltend. Es klingt so, als wären die Compilerprogrammier Vollpfosten und hätten ihr Handwerk nicht gelernt. Ich würd mir das erst erlauben, wenn ich selbst einen Compiler geschrieben hätte, der alle Regressionstest besteht.

Und die Vorstellung, daß ein Optimizer da viel machen kann, ist irrig. Auch der kann später hinzugelinkten Objectcode nicht wirklich analysieren. Ein 8-Bit Prozessor braucht für die Behandlung der in C gängigen Datentypen wie int oder long nun mal mehrere Register. Bei "großen" Prozessoren wird das mit den Registern besser, dafür ist der Code des Interrupthandlers wahrscheinlich nicht im Cache und muß erst aus dem langsamen Hauptspeicher geladen werden (und verdrängt dabei möglicherweise den Code des Hauptprogramms). Das ist dann zwar anders, aber nicht unbedingt besser.

MfG Klebwax

Siro
07.06.2018, 08:25
Ich will den Compiler nicht schlecht machen Klebwax,
ich weis aber und das wird sogar angezeigt, dass im "Free" Mode der Code "äußerst" ungünstig wird, so würde ich das mal nennen.

Dies ist meine Standard Meldung bei allen Projekten:


You have compiled in FREE mode.
Using Omniscient Code Generation that is available in PRO mode,
you could have produced up to 60% smaller and 400% faster code.
See http://www.microchip.com/MPLABXCcompilers for more information.

Daher ist es gut möglich, dass hier die "zusätzlichen" Zeiten kommen könnten.
400 Prozent ist doch erheblich.....

Klebwax
07.06.2018, 09:12
Daher ist es gut möglich, dass hier die "zusätzlichen" Zeiten kommen könnten.
400 Prozent ist doch erheblich.....

Ich kenne die Meldung. Aber "60% smaler and 400% faster" glaub ich erstmal nicht. Da müsste eher ein "or" stehen. Typisch dealt man zwischen "faster" and "smaler". Loop unroling macht den Code länger, aber schneller. Berechnungen über Tabellen zu machen ebenfalls. Für die Rechnungen mit ints oder longs Inlinecode zu verwenden oder Systemfunktionen zu callen genauso. Und die Spitzenwerte sind, wie bei Benchmarks so üblich, wirklich Spitzenwerte unter idealen Bedingungen. Der Optimizer arbeitet auch sicher im wesentlichen auf C-Ebene, das wird sich auf sowas wie eine Interrupt-Präambel kaum auswirken. Der Optimizer wird nicht plötzlich umschaltbare Registerbänke oder ähnliches entdecken.

Der XC8 ist für Microchip sicher ein Verlustgeschäft. Ohne einen funktionierenden, fehlerfreien Compiler kann man aber keine CPUs für Milliarden von $ an Profis verkaufe, sie müssen sich das also antun. Ich glaube, diese Meldung soll die professionellen Kunden, die mit den "deep pockets", dazu bringen, wenigstens einen Beitrag zur Entwicklung und Wartung des Compilers beizutragen.

MfG Klebwax

Siro
07.06.2018, 10:08
Ich finde der Compiler sollte umsonst sein, (logisch aus privaten Interesse natürlich ;))
Aber eine Verbreitung der Chips geht meiner Meinung nach auch durch Privatanwender voran,
was ich privat nutze, setzte ich dann auch in der Entwicklung in der Firma ein.
Bei mir war es zumindest so.
-----------

Zum Thema Assembler Listing:

Um ein Assembler Listing zu bekommen must Du erst etwas einstellen: ich beziehe mich auf die MPLABX-IDE

Du klickst oben im Menü
File
Project Properties

oder auch rechte Maustaste in dem Projects Fenster links.


Dann gibt es vermutlich 2 Konfigurationen
eventuell aber auch nur eine.
Dort musst Due auf Conf und dann den Unterpunkt "Loading" wählen.
Hier muss rechts in das Kästchen ein Haken rein bei "Load symbols when......

Nun musst das Projekt neu compiliert (Builded) werden.
Erst dann wird auch ein Assembler File erstellt.

Nun gehst Du oben im Menü auf
"Window" --> Debugging --> Output --> Disamply listing file


Nun sollte sich das Assembler Fenster öffnen.


Bei mir heisst die Funktion
void __interrupt isr(void)

Dann kannst Du Dir den erzeugten code anschauen.

Ich hab mal Bildchen angehangen:

Siro
07.06.2018, 12:36
Ich habe mal eben ein Project mit deinem PIC erstellt
und dies ist der erzeugte Assembler Code der interrupt Funktion:


unsigned char spi_reg_addr;
18:
19: void interrupt isr(void)
20: {
21: if (PIR1bits.SSPIF == 1)
0052 A69E BTFSS PIR1, 3, ACCESS
0054 D006 BRA 0x62
22: {
23: spi_reg_addr=SSPBUF;
0056 CFC9 MOVFF SSPBUF, spi_reg_addr
0058 F018 NOP
24: SSPBUF=0x00;
005A 0E00 MOVLW 0x0
005C 6EC9 MOVWF SSPBUF, ACCESS
25: RD5=0;
005E 9A83 BCF PORTD, 5, ACCESS
26: PIR1bits.SSPIF = 0;
0060 969E BCF PIR1, 3, ACCESS
27: }
28: }
0062 C012 MOVFF 0x12, 0x1C



er sichert keine Register und ein RETFIE vermisse ich auch.
Muss man da noch was einstellen, vermutlich erscheint es nicht im Listingfile ??

Ceos
07.06.2018, 12:46
Der XC8 ist für Microchip sicher ein Verlustgeschäft. Ohne einen funktionierenden, fehlerfreien Compiler kann man aber keine CPUs für Milliarden von $ an Profis verkaufe, sie müssen sich das also antun. Ich glaube, diese Meldung soll die professionellen Kunden, die mit den "deep pockets", dazu bringen, wenigstens einen Beitrag zur Entwicklung und Wartung des Compilers beizutragen.

Also wenn einer Fragen zum Thema XC Compiler von Microchip hat, her damit, hatte gerade erst vor einer Woche den FAE von microchip zu Besuch bei uns und darüber intensiv unterhalten. Der XC8 ist wohl ein übernommener und ewig weiterentwickelter Compiler (der Ursprung ist mir entfallen weil es bei uns um den XC16 ging) der aber so schnell nicht sterben wird, er nannte ihn den wohl am besten optimierten Compiler.

Microchip ist eine Firma in der keine Querfinanzierung gemacht wird und daher kosten die "Fortgeschrittenen" Feature halt Geld da die Abteilung die u.a. den Compiler pflegt sich damit finanzieren muss.

Deine Behauptung das es nur für die deep-pockets ein Anreiz sein soll ist definitiv falsch. Die Optimierung kann immense Geschwindigkeitsvorteile bringen und 400% sind nicht aus der Luft. Man nehme z.B. einen Switch Case mit 30 Einträgen (ein etwas großer zugegeben aber nicht zwingend ungewöhnlich). Den kann ich mittels gewöhnlicher "If ElseIf" lösen, entsprechend langsam ist das ganze. Ich kann aber auch (Vorausgesetzt alle cases sind wertemäßig ansteigend und haben unterneinander weniger Abstand als 5) einen switch case in eine Sprungtabelle umwandlen. Das ist codemäßig kleiner als die ganzen Bedingungen und ERHEBLICH schneller!

Kommt also mehr oder minder auf die zu optimierende Funktion an, dann sind 400% absolut real!

Viele Funktionen wie "garbage collecting unused methodes" und andere code optimierungen lassen sich aber auch ohne optimierungsstufe realisieren. Schon allein damit methoden in einzelne sektionen packen zu lassen und dann hinterher im linker (hat der XC8 so nicht) dann unreferenzierte sektionen löschen zu lassen kann einen Code schnell viel kleiner machen, vor allem wenn man Softwaremodule einsetzt.
Das ist stnadradmäßig nicht an, beim atmel gcc aber schon.

Siro
07.06.2018, 12:57
Jetzt hab ichs, glaube ich:
Der Interrupt Vector liegt ja bei 0x0008 bzw. 0x0018
und dort ist folgender Code zu finden, nämlich der Eintritt in die Interrupt Funktion
Hier werden alle notwendigen Register gerettet, erstmal unabhängig davon ob diese benutzt werden.


0008 821B BSF btemp, 1, ACCESS
000A CFFA MOVFF PCLATH, __pcstackCOMRAM
000C F001 NOP
000E CFFB MOVFF PCLATU, 0x2
0010 F002 NOP
0012 CFE9 MOVFF FSR0, 0x3
0014 F003 NOP
0016 CFEA MOVFF FSR0H, 0x4
0018 F004 NOP
001A CFE1 MOVFF FSR1, 0x5
001C F005 NOP
001E CFE2 MOVFF FSR1H, 0x6
0020 F006 NOP
0022 CFD9 MOVFF FSR2, 0x7
0024 F007 NOP
0026 CFDA MOVFF FSR2H, 0x8
0028 F008 NOP
002A CFF3 MOVFF PROD, 0x9
002C F009 NOP
002E CFF4 MOVFF PRODH, 0xA
0030 F00A NOP
0032 CFF6 MOVFF TBLPTR, 0xB
0034 F00B NOP
0036 CFF7 MOVFF TBLPTRH, 0xC
0038 F00C NOP
003A CFF8 MOVFF TBLPTRU, 0xD
003C F00D NOP
003E CFF5 MOVFF TABLAT, 0xE
0040 F00E NOP
0042 C01B MOVFF btemp, 0xF
0044 F00F NOP
0046 C01C MOVFF 0x1C, 0x10
0048 F010 NOP
004A C01D MOVFF 0x1D, 0x11
004C F011 NOP
004E C01E MOVFF 0x1E, 0x12
0050 F012 NOP
00D8 0012 RETURN 0
54:


!! Die NOP s sind NICHT etwa reingebastelt um Code zu verschwenden, obwohl ich das auch am Anfangs dachte....
das ist Prozessorbedingt so bei einem Befehl MOVFF.

er kommt auch bei einem Interrupt in diesen Code rein, habe ich grad mal simuliert und das dauert dann 40 Cycles für diesen Codeabschnitt

Bei 16 MHz / 4 wegen der Pipeline Struktur = 4 MHz =250ns pro Cycle
Bei 40 Cycles also 40*250ns sind 10 us und das entspricht deinem Fehler.

Vermutlich kommt hier dann die "Optimierung" zur Geltung, wenn man eine Pro Version hat.Ich kann das leider nicht testen, habe nur die Freie VersionIch glaube man kann sich aber auch eine Pro Version Testweise runterladenIdee: Du könntest noch die PLL im Chip einschalten und die Zeit damit vierteln.
Also den internen Takt vervierfachen. Must deine SPI Einstellung vermutlich noch ändern.

NRicola
07.06.2018, 17:56
Hallo zusammen,

interessante Diskussion, die hier losgebrochen ist. :-)
Aber zum Thema: Danke Siro für die Aufklärung. Den ASM-Code habe ich nun auch ausgeben und so halbwegs nachvollziehen können. Im nächsten Schritt wollte ich mich natürlich daran machen, einige Zeilen auszusparen, um zu schauen, auf welche er womöglich sogar verzichten könnte. So hatte ich versucht, Teile des C-Codes durch asm("...") zu ersetzen. Allerdings klappte das nicht so, da ich versucht hatte, es genauso anzuordnen, wie in der listing.disasm. Das bedeutet aber, dass Befehle zwischen void interrupt isr(void) und der "geschweiften Klammer auf" stehen. Das hat dem Compiler erstmal nicht so gefallen.
Aber ich habe festgestellt, wenn die Befehle dann nach der geschweiften Klammer eingefügt sind, nimmt mein Zeitverzug (immernoch der provisorische INT0-Auslöser) von 11µs auf 25µs zu. Das scheint also schon alles Hand und Fuß zu haben.

Das bringt mich natürlich dazu, das erstmal so zu akzeptieren und ich muss mal drüber nachdenken, wie ich es schaffe, damit umzugehen.
Ursprünglich war mein Ziel, dass ich ein Address-Byte über SPI übertrage und der gleich anschließend eine Antwort (z.B. Wert einer Variable) übersendet. Bei einem SCLK-Takt von 1MHz und diesem verspäteten Interrupt, wo dann SSPBUF gleich den zu übertragenden Wert der Variable hätte rein bekommen sollen, klappt das natürlich nicht. Jetzt muss ich womöglich mehrere Variablen-Anfragen verschachteln. Also so in etwa:

1. SS & SCLK aktiv, 1Byte-Adresse1 MOSI; MISO nichts
<mehr oder weniger pause>
2. SS & SCLK aktiv, 1Byte-Adresse2 MOSI; MISO Variable von Adresse 1
<mehr oder weniger pause>
3. SS & SCLK aktiv, 1Byte-Adresse3 MOSI; MISO Variable von Adresse 2
<mehr oder weniger pause>
...
<mehr oder weniger pause>
N. SS & SCLK aktiv, 1Byte-AdresseN MOSI; MISO Variable von Adresse N-1
<mehr oder weniger pause>
N+1. SS & SCLK aktiv, 1Byte Dummy MOSI; MISO Variable von Adresse N

Dieses Vorgehen wäre zwar möglich, ist aber genau dann unschön, wenn der Master auf Adressen schreiben möchte. Dann würde der µC es nicht schaffen nach der Bekanntgabe der Adresse rechtzeitig den SSPBUF leer zu machen, ehe dann auch gleich das Schreibbyte übertragen wird.

Vielleicht wäre es hier geschickter, den Master anzufassen und (bei der Kommunikation mit dem PIC18 ) eine definierte Pause zwischen dem Adress-Byte und dem Read- oder Write-Byte zu machen.

Grüß,
NRicola

Siro
07.06.2018, 20:48
Ich muß gestehen dass ich auch noch keine Erfahrung mit dem "Slave Mode" habe.
und stelle mir das auch etwas problematisch vor mit Bidirektionalen Betrieb.
Wenn ich ein Kommando sende und unmittelbar eine Antwort haben möchte,
bleibt dem Slave meiner Meinung nach ja keien Zeit das Kommando auszuwerten,
bzw. müste das ja dann innerhalb eines Clockzyklus erledigt sein.
Empfang auslesen auswerten und Ergenis Byte schnell genug ins Register zum Ausschieben packen.
Hier würde ich, sofern das überhaupt möglich ist, nach dem Senden eines Kommandobytes
entweder eine Pause einfügen oder einfach 1 oder mehr Dummy Bytes senden.
Dann weis der Master, z.B. dass die ersten n Rückgabebytes ignoriert werden müssen.

Vielleicht haben hier schon andere Erfahrungen mit gemacht
und können dazu etwas Info geben. Wäre für mich auch mal interessant.

NRicola
07.06.2018, 22:17
Ich habe gerade nochmal etwas ge-internet-sucht. Ich bin lediglich auf das hier gestoßen, wo ebenfalls der Master ausgebremst wird:
https://www.ccsinfo.com/forum/viewtopic.php?t=39145
Hier sind es 100µs Wartezeit. Das geht womöglich nur über Software-SPI. Aber schon ziemlich suboptimal. Zwar kann man mit der SCLK schön aufdrehen (bei meinen 1MHz sind die zwei Byte eigentlich binnen ca. 16,6µs durch) - aber wenn man jetzt nochmal >11µs oder gar bis 100µs warten muss, dann wird man schon ganz ordentlich ausgebremst.

Sollte da jemand noch eine elegantere Lösung für PICs/µCs haben, wäre sie herzlich willkommen!
Man kann die Frage auch andersherum stellen: hat jemand einen µC vor sich, der binnen (weit) weniger als 11µs bei 16MHz die Interrupt-Routine betreten hat?

Grüß
NRicola

Klebwax
08.06.2018, 00:49
Ich kenn mich mit den PIC18 nicht so wirklich aus, aber die neueren PIC16, die ich so verwende, bekommt man mit der PLL auf 32 MHz. Das halbiert die Zeit.

Ich würd mal versuchen, mir vom SlaveSelect einen Interrupt auslösen zu lassen und ab dann alles im Interrupthandler per Pollig zu machen. Damit ist aber dies Problem nicht gelöst:


Wenn ich ein Kommando sende und unmittelbar eine Antwort haben möchte, bleibt dem Slave meiner Meinung nach ja keien Zeit das Kommando auszuwerten, bzw. müste das ja dann innerhalb eines Clockzyklus erledigt sein. Empfang auslesen auswerten und Ergebnis Byte schnell genug ins Register zum Ausschieben packen.

SPI-Flash im Fast-Modus erwarten ein Dummy-Byte nach der Adresse, bevor sie anfangen, Daten zu schicken. Die machen das intern aber rein in Hardware, Fast heißt da leicht 80MHz.

Beim PCI-Bus kann der Slave mit NAK quittieren, und der Master muß bis zu vier mal das gleiche Kommando wiederholen. Da hat sich der Slave schon beim ersten Mal die Adresse gemerkt.

Und bei I2C kommt erst ein Write mit dem Datenpointer, da kann der Slave schon mal den Adresszeiger aufsetzen, und dann der Read. Das könnte man auch mit SPI machen. Select low, ein Write Command, dann die Adresse. Select wieder High. Dann Select low, Read Command, der Slave hat das erste Byte schon parat und schickt es. Währen es geschickt wird, holt er das nächste, usw. bis Select wieder High wird.

Das sind so Beispiele, die mir einfallen.

MfG Klebwax

NRicola
11.06.2018, 18:23
Hallo zusammen,

ich möchte mal das Thema abrunden mit der folgenden Lösung, die ich nun umgesetzt habe und dem folgenden Fazit:
An dem Zeitverzug zwischen <Interrupt wird angefordert> und <ich mache irgendwas in der ISR> von ca. 11,5µs bei 16MHz lässt sich wohl nichts rütteln.
Deshalb muss beim SPI-Master eine Wartepause eingelegt werden, sodass der PIC18-Slave Zeit hat, das erste MOSI-Byte (=Adresse) zu verarbeiten.

Um den Zeitverzug von 11,5µs dennoch hier nicht haben zu müssen, habe ich jetzt folgende Strategie angewandt:



SPI kann keinen Interrupt mehr auslösen:

SPIE1bits.SSPIE = 0;
das SS-Signal (PA5) wird gleichzeitig auf den INT0-Pin (RB0) gelegt
INT0 aktivieren:

INTCONbits.INT0IE = 1;
INTCON2bits.INTEDG0 = 0; //interrupt on rising edge:=1 (Je nach SPI-Modus)
ISR-Routine bereitet sich bei INT0 bereits vor und wartet den SPI-Interrupt-Flag ab:

void interrupt isr(void){
if (INTCONbits.INT0IF==1){
while(PIR1bits.SSPIF == 0); // auf SPI-Interrupt warten
spi_reg_addr=SSPBUF;
SSPEN=0;
//tu' noch irgendwas, z.B. einen Adress-Anfragen-Vergleich durchführen
SSPEN=1;
PIR1bits.SSPIF = 0;
INTCONbits.INT0IF = 0;
}
}

Damit habe ich nicht mehr die 11,5µs Zeitverzug abzuwarten, sondern nur noch ca. 3,3µs. damit könnte man womöglich noch ohne Warten des Masters bei einem SCLK von 100-200kHz hinkommen. Zumindest ist das besser als die 100µs Wartezeit von dem Forumsbeitrag von ccsinfo (siehe oben).

Grüß
NRicola

NRicola
13.06.2018, 12:35
Hallo zusammen,

noch zu den Timings: oben hat sich ein Fehler eingeschlichen. Die 3,3µs waren nicht korrekt. Sie kamen daher, dass ein Interrupt ca. 11,5µs benötigt, um einsatzfähig zu sein. Bei meinen 1MHz SCLK waren die 8 gesendeten Bits schon nach 8µs durch. Die Differenz waren die rund 3,3µs.

Ich habe nun eine Zuordnung zu 8 Adressen (case-Struktur) gebaut und etwas mit den Timings rumgespielt.



Ohne Pause zwischen Byte 1 (MOSI) und Byte 2 (MISO): SCLK darf nicht mehr als 125kHz betragen. Die Gesamtübertragungsdauer für beide Bytes beträgt dann 132µs
Pause zwischen den beiden Bytes (also SCLK ausgesetzt) bei einem SCLK=1MHz: bei mir waren rund 9-10µs notwendig, damit der PIC18 genug Zeit hatte zu reagieren. Übertragungsdauer gesamt 25µs
Pause zwischen beiden Bytes, wenn die Dauer des ersten übertragenen Bytes in etwa dem Interrupt Verzug von 11,5µs entspricht (SCLK=700kHz): etwa 6,5µs muss die Pause betragen. Übertragungsdauer ca. 28,8µs.


Wie schon erwähnt, basieren die Werte auf einen 16MHz-Clock beim µC.

Grüß
NRicola