Forum: Mikrocontroller und Digitale Elektronik STM32F2 SPI DMA CS Handling


von Commander Keen (Gast)


Lesenswert?

Hallo,

ich versuche gerade zwei STM32F205 uC über SPI zu verbinden.
Mein erster Schritt ist es nun aus dem Master einen Output Stream via 
DMA zu entlocken.

Ich initialisiere also die DMA, setze den CS und warte auf den 
TransferComplete Interrupt in dem ich den CS wieder zurücknehme.

Mit dem Code unten funktioniert das im Prinzip, außer dass er im 
Interrupt den CS genau 15 Bits zu früh zurücksetzt.
Das erscheint mir auf dem zweiten Blick auch logisch, das 
TransferComplete Flag bedeutet in diesem Falle dass die letzten Daten 
über DMA an die SPI Peripherie übergeben wurden, sind zu diesem 
Zeitpunkt aber noch nicht gesendet. Für diesen Test folgendes Define 
aktivieren.
1
 
2
#define TRANSMIT_COMPLETE

Also habe ich mir gedacht ich benutze den TransferComplete Interrupt des 
DMA Receive Streams. Das funktioniert dann beim ersten Senden wie 
gewünscht, allerdings ist anschließend Sendeschluss. Für diesen Test 
folgendes Define aktivieren.
1
 
2
#define RECEIVE_COMPLETE

Es kommen beim zweiten Sendevorgang keinerlei Daten mehr auf den Bus 
geschweige denn ein TransferComplete Interrupt

###
1
#include <stdint.h>
2
#include "stm32f2xx.h"
3
4
#define TRANSMIT_COMPLETE
5
//#define RECEIVE_COMPLETE
6
7
#ifdef TRANSMIT_COMPLETE
8
#define DMA_STREAM_IRQn    DMA1_Stream7_IRQn
9
#define DMA_IRQ_STREAM    DMA1_Stream7
10
#define DMA_IRQ_TCIF    DMA_IT_TCIF7
11
#define DMA_IRQ_HANDLER    DMA1_Stream7_IRQHandler
12
#endif
13
#ifdef RECEIVE_COMPLETE
14
#define DMA_STREAM_IRQn    DMA1_Stream0_IRQn
15
#define DMA_IRQ_STREAM    DMA1_Stream0
16
#define DMA_IRQ_TCIF    DMA_IT_TCIF0
17
#define DMA_IRQ_HANDLER    DMA1_Stream0_IRQHandler
18
#endif
19
20
21
void init(void) {
22
  GPIO_InitTypeDef gpio;
23
  SPI_InitTypeDef spi;
24
  //clock peripherals
25
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);
26
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //CS
27
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //CLK, MISO, MOSI
28
  //init CS pin
29
  GPIO_StructInit(&gpio);
30
  gpio.GPIO_Pin = GPIO_Pin_15;
31
  gpio.GPIO_Mode = GPIO_Mode_OUT;
32
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
33
  gpio.GPIO_OType = GPIO_OType_PP;
34
  gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
35
  GPIO_Init(GPIOA, &gpio);
36
  GPIOA->BSRRL = GPIO_Pin_15; //set CS
37
  //init spi pins
38
  gpio.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_10 | GPIO_Pin_10;
39
  gpio.GPIO_Mode = GPIO_Mode_AF;
40
  gpio.GPIO_Speed = GPIO_Speed_50MHz;
41
  gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
42
  GPIO_Init(GPIOC, &gpio);
43
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SPI3);
44
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_SPI3);
45
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SPI3);
46
  //init spi
47
  SPI_StructInit(&spi);
48
  spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
49
  spi.SPI_Mode = SPI_Mode_Master;
50
  spi.SPI_DataSize = SPI_DataSize_8b;
51
  spi.SPI_CPOL = SPI_CPOL_Low;
52
  spi.SPI_CPHA = SPI_CPHA_1Edge;
53
  spi.SPI_NSS = SPI_NSS_Soft;
54
  spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
55
  SPI_Init(SPI3,&spi);
56
  SPI_Cmd(SPI3, ENABLE);
57
}
58
59
void send_dma(uint8_t* src, uint8_t* dest, uint32_t cnt) {
60
  DMA_InitTypeDef  dma;
61
  NVIC_InitTypeDef nvic;
62
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
63
  //init_nvic
64
  nvic.NVIC_IRQChannel = DMA_STREAM_IRQn;
65
  nvic.NVIC_IRQChannelPreemptionPriority = 0;
66
  nvic.NVIC_IRQChannelSubPriority = 2;
67
  nvic.NVIC_IRQChannelCmd = ENABLE;
68
  NVIC_Init(&nvic);
69
  //init tx dma
70
  dma.DMA_Channel = DMA_Channel_0;
71
  dma.DMA_PeripheralBaseAddr = (uint32_t)&SPI3->DR;
72
  dma.DMA_Memory0BaseAddr = (uint32_t) &src;
73
  dma.DMA_DIR = DMA_DIR_MemoryToPeripheral;
74
  dma.DMA_BufferSize = cnt;
75
  dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
76
  dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
77
  dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
78
  dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
79
  dma.DMA_Mode = DMA_Mode_Normal;
80
  dma.DMA_Priority = DMA_Priority_High;
81
  dma.DMA_FIFOMode = DMA_FIFOMode_Disable;
82
  dma.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
83
  dma.DMA_MemoryBurst = DMA_MemoryBurst_Single;
84
  dma.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
85
  DMA_Init(DMA1_Stream7, &dma);
86
  //init rx dma
87
  dma.DMA_Channel = DMA_Channel_0;
88
  dma.DMA_DIR = DMA_DIR_PeripheralToMemory;
89
  dma.DMA_Memory0BaseAddr = (uint32_t) &dest;
90
  dma.DMA_BufferSize = cnt;
91
  dma.DMA_MemoryInc = DMA_MemoryInc_Enable;
92
  DMA_Init(DMA1_Stream0, &dma);
93
  //enable streams
94
  DMA_Cmd(DMA1_Stream0, ENABLE);
95
  DMA_Cmd(DMA1_Stream7, ENABLE);
96
  //enable interrupt
97
  DMA_ITConfig(DMA_IRQ_STREAM, DMA_IT_TC, ENABLE);
98
  SPI_I2S_DMACmd(SPI3,SPI_I2S_DMAReq_Rx,ENABLE);
99
  SPI_I2S_DMACmd(SPI3,SPI_I2S_DMAReq_Tx,ENABLE);
100
  GPIOA->BSRRH = GPIO_Pin_15;  //clear CS
101
  I2S_Cmd(SPI3, ENABLE);
102
}
103
104
void DMA_IRQ_HANDLER(void) {
105
  DMA_ClearITPendingBit(DMA_IRQ_STREAM, DMA_IRQ_TCIF);
106
  GPIOA->BSRRL = GPIO_Pin_15;  //set CS
107
}
108
109
uint8_t outbuffer[10];
110
uint8_t inbuffer[10];
111
int main(void) {
112
  outbuffer[0] = 1;
113
  outbuffer[0] = 2;
114
  outbuffer[0] = 3;
115
  init();
116
  for(int32_t i=0 ; i<100000 ; i++);
117
  send_dma(outbuffer, inbuffer, 3);    //breakpoint
118
  for(int32_t i=0 ; i<100000 ; i++);
119
  send_dma(outbuffer, inbuffer, 3);    //breakpoint
120
  while(1);
121
  return 0;
122
}

von Irgendwer (Gast)


Lesenswert?

Was der DMA-Teil macht ist doch eigentlich völlig egal,
interessanter ist was der SPI-Teil macht.
Da z.B. der Interrupt mittels "Transmit buffer empty flag"

von STM Apprentice (Gast)


Lesenswert?

Commander Keen schrieb:
> Also habe ich mir gedacht ich benutze den TransferComplete Interrupt des
> DMA Receive Streams.

Darf ich dich auf meine sehr ähnliche Problematik hinweisen:

Beitrag "[STM32F4xx] SPI Optimierung"

Vermutlich musst auch du das SPI_I2S_FLAG_BSY auswerten bis
du den CS wegnehmen kannst/darfst. Ob es dafür einen Interrupt
gibt kann ich so spät in der Nacht nicht sagen .... ;-)

von Commander Keen (Gast)


Lesenswert?

Hallo,

habe beim Code zusammenstreichen noch einen Fehler gemacht, es muss 
heißen
1
gpio.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;

@ Irgendwer
Die SPI Interrupts hatte ich bisher nicht auf dem Schirm, werde ich mir 
mal anschauen

@STM Apprentice
Ich weigere mich bei der Verwendung einer DMA auf irgendwelches Polling 
zurück zu greifen. Dann wechsele ich lieber den uC ;-)

Ich habe hier
http://www.diller-technologies.de/stm32.html#spi_dma
unter
"16 SPI mit DMA"

noch eine Möglichekit für eine andere Familie gesehen.
Dort werden Interrupts miteinander verschachtelt.
Den Sinn habe ich noch nicht ganz verstanden.

Scheint bei den STMs alles nicht so benutzerfreundlich zu sein ...

von Stefan K. (stefan64)


Lesenswert?

Du musst auf jeden Fall den SPI-Receive DMA benutzen, um das CS 
zurückzunehmen, weil nur dieser NACH erfolgtem Transfer aktiv wird.

Was heisst das genau:
> Es kommen beim zweiten Sendevorgang keinerlei Daten mehr auf den Bus
> geschweige denn ein TransferComplete Interrupt
Wird der SPI-Transfer überhaupt nicht gestartet?

Viele Grüße, Stefan

von Jim M. (turboj)


Lesenswert?

Commander Keen schrieb:
> #define RECEIVE_COMPLETE
> Es kommen beim zweiten Sendevorgang keinerlei Daten mehr auf den Bus
> geschweige denn ein TransferComplete Interrupt

Kann ich mit dem geposteten Code nicht wirklich nachvollziehen.

Hier müsste mit 'nem Debugger mal nachgeschaut werden wo es klemmt. 
Eventuell hängt der Prozessor in irgendeinem nicht behandelten Interrupt 
fest.

von STM Apprentice (Gast)


Lesenswert?

Commander Keen schrieb:
> Es kommen beim zweiten Sendevorgang keinerlei Daten mehr auf den Bus
> geschweige denn ein TransferComplete Interrupt

... äääääähhhhmmmm ....

Ist es dein Ernst, bei jedem Block Transfer die ganze DMA-
Maschine und den NVIC neu zu initialisieren?

Bestimmte Sachen können beim STM32 nicht zweimal initialisiert
werden, manche Sachen müssen auch erst disabled werden bevor
sie umprogrammiert werden können ..... bin mir nicht sicher
ob das auf deine Anwendung zutrifft.

von STM Apprentice (Gast)


Lesenswert?

Commander Keen schrieb:
1
gpio.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_10 | GPIO_Pin_10;

Ich könnte mir vorstellen dass das nicht so gemeint ist.

von STM Apprentice (Gast)


Lesenswert?

STM Apprentice schrieb:
> Ich könnte mir vorstellen dass das nicht so gemeint ist.

ooops, bereits vorher erkannt worden und ich hab es übersehen.

von STM Apprentice (Gast)


Lesenswert?

Commander Keen schrieb:
> Ich weigere mich bei der Verwendung einer DMA auf irgendwelches Polling
> zurück zu greifen.

Wenn's nicht anders geht, dann vielleicht so:

Der DMA Interrupt wird ja sowieso durchlaufen weil der CS
deaktiviert werden soll. Der IRQ wird ausgelöst weil die DMA-
Maschine fertig ist, jetzt muss nur noch die "Pipeline" leer
werden, d.h. das letzte Byte vom SPI muss draussen sein. Dieses
Flag SPI_I2S_IT_TXE kann man im Interrupt-Kontext pollen, das
kann ja auch nicht lange dauern.

Das müsste funktionieren vorausgesetzt die SPI Flags werden im
DMA Modus auch entsprechend wie im Nicht-DMA-Modus gesetzt.

von Commander Keen (Gast)


Lesenswert?

Stefan K. schrieb:
> Wird der SPI-Transfer überhaupt nicht gestartet?
Ich denke nicht. Wie erwähnt kommen nach dem ersten send_dma Aufruf 
sinnige Pegel am SPI. Er schickt die 3 Bytes raus und auch der CS ist in 
Ordnung. Beim zweiten Aufruf von send_dma passiert an den SPI Pins rein 
gar nichts mehr und er springt auch nicht mehr in die ISR.

Jim M. schrieb:
> Eventuell hängt der Prozessor in irgendeinem nicht behandelten Interrupt
> fest.
Beim debuggen läuft er ganz normal weiter. Auch zusätzlicher Code in der 
while(1) wird dann ausgeführt.

STM Apprentice schrieb:
> Ist es dein Ernst, bei jedem Block Transfer die ganze DMA-
> Maschine und den NVIC neu zu initialisieren?
Der Code ist quick&dirty. Ich hatte den ganzen dma und nvic Kram zuvor 
schon in der init Routine die nur einmalig aufgerufen wird allerdings 
ohne Erfolg. Deswegen aktuell alles immer neu initialisiert weil ich 
gehofft habe so eher zum Erfolg zu kommen.

@ STM Apprentice
Im Prinzip gebe ich Dir recht. Sobald der Interrupt kommt könnte ich die 
restliche (kleine) Zeit warten bis die letzten Bits rausgeschickt sind.

Allerdings verstehe ich nicht warum der Code bei dem DMA1_Stream7 
Interrupt (Transmit) funktioniert und beim DMA1_Stream0 (Receive) nicht 
mehr.
Aus Sicht der DMA dürfte doch beides gleich sein ?

von STM Apprentice (Gast)


Lesenswert?

Commander Keen schrieb:
> I2S_Cmd(SPI3, ENABLE);

Könnte es sein dass diese Zeile ist falsch ist?

* @brief Enables or disables the specified SPI peripheral (in I2S mode).

Wodurch genau wird eigentlich der DMA/SPI Transfer ausgelöst?

von Commander Keen (Gast)


Lesenswert?

STM Apprentice schrieb:
> Commander Keen schrieb:
>> I2S_Cmd(SPI3, ENABLE);
>
> Könnte es sein dass diese Zeile ist falsch ist?
So wie ich das verstanden habe unterstützt der SPI 2 und 3 noch den I2S 
Mode. Der Unterschied zur Funktion SPI_Cmd(SPI3, ENABLE); besteht in der 
Fehlerabfrage

STM Apprentice schrieb:
> Wodurch genau wird eigentlich der DMA/SPI Transfer ausgelöst?
1
SPI_I2S_DMACmd(LCD_SPI_PERIPH,SPI_I2S_DMAReq_Rx,ENABLE);
2
SPI_I2S_DMACmd(LCD_SPI_PERIPH,SPI_I2S_DMAReq_Tx,ENABLE);

Ich habe den Fehler nun gefunden.
Bevor die DMAs initialisiert werden sollte man
1
  * @brief  Deinitialize the DMAy Streamx registers to their default reset values.
2
DMA_DeInit(DMA1_Stream0); 
3
//bzw.
4
DMA_DeInit(DMA1_Stream7);
aufrufen.

Nun schickt er die Daten mit korrektem CS Timing raus und ich bekomme 
sogar richtige Daten als Antwort im Receive Puffer.

Nochmals Danke für den Input

von STM Apprentice (Gast)


Lesenswert?

Commander Keen schrieb:
> Ich habe den Fehler nun gefunden.
> Bevor die DMAs initialisiert werden sollte man  * @brief  Deinitialize
> the DMAy Streamx registers to their default reset values.
> DMA_DeInit(DMA1_Stream0);
> //bzw.
> DMA_DeInit(DMA1_Stream7);
> aufrufen.

Klingt nicht sehr überzeugend, denn nach einem Kaltstart bzw
Reset sollte man eigentlich den De-Init Zustand haben - so
wie du es selbst zitierst.

Sieht eher danach aus als ob dein Programm noch etwas anderes
mit den Registern bezüglich DMA macht.

Ansonsten wäre das ja eine Macke im STM-Chip wenn man
vorher per Software resetten muss .....

von Darth Moan (Gast)


Lesenswert?

Moin,

ich moechte an dieser Stelle einen gut gemeinten Rat zum Besten geben.
Wenn deine Hauptschleife nicht den SPI Status prueft, weil du das nicht
moechtest, dann sollte deine send-Funktion dies auf jeden Fall tun! Und
eine re-initialisierung der SPI oder DMA ablehnen, wenn der Transfer
noch laeuft. Die Warteschleife, die nichts tut, ausser warten, kann von
einem agressieven Optimierer schonmal komplett rausgeschmissen werden.
Das kann man in Variablen halten und im RX-DMA Interrupt entsprechend
auf "finished" setzen, oder direkt den CS GPIO zuruecklesen.
Wenn man einen DMA Transfer neu "konfigurieren" will (adresse, laenge)
sollte man vorher den DMA disablen. Ich habe den F407 am start, aber
ich glaube dass ist bei deinem 205 das gleiche.

sieht bei mir etwa so aus (nach dem tarnsfer complete check):
1
    GPIOB->BSRRH = GPIO_PIN_12;
2
3
    DMA1_Stream4->CR &=  ~DMA_SxCR_EN;
4
    DMA1_Stream3->CR &=  ~DMA_SxCR_EN;
5
6
    DMA1->HIFCR = (DMA_HIFCR_CTCIF4 | DMA_HIFCR_CHTIF4 | DMA_HIFCR_CTEIF4 | DMA_HIFCR_CDMEIF4 | DMA_HIFCR_CFEIF4 );
7
    DMA1->LIFCR = (DMA_LIFCR_CTCIF3 | DMA_LIFCR_CHTIF3 | DMA_LIFCR_CTEIF3 | DMA_LIFCR_CDMEIF3 | DMA_LIFCR_CFEIF3 );
8
9
10
    DMA1_Stream4->CR  = ( DMA_SxCR_PL_1 | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0  );
11
    DMA1_Stream3->CR  = ( DMA_SxCR_PL_1 | DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 | DMA_SxCR_MINC );
12
13
    DMA1_Stream4->NDTR = Len;
14
    DMA1_Stream3->NDTR = Len;
15
16
    DMA1_Stream4->PAR  = (uint32_t)&(SPI2->DR);
17
    DMA1_Stream3->PAR  = (uint32_t)&(SPI2->DR);
18
    DMA1_Stream4->M0AR = (uint32_t)MsgPtr;
19
    DMA1_Stream3->M0AR = (uint32_t)RXBuf;
20
21
    DMA1_Stream4->FCR &= (~(DMA_SxFCR_FEIE | DMA_SxFCR_DMDIS));
22
    DMA1_Stream3->FCR &= (~(DMA_SxFCR_FEIE | DMA_SxFCR_DMDIS));
23
24
    DMA1_Stream3->CR  |=  DMA_SxCR_EN;
25
    DMA1_Stream4->CR  |=  DMA_SxCR_EN;

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.