Hallo!
Ich habe hier das STM32F429-DISCOVERY board und möchte gerne mit dem SPI
Datenübertragungen realisieren. Allerings treten Interrupts auf, die ich
eigentlich deaktiviert habe.
So initialisiere ich das ganze:
Später starte ich dann in einer Initialisierungsroutine eine
Übertragung, bei der ich einfach per Polling byte für byte an die
Peripherie rausschiebe.
Im regulären Betrieb kommen dann Übertragungen mit dem DMA.
Ich brauche also niemals die Receive/Transmit interrupts. Daher habe ich
explizit die Interrupts für TXE (Transfer register empty) und RXNE
(receive register not empty) abgeschaltet. Ich möchte mit der ISR
nämlich nur Fehlerfälle abfangen. Dazu sieht meine ISR momentan so aus:
1
// called on error conditions
2
extern "C" void SPI1_IRQHandler()
3
{
4
if (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE))
5
while (1) {}
6
else if (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE))
7
while (1) {}
8
else if (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_OVR))
9
while (1) {}
10
else if (SPI_I2S_GetFlagStatus(SPI1, SPI_FLAG_MODF))
11
while (1) {}
12
else if (SPI_I2S_GetFlagStatus(SPI1, SPI_FLAG_CRCERR))
13
while (1) {}
14
else if (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TIFRFE))
15
while (1) {}
16
else if (SPI_I2S_GetFlagStatus(SPI1, I2S_FLAG_UDR))
17
while (1) {}
18
else if (SPI_I2S_GetFlagStatus(SPI1, I2S_FLAG_CHSIDE))
19
while (1) {}
20
else
21
while (1) {}
22
}
Das Ziel ist es, während der Entwicklung des Programms die auftretenden
Fehlerfälle mitzubekommen. Nun pasiert aber etwas interessantes:
Der TXE Interrupt wird ausgelöst, obwohl ich ihn explizit deaktiviert
habe - was läuft hier falsch?!
Danke an alle, die mir einen Tip geben können!
LG,
StrangeMan
PS: Ich habe es eben nochmal mit dem Debugger überprüft:
SPI_I2S_ITConfig() wird genau dreimal aufgerufen, das sind die drei oben
aus dem Quellcode. Es ist also sichergestellt, dass die Einstellungen
nicht überschrieben werden.
SPI_Init(SPI1, &SPI_InitStruct);
sitzt an der falschen Stelle. Erst beschreibst Du alle Struct-Variablen
und dann machst Du das SPI_Init(...)
Damit setzt Du die Struct auf default.
SPI_Init(...) immer am Anfang.
@struct:
Ich glaube, du verwechselst das mit SPI_StructInit(). SPI_Init()
initialisiert tatsächlich das SPI mit den entsprechenden Parametern aus
dem struct.
@peter_da_steht_er:
Im Zuge der Initialisierung mache ich das per Polling - ich lese aber
auch nix sondern schreibe nur.
Der TX DMA schreibt 49 Bytes auf den SPI, der RX DMA liest die ersten 4
Bytes von SPI, und hört dann auf, da es nicht mehr Daten zu lesen gibt.
Hier sind die dazugehörigen Interrupts:
1
// irq handler for incoming button data and error handling
2
extern "C" void DMA2_Stream2_IRQHandler()
3
{
4
if (DMA_GetFlagStatus(DMA2_Stream2, DMA_FLAG_TCIF2))
else if (DMA_GetFlagStatus(DMA2_Stream2, DMA_FLAG_TEIF2))
16
{
17
while (1) {};
18
}
19
else if (DMA_GetFlagStatus(DMA2_Stream2, DMA_FLAG_DMEIF2))
20
{
21
while (1) {};
22
}
23
else if (DMA_GetFlagStatus(DMA2_Stream2, DMA_FLAG_FEIF2))
24
{
25
while (1) {};
26
}
27
}
28
29
// irq handler for TX-channel error handling
30
extern "C" void DMA2_Stream5_IRQHandler()
31
{
32
/* DMA_FLAG_TEIFx: Streamx transfer error flag
33
* DMA_FLAG_DMEIFx: Streamx direct mode error flag
34
* DMA_FLAG_FEIFx: Streamx FIFO error flag
35
*/
36
if (DMA_GetFlagStatus(DMA2_Stream5, DMA_FLAG_TEIF5))
37
{
38
while (1) {};
39
}
40
else if (DMA_GetFlagStatus(DMA2_Stream5, DMA_FLAG_DMEIF5))
41
{
42
while (1) {};
43
}
44
else if (DMA_GetFlagStatus(DMA2_Stream5, DMA_FLAG_FEIF5))
45
{
46
while (1) {};
47
}
48
}
Das funktioniert aber eigentlich ganz gut. Ich poste den Code jetzt nur,
weil du gefragt hast und es sicher auch noch andere interessiert.
Mein Problem hat aber mit dem DMA nichts zu tun - es geht ja nur darum,
warum der TXE Interrupt vom SPI triggert, obwohl ich ihn abgeschaltet
habe.
Vielen Dank für Hilfe,
Johannes
So ihr lieben, ich hab meinen Fehler. Hier kommt die Lösung:
Dass das TXE Bit gesetzt war, verleitete mich zu der Annahme, dass
dieses auch den Interrupt ausgelöst hat. Das muss aber nicht stimmen,
genausogut könnte der Interrupt von einem anderen Bit ausgelöst worden
sein - nur habe ich in meiner ISR eben als erstes auf das TXE Bit
geprüft und daher niemals den Status der anderen Bits gesehen. Ich habe
die ISR nun so modifiziert:
Auf diese Weise sieht man im Debugger den Status aller relevanten Bits.
In meinem Fall war neben dem TXE Bit auch das OVR Bit gesetzt, welches
den Interrupt ausgelöst hat und signalisiert, dass das Datenregister
nicht gelesen wurde und dessen Inhalt durch neue Daten überschrieben
wurde.
Das kommt daher, dass ich mit dem RX-DMA nur 4 Bytes gelesen habe, der
TX DMA aber 49 Bytes schreibt. Daher hat der RX-DMA ab dem 5. Byte seine
Arbeit eingestellt und beim 6. Byte wird dann das OVR flag gesetzt. Es
muss also vom RX-DAM die gleiche Anzahl an Bytes gelesen werden, wie der
TX-DMA schreibt.
Problem gelöst.
Noch ein Nachtrag: Der oben gepostete Code enthält verschiedene Fehler.
1) Zum Zeitpunkt der Aktivierung des DMA muss der dazugehörige
Peripherie-Baustein deaktiviert sein. Erst nachdem der DMA
konfiguriert und aktiviert wurde, darf das SPI-Modul eingeschaltet
werden, wodurch sofort die Übertragung gestartet wird. Das bedeutet,
dass zwischen zwei aufeinanderfolgenden Übertragungen das SPI-Modul
abgeschaltet werden muss. Es empfiehlt sich, das SPI-Modul nicht direkt
nach dem Beenden einer Übertragung abzuschalten, sodern nur kurz vor dem
Start einer neuen Übertragung kurz zu deaktivieren, da sonst zwischen
den Übertragungen die Pins nicht mehr aktiv sind (z.B. wird die
Clock-Leitung nicht auf ihrem Idle Zustand gehalten)
2) Es empfiehlt sich, den FIFO im DMA zu benutzen, da der DMA sonst
ohne Zwischenschritt sofort bei Eintreffen eines neuen Bytes Zugriff auf
den Ziel-Speicher haben muss, damit das eingehende Byte nicht verloren
geht. Der FIFO kann als Zwischenspeicher also den RX-Kanal
ausfallsicherer machen. Außerdem arbeitet der SPI mit Bytes, der
Speicher wird aber im Prozessor auf dem Speicherbus als 32bit Doppelwort
addressiert. Der FIFO kann so konfiguriert werden, dass er immer
Doppelwörter liest/schreibt, und so für 4 Bytes des SPI nur einen
einzigen Speicherzugriff macht, was im Endeffekt den Speicherbus weniger
belastet.
3) Ich weiß nicht, ob es relevant ist, aber ich habe dem TX-DMA eine
geringere Priorität gegeben als dem RX-DMA. Ich gehe davon aus, dass
es im Zweifelsfall wichtiger ist, die eingehenden Daten zu lesen, bevor
man das nächste Byte sendet und damit möglicherweise die eingelesenen
Daten überschreibt.
4) Bei mir hat der TX-DMA immer direkt bei seiner Aktivierung
(DMA_Cmd(DMA2_Stream5, ENABLE)) einen FIFO Error ausgelöst. Das ist
scheinbar ein bekanntes Problem (siehe diverse Foren-Threads). Die
Lösung ist, das dazugehörige Flag einfach zu löschen, nachdem der DMA
aktiviert wurde. Mein DMA Interrupt hat allerdings die höchste Priorität
(ich will, dass der bei seinem Eintreten alles stoppt, damit ich solche
Fehler bemerke). Das bedeutet, dass ich den DMA-Fifo-Error Interrupt
deaktivieren muss, dann den DMA aktiviere, dann das FIFO-Error-Flag
lösche und dann den Interrupt wieder aktiviere.
5) Bevor man den DMA konfiguriert, sollte man ihn in jedem Falle
erstmal deaktivieren. Das geschieht normalerweise nach dem
Fertigstellen einer Übertragung automatisch, aber wer weiß...
So, hier nochmal der komplette Code:
Initialisierung
Peter Frun schrieb:> Ja, vielen Dank für deine Mühe.
Dem schliesse ich mich gerne an. Seit einiger Zeit fummele ich an der
DMA für die beiden I2S Schnittstellen des F407, die sich ja auch des SPI
bedienen und stolperte immer wieder. Deine Erfahrung lasse ich da jetzt
mal einfliessen. Vielen Dank und eine tiefe Verbeugung!