Forum: Mikrocontroller und Digitale Elektronik STM32 DMA mit SPI funktioniert nicht


von Leon L. (leonelf)


Lesenswert?

Ich versuche gerade SPI mit DMA zum laufen zu bringen. Der 
DMA2_IRQHandler wird zwar aufgerufen, es ist aber nie ein "DMA_IT_TC". 
Die Base Adress im Register wird auch nicht inkrementiert, obwohl ich es 
auf DMA_MemoryInc_Enable gestellt habe.

Hiermit initialisiere ich DMA:
1
void initDMA(){
2
  RCC->AHB1ENR |= RCC_AHB1Periph_DMA2;
3
4
  DMA_InitTypeDef dmaInitStruct;
5
  dmaInitStruct.DMA_Channel = DMA_Channel_3;
6
  dmaInitStruct.DMA_Memory0BaseAddr = (uint32_t) tlcValues;
7
  dmaInitStruct.DMA_PeripheralBaseAddr = (uint32_t) &(SPI1->DR);
8
  dmaInitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
9
  dmaInitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
10
  dmaInitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;
11
  dmaInitStruct.DMA_Mode = DMA_Mode_Normal;
12
  dmaInitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
13
  dmaInitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
14
  dmaInitStruct.DMA_BufferSize = 288; //192*12bit
15
  dmaInitStruct.DMA_Priority = DMA_Priority_High;
16
  dmaInitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
17
  dmaInitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
18
  dmaInitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
19
20
  DMA_Init(DMA2_Stream3, &dmaInitStruct);
21
  DMA_Cmd(DMA2_Stream3, ENABLE);
22
23
  SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
24
25
26
    NVIC_InitTypeDef nvicInitStruct;
27
    nvicInitStruct.NVIC_IRQChannel = DMA2_Stream3_IRQn;
28
    nvicInitStruct.NVIC_IRQChannelPreemptionPriority = 0;
29
    nvicInitStruct.NVIC_IRQChannelSubPriority = 0;
30
    nvicInitStruct.NVIC_IRQChannelCmd = ENABLE;
31
    NVIC_Init(&nvicInitStruct);
32
33
  DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, ENABLE);
34
}

Und hiermit SPI1:
1
void initSPI(){
2
  RCC->APB2ENR |= RCC_APB2Periph_SPI1;
3
4
  SPI_InitTypeDef spiInitStruct;
5
  spiInitStruct.SPI_Direction = SPI_Direction_1Line_Tx;
6
  spiInitStruct.SPI_Mode = SPI_Mode_Master;
7
  spiInitStruct.SPI_DataSize = SPI_DataSize_8b;
8
  spiInitStruct.SPI_CPOL = SPI_CPOL_Low;
9
  spiInitStruct.SPI_CPHA = SPI_CPHA_1Edge;
10
  spiInitStruct.SPI_NSS = SPI_NSS_Soft;
11
  spiInitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
12
  spiInitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
13
14
  SPI_Init(SPI1, &spiInitStruct);
15
  SPI_Cmd(SPI1, ENABLE);
16
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1);
17
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1);
18
19
  SPI_Init(SPI2, &spiInitStruct);
20
21
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_SPI2);
22
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_SPI2);
23
  SPI_Cmd(SPI2, ENABLE);
24
}

Das hier ist der Interrupt:
1
void DMA2_Stream3_IRQHandler(){
2
  if(DMA_GetFlagStatus(DMA2_Stream3, DMA_IT_TC)){
3
    DMA_ClearITPendingBit(DMA2_Stream3, DMA_IT_TC);
4
    printf("LOL");
5
  }
6
}

Aufgerufen wird er, der breakpoint am if wird aufgerufen. Aber zum 
Clearflag und zum printf kommt es nie.
Aus SPI1 kommen auch keine Daten, das Oszilloskop zeigt nur das Rauschen 
an (0V+-20mV), das auf GND liegt.

Das GPIO ist richtig konfiguriert, da das SPI selbst funktioniert, wenn 
ich es manuell ansteuere. Genutzt wird das STM32F4-Discovery Board

: Bearbeitet durch User
von Little B. (lil-b)


Lesenswert?

Um das Verhalten des Controllers nachzuvollziehen, wäre es schön zu 
wissen, warum die ISR aufgerufen wird.

Ein Transfer Complete Interrupt ist wohl nicht die Ursache. Welches Flag 
ist denn gesetzt?

Leon Loeser schrieb:
> dmaInitStruct.DMA_BufferSize = 288; //192*12bit

das sieht ungewöhnlich aus! sollte aber nicht die fehlerquelle sein.
wie sind denn deine Daten im Speicher abgelegt? nicht im 16-bit Raster?

Leon Loeser schrieb:
> nvicInitStruct.NVIC_IRQChannelPreemptionPriority = 0;
>     nvicInitStruct.NVIC_IRQChannelSubPriority = 0;

Es ist auch ungewöhnlich, dem DMA die höchste Priorität zu geben. Sollte 
geändert werden, muss aber auch nicht die Fehlerquelle sein

von brt (Gast)


Lesenswert?

Hallo,

versuch doch mal mit geringerer SPI-Geschwindigkeit. Hatte gerade ein 
ähnliches Problem, was ich auch noch nicht genauer eruiert habe. Aber ab 
und zu wurde der IRQ nicht mehr aufgerufen, die DMA "hatte sich 
aufgehängt".
Seitdem ich die SPI-Baudrate verringert habe funktioniert es fehlerfrei.
Nur mal so zu Eingrenzen des Problems...

Grüße, Brt

von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

Sorry, ich blicke es spontan nicht. Vielleicht hilft der Hinweis:

> Der RX Interrupt wird ausgelöst, sobald das letzte Byte empfangen
> wurde, der gesamte SPI Transfer also abgeschlossen ist. Jetzt kann
> die Chip Select Leitung wieder auf high gelegt werden. TX sollte in
> keinem Fall für diesen Zweck genutzt werden, weil dieser zu früh
> mit der Arbeit fertig ist.

http://www.diller-technologies.de/stm32.html

Sorry, falls ich daneben liege.

von Leon Loeser (Gast)


Lesenswert?

Ich übertrage it DMA in diesem Fall. Das Datenformat wären 8bit (byte, 
siehe DMA config)

von Herbschi (Gast)


Lesenswert?

Hier ein paar nützliche Link, die weiterhelfen könnten:

http://electronics.stackexchange.com/questions/100685/full-duplex-slave-spi-dma-and-interrupts-on-stm32f103
http://www.eevblog.com/forum/microcontrollers/stm32-spi-with-dma/
http://www.micromouseonline.com/2012/03/11/adding-dma-to-the-spi-driver-with-the-stm32f4/

Und ja, die eingestellte Buffer Size ist falsch. DMA kann nicht die 
Bits im Speicher zusammenschieben oder auseinanderziehen. Umformatieren 
über DMA geht nur zwischen Byte, 16-Bit-Halbwort oder 32-Bit-Wort.

LG Herbert

von Leon L. (leonelf)


Lesenswert?

das mit den 12 byte ist anders gemeint. im speicher liegen 288 bit rum. 
die sind für den TLC 5941, es sind also 12*24 bit. Das wären 36 byte, 
aber es sind 8 tlcs, also 288 byte. Habe das wohl etwas missverständlich 
kommuniziert^^
Es liegt aber im zusammengeschobenen format im Speicher vor^^

von Dieter Graef (Gast)


Lesenswert?

Ich würde vor der Initialisierung die DMA Register in einen definierten 
Zustand bringen z.B. mit

DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_FEIF3 | DMA_FLAG_DMEIF3 | 
DMA_FLAG_TEIF3 | DMA_FLAG_HTIF3 | DMA_FLAG_TCIF3);

  /* Disable DMA2 Stream3 */
  DMA_Cmd(DMA2_Stream3, DISABLE);

  /* Deinit DMA2 Stream3 */
  DMA_DeInit(DMA2_Stream3);

 und dann zwischen init und cmd noch die transfer complete Unterbrechung 
sowie die flow control freischalten z.B. mit

DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, ENABLE);
DMA_FlowControllerConfig(DMA2_Stream3, DMA_FlowCtrl_Peripheral);

von Leon L. (leonelf)


Lesenswert?

Ich habe jetzt mal das deInit und flowcontrol eingefügt. Aktuell zeigt 
er mir im statusregister während dem interrupt an, dass Transfer 
Complete, Half Complete und FiFo-Error auf 1 gesetzt sind.

: Bearbeitet durch User
von Little B. (lil-b)


Lesenswert?

Interessant, dass das FIFO Error Flag gesetzt ist, obwohl du den FIFO 
ausgeschaltet hast...

Leon Loeser schrieb:
> DMA_Cmd(DMA2_Stream3, ENABLE);

setze das mal ganz ans ende, nachdem tatsächlich ALLES initialisiert 
ist.
Dies startet nämlich die DMA übertragung.

Momentan startest du die Übertragung zu einem Zeitpunkt, an dem der 
SPI_DMA_Request noch nicht aktiviert ist. KANN probleme machen, MUSS 
aber nicht.

Ich erwarte natürlich, dass du zuerst den SPI initialisierst, dann den 
DMA.

Leon Loeser schrieb:
> dmaInitStruct.DMA_Memory0BaseAddr = (uint32_t) tlcValues;

Ich gehe hier davon aus, dass tlcValues ein Array oder Pointer ist.

Zusätzlich kannst du noch Probieren, dmaInitStruct.DMA_FIFOThreshold auf 
einen definierten Wert zu setzen. Sollte für die Hardware kein 
Unterschied machen, da der FIFO ja deaktiviert ist, aber vieleicht zickt 
die Init-Routine dabei rum, und du merkst es nicht.
Siehe
1
assert_param(IS_DMA_FIFO_THRESHOLD(DMA_InitStruct->DMA_FIFOThreshold));
in der Init Routine des DMA. Dann wäre dein DMA nicht initialisiert.

Ich habe auch schon viel über den DMA geflucht, der Fehler liegt meist 
im Detail. Vieleicht helfen dir diese Ideen.

~Lil B

von Leon L. (leonelf)


Lesenswert?

Ok, habe es jetzt zum laufen gebracht. (Um genau zu sein, heute morgen 
um halb drei).

initDMA() sieht jetzt so aus:
1
void initDMA(){
2
  RCC->AHB1ENR |= RCC_AHB1Periph_DMA2;
3
4
  DMA_Cmd(DMA2_Stream3, DISABLE);
5
  DMA_DeInit(DMA2_Stream3);
6
7
  dmaInitStruct.DMA_Channel = DMA_Channel_3;
8
  dmaInitStruct.DMA_Memory0BaseAddr = (uint32_t) tlcValues;
9
  dmaInitStruct.DMA_PeripheralBaseAddr = (uint32_t) &(SPI1->DR);
10
  dmaInitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
11
  dmaInitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
12
  dmaInitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;
13
  dmaInitStruct.DMA_Mode = DMA_Mode_Normal;
14
  dmaInitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
15
  dmaInitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
16
  dmaInitStruct.DMA_BufferSize = 288; //192*12bit
17
  dmaInitStruct.DMA_Priority = DMA_Priority_High;
18
  dmaInitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
19
  dmaInitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
20
  dmaInitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
21
22
  DMA_Init(DMA2_Stream3, &dmaInitStruct);
23
24
  DMA_SetCurrDataCounter(DMA2_Stream3, 288);
25
26
  NVIC_InitTypeDef nvicInitStruct;
27
  nvicInitStruct.NVIC_IRQChannel = DMA2_Stream3_IRQn;
28
  nvicInitStruct.NVIC_IRQChannelPreemptionPriority = 1;
29
  nvicInitStruct.NVIC_IRQChannelSubPriority = 0;
30
  nvicInitStruct.NVIC_IRQChannelCmd = ENABLE;
31
  NVIC_Init(&nvicInitStruct);
32
33
  DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, ENABLE);
34
35
  DMA_Cmd(DMA2_Stream3, ENABLE);
36
}

Es wahr wohl wirklich die reihenfolge der Aufrufe.

der IRQHandler sieht dann so aus:
1
void DMA2_Stream3_IRQHandler(){
2
  if(DMA_GetFlagStatus(DMA2_Stream3, DMA_IT_TCIF3)){
3
  DMA_Cmd(DMA2_Stream3, DISABLE);
4
  DMA_SetCurrDataCounter(DMA2_Stream3, 288);
5
  DMA_ClearFlag(DMA2_Stream3, DMA_IT_TCIF3);
6
  DMA_Cmd(DMA2_Stream3, ENABLE);
7
    DMA_ClearITPendingBit(DMA2_Stream3, DMA_IT_TC);
8
  }
9
}

Ich glaube da habe ich die ganze Zeit die Flags und IT Bits verwechselt.
Der Interrupt startet momentan nur die Übertragung neu (proof of 
concept). Das wird dann bald mit einem Timer verbunden. Gedacht ist es, 
um TLC5941 mit daten für nen RGB LED Cube zu füllen^^

Eine Frage hätte ich noch:

All diese Utility Funktionen haben dieses assert_param(...) in sich. Ist 
das für den Compiler? oder wird das während dem Programmablauf 
aufgerufen? Ich frage, da ich auch direkte Registermanipulation nutzen 
würde, falls das schneller ist...

Achja: Ist es möglich, die MemoryAdress zu ändern, ohne das ganze 
initStruct zu nutzen? also einfach ins register schreiben oder so?

: Bearbeitet durch User
von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

Leon Loeser schrieb:
> All diese Utility Funktionen haben dieses assert_param(...) in sich. Ist
> das für den Compiler?

Nein.

> oder wird das während dem Programmablauf
> aufgerufen?

Ja.

> Ich frage, da ich auch direkte Registermanipulation nutzen
> würde, falls das schneller ist...

Das ginge schneller.

> Achja: Ist es möglich, die MemoryAdress zu ändern, ohne das ganze
> initStruct zu nutzen? also einfach ins register schreiben oder so?

Ja, aber vorher den DMA stoppen und anschließend wieder starten.

von Leon L. (leonelf)


Lesenswert?

danke!

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.