Forum: Mikrocontroller und Digitale Elektronik STM32F7 SPI DMA 400kSPS zum externen ADC


von Tycho B. (asellus)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe einen ADS7054 ADC angeschlossen an STM32L431RBI, den ich mit 
400kSPS auslesen möchte, möglichst ohne uC-Last. Bevor ich wirklich an 
der Platine rumteste, wollte ich das prinzipielle Vorgehen am 
STM32f769-DISCO Evalboard ausprobieren, deswegen auch STM32F7 in der 
Überschrift. Sollte ja dann übertragbar sein, denke ich.

Der ADC benötigt 18 clocks und SS-Puls, siehe Anhang. Das ist die 
eigentliche Problematik, denn SPI von STM kann maximal 16 Bits 
raustakten wenn DR-Register beschrieben wird.

Zunächst interessiert mich das Senden, also das Zusammenspiel von clock 
und NSS. NSS in hardware funktioniert nur bis halbwort-Größe (0b1111 << 
SPI_CR2_DS_Pos). Habe testweise also DMA circular und NSS in hardware 
ausprobiert, genau was ich brauche, aber eben nur 16 clocks und nicht 
18.

Dann gelesen, dass üblicherweise NSS in Software genutzt wird. Also SPI 
auf 8 bit umgestellt, SPI Tx DMA mit 3 Bytes geladen, SPI ISR Tx 
complete benutzt um DMA neu zu starten und NSS zu pulsen. Funktioniert. 
Jetzt brauche ich genau 400 kSPS. Das bedeutet, dass pro Sample 2,5 us 
gebraucht werden. APBPeripheral clock für SPI ist 80 MHz (auf dem 
STM32L431RBI), mit prescaler=4 sind es 20 MHz.
2,5 us * 20 MHz = 50 clocks. 50 clocks circular mit DMA zu generieren 
wären 5 DMA-Aktionen und SPI Data size von 10 bit. Die empfangenen Daten 
werden dann von einem anderen DMA in den Speicher geschrieben, z.B. 
blockweise 1600 Bytes. Diese müssen aber aufwändig zusammengesetzt 
werden weil sie durch data size von 10 bit fragmentiert sind. Mit diesem 
Ansatz (DMA, kontinuierlich) sehe ich keine andere Möglichkeit als die 
beschriebenen 5 x 10bit, um mit 400 kSPS auszulesen.

So, nächste Möglichkeit: Timer Update event nutzen, um DMA zu starten. 
Das ist DEUTLICH flexibler in der Zeit, jede Abtastrate ist sehr einfach 
zu realisieren. DMA wird dabei so konfiguriert, dass aus dem Speicher 
ins SPI2->DR geschrieben wird. Nun aber kann man so wieder nur ein Byte 
bzw. Halbwort übertragen, egal womit man DMA1_Stream7->NDTR belädt, denn 
meinem Verständnis nach ist dieser DMA nun an den Timer gebunden und 
haut sofort alles raus. SPI ist aber mit dem Raustakten des ersten 
Wertes beschäftigt und ist erstmal blind. Und man muss sich wieder um 
NSS kümmern.

Dann habe ich gelesen, dass man einenTimer mit mehreren DMA-Aktionen 
überladen kann, mit OutputCompare, glaube ich. Wäre das der gängige Weg?

Gibt es vielleicht eine wirklich elegante Methode? Irgendwie fällt mir 
nichts mehr ein.

von Uwe B. (Firma: TU Darmstadt) (uwebonnes)


Lesenswert?

Fuer ein aehnliche Problem mit eine F303 habe ich als Notiz:
 * ADC Signale, Abtastung mit 200 kHz
 * - SPI1 (oberer Kanal)
 *   TIM2 Periode: 4 us, CH3 aktiv: 3 us
 *   Compare startet ueber DMA_CH1 SPI1
 *   SPI1 RX wird ueber DMA_CH2 ausgelesen

Vielleicht hilfts weiter oder auf Nachfrage mehr

von Tycho B. (asellus)


Lesenswert?

Uwe B. schrieb:
> Fuer ein aehnliche Problem mit eine F303 habe ich als Notiz:
>  * ADC Signale, Abtastung mit 200 kHz
>  * - SPI1 (oberer Kanal)
>  *   TIM2 Periode: 4 us, CH3 aktiv: 3 us
>  *   Compare startet ueber DMA_CH1 SPI1
>  *   SPI1 RX wird ueber DMA_CH2 ausgelesen
>
> Vielleicht hilfts weiter oder auf Nachfrage mehr

4 us => 250 kHz?

Mit "Compare startet ueber DMA_CH1 SPI1" kann man doch wieder nur max. 
16 clocks takten, denn man kann per DMA in den SPI data register nur 
einen Wert schreiben. DMA wird ja vom Timer und nicht von SPI ausgelöst. 
Zumindest konnte ich durch Beladen des NDTR-Registers (number of data 
register) trotzdem kein mehrfaches Senden auf SPI auslösen. Immer nur 
ein Byte/Halbwort, je nachdem wie SPI eingestellt ist.

von Tycho B. (asellus)


Lesenswert?

Generelle Frage: kann man eigentlich mit DMA DMA-Register befüllen?

z.B.:
1
DMA1_Stream7->PAR = (uint32_t)&(DMA2_Stream7->NDTR);
Die Peripherie-Adresse ist der NDTR-Register des anderen DMA.

Oder sogar des selben DMA, aber ein anderes Stream?
1
DMA1_Stream7->PAR = (uint32_t)&(DMA1_Stream0->NDTR);

Hat bei mir beides nicht funktioniert, aber vielleicht mache ich auch 
was verkehrt.

von Gerald M. (gerald_m17)


Lesenswert?

Für den ADC einfach 3x8 Bit via SPI und DMA senden/empfangen. Danach die 
nicht gebrauchten Bits wegschmeißen.
Den DMA durch einen Timer Event Triggern.

Das Ganze kannst du sogar direkt so mit CubeMX konfigurieren und dann 
schaust du dir den Code an wann welche Register gesetzt werden.

von ElektroFH (Gast)


Lesenswert?

Tycho B. schrieb:
> Hat bei mir beides nicht funktioniert, aber vielleicht mache ich auch
> was verkehrt.

Ich denke beide deiner Wegen sind gut gedacht. Nun eine Fragmentierung 
des Daten-Flusses wird immer eigenen Nachteile mit sich bringen.
Ich verstehe nicht warum ADS7054 so ein "komische" 18bit SPI hat.

Es gibt viele ähnliche 14/16bit ADCs, mit 16Bit SPI kommunizieren.
Wie auch sehr low Power (falls nötig) AD7980, AD7988.

Oder LTC2365, LTC2370, LTC2380 oder AD7915/7918.

von Tycho B. (asellus)


Angehängte Dateien:

Lesenswert?

Gerald M. schrieb:
> Für den ADC einfach 3x8 Bit via SPI und DMA senden/empfangen.

Funktioniert irgendwie nicht.

SPI GPIO init ist gemacht.

SPI init:
1
  
2
RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;  //SPI2 clock enable
3
SPI2->CR1 = SPI_CR1_MSTR|(0b001 << SPI_CR1_BR_Pos)|SPI_CR1_SSM|SPI_CR1_SSI ;
4
SPI2->CR2 = (0b0111 << SPI_CR2_DS_Pos)|SPI_CR2_TXDMAEN|SPI_CR2_RXDMAEN;
5
SPI2->CR1 |= SPI_CR1_SPE;

Timer init:
1
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;        
2
TIM2->CR1 = 0b00000000;
3
TIM2->CR2 = 0b00000000;
4
TIM2->DIER = TIM_DIER_UDE;
5
TIM2->PSC = (108/1)-1;     //Set the prescaler
6
TIM2->ARR = 29;     //Set the autoreload value
7
TIM2->CR2 &= (uint16_t)~TIM_CR2_MMS;
8
TIM2->CR2 |=  TIM_CR2_MMS_1; //UPDATE EVENT

Timer DMA init:
1
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
2
DMA1_Stream7->CR = 0;
3
while(DMA1_Stream7->CR & (1 << 0));
4
DMA1_Stream7->M0AR = (uint32_t)source;
5
DMA1_Stream7->PAR = (uint32_t)&(SPI2->DR);
6
DMA1_Stream7->NDTR = 3;
7
DMA1_Stream7->FCR = 0;
8
DMA1_Stream7->CR =
9
DMA_SxCR_EN |       //Enable
10
(0b01 << DMA_SxCR_DIR_POS) |   //DMA  0b01 = memory-to-peripheral mode
11
DMA_SxCR_CIRC |   //DMA Circular mode 
12
(0 << DMA_SxCR_PINC_POS) |     //Do not increment peripheral
13
(0 << DMA_SxCR_PSIZE_POS) | //Peripheral data size 0 = 16bit , 1 = 32bit
14
(0 << DMA_SxCR_MSIZE_POS) |     //Memory data size 0 = 16bit , 1 = 32bit
15
(0b11 << DMA_SxCR_PL_POS) |  //DMA Priority very high
16
(0b00 << DMA_SxCR_PBURST_POS) | //peripheral burst: 16 bit
17
(0 << DMA_SxCR_MBURST_POS) |   //memory burst: single transfer 
18
(3 << DMA_SxCR_CHSEL_POS);  //DMA channel 3
19
20
TIM2->CR1 = TIM_CR1_CEN | TIM_CR1_ARPE;

Damit bekomme ich periodisch 1 Byte, nicht 3 Bytes, siehe Anhang.
Obwohl DMA1_Stream7->NDTR = 3;

von Tycho B. (asellus)


Lesenswert?

ElektroFH schrieb:
> Es gibt viele ähnliche 14/16bit ADCs, mit 16Bit SPI kommunizieren.
> Wie auch sehr low Power (falls nötig) AD7980, AD7988.

Das lag nicht in meinem Einflussbereich. Es gibt wohl aber auch uC, die 
nicht auf 16 clocks im SPI begrenzt sind. Hier im forum gelesen.

von Moira S. (Firma: Fei Fei Hydroponics) (zeusmining)


Lesenswert?

Dies erfordert eine sehr professionelle Terminologie und Kenntnisse, um 
besser zu verstehen.

https://www.zeusbtc.com/Integrated-Circuit/IC-Details.asp?ID=452

von Flomann (Gast)


Lesenswert?

Es wird nur ein Byte abgesetzt da der Dma mit dem einen Request des 
Timer nur eine
Transfer Unit zum Spi Fifo transferiert. Du müsstest mir dem einen Dma 
Timer Request
einen Burst Transfer der gesamten 3 Bytes einleiten was dann bei 1 Byte 
unit  in 3 Dma Transfer
Zyklen endet. Aber Achtung das geht nur so lange gut wie der Spi Tx Fifo 
platz hat
da hierbei kein Handshake mit dem eigentlichen Fifo und dessen platz 
stattfindet.
Normal nutzt man dafür dann den txFifo Not full/has Space (oder ähnlich 
gennant halt
sinngemäß) Request.
Das kann sich bei 3Byte noch ausgehen Musst du prüfen.
(Stm32 habe ich weniger mit zu tun bin bei Efm32 gelandet)
Ich kenne die verschiedenen Stm32 Dma' Implementierung nicht so gut in 
Verbindung
mit der SPI Peripherie.

Mit dem Efm32 nutze ich für sowas den Spi AutoTx der getriggert wird und 
hab nur
ein Dma Kanal für Rx oder stoße einen nötigen  Tx Fifo Transfer 
getriggert über einen
Dma Sync Descriptor an, der eigentliche Daten Transfer zum Spi Tx Fifo 
erfolgt mit
dessen Request und eigenem Descriptor dafür.
Bei dem Nxp mit dem ich kurz arbeitete hat dessen Dma dafür neben den 
requests des
Z.b. Peripherie Fifo auch einen nötige Trigger Bedingung, z.b extern das 
Busy eines ADC
über Gpio oder Timer Events. Das geht dann auch mit langen Transfers > 
Fifo size
Anständig.

von Tycho B. (asellus)


Angehängte Dateien:

Lesenswert?

Flomann schrieb:
> Es wird nur ein Byte abgesetzt da der Dma mit dem einen Request des
> Timer nur eine
> Transfer Unit zum Spi Fifo transferiert.

> da hierbei kein Handshake mit dem eigentlichen Fifo und dessen platz
> stattfindet

So habe ich mir das auch gedacht, als nur ein Byte angekommen ist.

Flomann schrieb:
> Normal nutzt man dafür dann den txFifo Not full/has Space (oder ähnlich
> gennant halt

Dieses triggert dann DMA für die zweite Befüllung des SPI->DR-Registers. 
"Not full/has Space" als trigger für DMA gibt es bei STM32 aber nicht, 
nur als interrupt, siehe Anhang.

Es gibt die Möglichkeit einen Burst mit z.B. DMA2 Stream6 Channel0, da 
nutzt man die verschiedenen OutputCompare von TIM1, so weit ich es 
verstanden habe. Wie das aber genau funktioniert habe ich nicht 
verstanden. Konfiguriert man auf den verschiedenen OutputCompare(nur auf 
den Signalen, ohne sie nach Außen zu führen) zeitlich passende 
verschiedene Überläufe, die nacheinander DMA auslösen, welches den 
SPI->DR-Register befüllt? Ohne Handshake, nur durch ausmessen/ausrechnen 
der Timings.

Würde wahrscheinlich funktionieren, das Problem mit NSS muss man dann 
aber trotzdem über Interrupte lösen...

von FloMann (Gast)


Lesenswert?

Hallo,
also erstmal mein Beitrag oben sicht ja am PC mit Firefox
furchtbar bzgl. Format aus. Am Lenovo Tab und Chrome mit
dem er erstellt wurde passte das eigentlich. ???? Naja

Bezüglich des SPI Requests für den DMA, das war eher allgemein
gemeint das der zugehöhrige SPI Request für ein ordentlichen
Datenfluss Handshake genuzt wird. Der Request selbst ist schon so
designed das dieser ausgelöst wird wen noch Platz im FIFO ist.
Das wollte ich jetzt im Detail nicht am genauem Namen festmachen.
Bei STM heist er dann einfach "SPIx_TX".
(Beim EFM32 z.b. USARTxTXEMPTY und USARTxTXBL(Req. bei Platz im FIFO))

Es ist ja etwas "ungewöhnlich" einen Request vom Timer zu nehmen
der dann den DMA veranlasst den SPI Fifo zu füllen, das ist halt
auserhalb des SPI Datenflus Handshake mit dem DMA und ggf. 
problematisch.
(z.b. wenn zu transferierende Daten > Fifo Größe)

Im Prinzip wolltest du ja damit wohl ereichen das du Abhängig vom Timer
eine Abtastrate x hast. Die Wandlung wird ja bei dem Typ mit der
SPI Kommunikation durchgeführt. Das ganze quasi in HW Timing und
weniger SW Overhead?

Nur mit dem SPI_TX als Request und DMA in Circular Modus geht das ja 
nicht
wirklich flexibel.
SPI und DMA Kanal aktiv und das läuft dann so vor dich hin.
Für deine gewünschten 400Ksmp/sec hast du ja dann auch mal in die
"Trickkiste" gegriffen und mit angepassten SPI-Clock, -Frame(10Bit) und
fünf Transfers gearbeitet um dies zu bewerkstelligen.
Mal eine Frage, dies lief sauber deinen Ansprüchen/Anforderungen 
gerecht?
Das nötige "Formatieren/Packing" der Daten kann man ja noch machen.
Wenn dies deine Aufgabe erfüllt warum nicht?

Oder warum nicht (erstmal) einen einfachen konventionellen weg.
Timer -> TMR Interrupt -> Im Interrupt Wandeln mit Daten holen anstoßen.
Bei 3Byte/Zyklus muss es ja auch nichtmal unbedingt mit DMA für den SPI
sein zumal der SPI auch noch ein 4Byte FiFo hat.
Wäre das ein Problem, z.b. Latenz der ISR bzw. auch der sicher etwas
größerer Jitter als wenn die Wandlung rein in HW Timings angestoßen 
wird.
Noch viele andere nötige Tasks? so das die ISR Latenz + Zeit in ISR für
Wandlung und Daten holen bei einem (kontinuierlichen?) 2,5us 
Abtastzyklus
zu, zu hohen Auslastungen führt?

Nochmal was anderes:
Der DMA im F76x hat einen Burst Modus für 4,8,16 Transfers pro Request.
Annahme: Überall Byte Transfer Units, Anzahl Transfers = 4,
Request vom Timer welcher dann ein Burst mit 4 Transfers
einleitet zum SPI TX Fifo.
Das könnte vll. gehen da der SPI Fifo 4Byte groß ist sollte das 
reinpassen.
Problem -> der L4xx DMA hat den "Burst" Modus so nicht, dessen
DMA implementierung ist im detail auch unterschiedlich wenn ich das
richtig sehe.

Achja bezüglich dem Hardware Chipselect "Problem" das kann ich aktuell
nicht ganz nachvollziehen. Mit den mir bekannten Controllern
war das i.R. so, das so lange Daten kontinuerlich in den SPI
Fifo geschrieben wurden diese Frames als Sequenz rausgehen und der
Chipselect enstprechend diese Zeit gesetzt blieb.(auser bei speziellen 
aktivierten Modi die ein stroben/pulsen per Frame explizt vorsehen)
Beim EFM32 hat man z.b. aber auch noch konfigurations Möglichkeiten
z.b. mit dem CSHold können "Datenlücken" in SPI Bit Zeiten überbrückt 
werden bevor das CS weggenommen wird. Macht das der STM32 nicht
auch so? wenn enstprechend parametriert. Zumindest sollte das
funktionieren sonst ist ein Abtastprozess mit dem Wandler und mit 
möglichst
viel in "HW-(Timing)" und ohne SW intervention eh nicht wirklich 
machbar.
Dann vll. doch die einfache "Konventionelle" Lösung?

Gruß

von Tycho B. (asellus)


Lesenswert?

FloMann schrieb:
> Im Prinzip wolltest du ja damit wohl ereichen das du Abhängig vom Timer
> eine Abtastrate x hast. Die Wandlung wird ja bei dem Typ mit der
> SPI Kommunikation durchgeführt. Das ganze quasi in HW Timing und
> weniger SW Overhead?

Ja, genau

FloMann schrieb:
> Mal eine Frage, dies lief sauber deinen Ansprüchen/Anforderungen
> gerecht?

Ja, die Timings kann man sehr flexibel einstellen. SPI FIFO erlaubt 
sogar ununterbrochenes Rausschieben der Bits. Das war mir vorher nicht 
klar. Man muss also die zweite Befüllung gar nicht genau timen.

FloMann schrieb:
> Oder warum nicht (erstmal) einen einfachen konventionellen weg.
> Timer -> TMR Interrupt -> Im Interrupt Wandeln mit Daten holen anstoßen.
> Bei 3Byte/Zyklus muss es ja auch nichtmal unbedingt mit DMA für den SPI
> sein zumal der SPI auch noch ein 4Byte FiFo hat.
> Wäre das ein Problem, z.b. Latenz der ISR bzw. auch der sicher etwas
> größerer Jitter als wenn die Wandlung rein in HW Timings angestoßen
> wird.
> Noch viele andere nötige Tasks? so das die ISR Latenz + Zeit in ISR für
> Wandlung und Daten holen bei einem (kontinuierlichen?) 2,5us
> Abtastzyklus
> zu, zu hohen Auslastungen führt?

Der ADC kann bis zu 1MSPS und IIR-Filter soll auch dazu. Ich will die 
Auslastung verringern. Andererseits ist es auch eine schöne Aufgabe, um 
die DMA-Innereien zu verstehen.

FloMann schrieb:
> Achja bezüglich dem Hardware Chipselect "Problem" das kann ich aktuell
> nicht ganz nachvollziehen. Mit den mir bekannten Controllern
> war das i.R. so, das so lange Daten kontinuerlich in den SPI
> Fifo geschrieben wurden diese Frames als Sequenz rausgehen und der
> Chipselect enstprechend diese Zeit gesetzt blieb.

Ist bei STM32 anders. CS wird extra bei jedem Byte oder Halbwort, je 
nachdem was man einstellt, runtergezogen und wieder hoch.

Ich habe das jetzt mit dem überladenen Timer-DMA gemacht. Sieht schief 
aus, funktioniert aber wunderbar.

von Flomann (Gast)


Lesenswert?

Tycho B. schrieb:
> Ist bei STM32 anders. CS wird extra bei jedem Byte oder Halbwort, je
> nachdem was man einstellt, runtergezogen und wieder hoch.
>
> Ich habe das jetzt mit dem überladenen Timer-DMA gemacht. Sieht schief
> aus, funktioniert aber wunderbar.

Hallo,

Du nutzt jetzt die eine DMA Request "Leitung" eines Timer der so
konfiguriert ist das er dir zeitlich passend die 3 Requests
zum DMA erzeugt um die 3 Byte ADC Daten über das SPI zu senden.
Oder verteilt über verschachtelte unterschiedliche Requests.

Bezüglich dem CS ist das tatsächlich allgemein so bei STM, also auch
bei anderen derivaten oder jetzt bezogen auf den F7 und dessen SPI 
implementierung, das diese den CS in HW nur im takte der Framebreite
pulsen lässt?, kein HW CS für kontinuierliche Sequenzen von x*Frames.

Aus dem RM0440 z.b: (müsste für die STM32G4xx Typen sein)
1
NSS output enable (SSM=0,SSOE = 1): this configuration is only used when the
2
MCU is set as master. The NSS pin is managed by the hardware. The NSS signal
3
is driven low as soon as the SPI is enabled in master mode (SPE=1), and is kept
4
low until the SPI is disabled (SPE =0). A pulse can be generated between
5
continuous communications if NSS pulse mode is activated (NSSP=1). The SPI
6
cannot work in multimaster configuration with this NSS setting.
oder aus RM0090(von F4xx)
1
NSS output enabled (SSM = 0, SSOE = 1)
2
This configuration is used only when the device operates in master mode. The
3
NSS signal is driven low when the master starts the communication and is kept
4
low until the SPI is disabled.
Beim ersten ist angeblich für den CS das "Enable" des SPI 
auschlaggebend.
Beim zweiten wird es mit start der Kommunikation gesetzt bis das SPI
"abgeschaltet" wird.
AHA mir etwas verwirrend, so mal zwei Beispiele die ich noch aus
Dokumenten fand die ich hier noch habe von der MCU suche letztes Jahr
für ein Projekt (aber dies zu der Zeit gar nicht im detail beachtet 
habe)

Wie machst du das dann jetzt mit dem CS wenn das der STM32 nicht für 
eine
"Sequenz" von x*Frames dies in HW macht.
Per Software in einem zeitlich passenden eh vorhanden Interrupt oder 
auch
über einen (den Req) Timer der Zeitlich passend den Ausgang bedient.

Gruß
Florian

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.