Forum: Mikrocontroller und Digitale Elektronik [STM32f4] I2S mit DMA


von A. M. (am85)


Lesenswert?

Hi

Ich habe ein digitales MEMS Mikrofon, dass ich gerne an das 
STM32f4-Discovery über I2S anbinden möchte. Die Daten sollen über DMA in 
den Speicher geschrieben werden. Die Samples haben eine Breite von 24 
Bit und werden entsprechend in einer 32 Bit long Variable gespeichert. 
Die Daten sollen noch auf dem Controller verarbeitet werden, weswegen 
sich ja ein Ringpuffer oder ähnliches anbietet. Ich habe das selbe schon 
auf einen M3 von TI realisiert. Dort betreiben ich den DMA im Ping-Pong 
Modus, mit einer primären und sekundären DMA-Konfiguration, und habe 
einen Software Ringpuffer. Dort ist es so, dass wenn der I2S FIFO voll 
ist, dann wird ein DMA Request ausgelöst, was wiederum den I2S Interrupt 
auslöst und die I2S ISR aufgerufen wird. In dieser ISR prüfe ich dann 
beide DMA Konfiguration (eben "Ping" oder "Pong" ;-) ) darauf, ob sie 
angehalten wurden, weil die definierte Anzahl an zu übertragenden 
Elementen vollständig übertragen wurden. Ist das der Fall, dann 
konfiguriere ich einen Transfer mit neuer Zieladresse und gebe den 
Transfer frei, so dass er gestartet wird, wenn der andere Transfer 
abgeschlossen ist, u.s.w. Das ganze sieht dann für das Teil von TI so 
aus:

Einmal die Konfiguration des DMA
1
void uDMAInit()
2
{
3
4
  /**
5
   * Enable the uDMA controller at the system level.  Enable it to continue
6
   * to run while the processor is in sleep.
7
   */
8
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
9
    SysCtlPeripheralSleepEnable(SYSCTL_PERIPH_UDMA);
10
11
  /*
12
   * Aktiviert den uDMA Controller
13
   */
14
    uDMAEnable();
15
16
  /*
17
   * Setzt den Pointer auf den Speicher für die Kontrollstruktur
18
   */
19
    uDMAControlBaseSet(uDMAControlTable);
20
21
//    ROM_uDMAChannelAttributeDisable(UDMA_CHANNEL_UART1TX,
22
//                                    UDMA_ATTR_ALTSELECT |
23
//                                    UDMA_ATTR_HIGH_PRIORITY |
24
//                                    UDMA_ATTR_REQMASK);
25
  /*
26
   * I2S unterstützt nur den Burst-Mode und da der I2SRx-Kanal nur die Priorität 28 hat,
27
   * wird das Attribut "high priority" gesetzt
28
   */
29
    uDMAChannelAttributeEnable(UDMA_CHANNEL_I2S0RX, UDMA_ATTR_USEBURST | UDMA_ATTR_HIGH_PRIORITY);
30
31
  /*
32
   * Konfiguration für die primäre Kontrollstruktur des uDMA für den I2SRx Kanal (Puffer 1)
33
   * - uDMA Blockgröße: 32 Bit (I2S Datengröße ist 24 Bit)
34
   * - Keine Inkrementierung der Quelladresse, da es sich um ein Register handelt
35
   * - Inkrement der Zieladresse ist 32 Bit
36
   * - Anzahl der Blöcke, bis der uDMA auf neue Requests reagiert: 8
37
   */
38
    uDMAChannelControlSet(UDMA_CHANNEL_I2S0RX | UDMA_PRI_SELECT,
39
              UDMA_SIZE_32 | UDMA_SRC_INC_NONE | UDMA_DST_INC_32 | UDMA_ARB_8);
40
41
  /*
42
   * Konfiguration für die alternative Kontrollstruktur des uDMA für den I2SRx Kanal (Puffer 2)
43
   * - uDMA Blockgröße: 32 Bit (I2S Datengröße ist 24 Bit)
44
   * - Keine Inkrementierung der Quelladresse, da es sich um ein Register handelt
45
   * - Inkrement der Zieladresse ist 32 Bit
46
   * - Anzahl der Blöcke, bis der uDMA auf neue Requests reagiert: 8
47
   *     -> Anpassung notwending, wenn mehrere Peripheriegeräte per uDMA arbeiten (Priorisierung,
48
   *       siehe Datenblatt lm3s9b92 Januar 2012, Kapitel 7.2.3, S. 347)
49
   */
50
    uDMAChannelControlSet(UDMA_CHANNEL_I2S0RX | UDMA_ALT_SELECT,
51
              UDMA_SIZE_32 | UDMA_SRC_INC_NONE | UDMA_DST_INC_32 | UDMA_ARB_8);
52
53
  /*
54
   * Konfiguriert die Übertragungsparamter für den ersten Transfer des I2SRx Kanals für den uDMA
55
   * Primäre Kontrollstruktur (Ping Pong Modus, Buffer1), Datenquelle ist der I2SRx FIFO
56
   */
57
  uDMAChannelTransferSet(UDMA_CHANNEL_I2S0RX | UDMA_PRI_SELECT,
58
              UDMA_MODE_PINGPONG, (void*) I2S_SOURCE,
59
              (void*)I2SPuffer[0], BUFFER_SIZE);
60
  /*
61
   * Konfiguriert die Übertragungsparamter für den ersten Transfer des I2SRx Kanals für den uDMA
62
   * Alternative Kontrollstruktur (Ping Pong Modus, Buffer2), Datenquelle ist der I2SRx FIFO
63
   */
64
  uDMAChannelTransferSet(UDMA_CHANNEL_I2S0RX | UDMA_ALT_SELECT,
65
              UDMA_MODE_PINGPONG, (void*) I2S_SOURCE,
66
              (void*)I2SPuffer[1], BUFFER_SIZE);
67
68
  /*
69
   * Startet den uDMA Kanal für die I2S Schnittstelle
70
   */
71
    uDMAChannelEnable(UDMA_CHANNEL_I2S0RX);
72
73
    //
74
    // Enable the uDMA controller error interrupt.  This interrupt will occur
75
    // if there is a bus error during a transfer.
76
    //
77
    IntEnable(INT_UDMAERR);
78
}

Dann die I2S ISR
1
void I2SIntHandler(void)
2
{
3
4
  //UARTprintf("I2S ISR\n");
5
6
  unsigned long ulStatus;
7
    unsigned long ulMode;
8
9
    // Interruptstatus abfragen
10
    ulStatus = I2SIntStatus(I2S0_BASE , 1);
11
12
    //Interruptquelle löschen, damit der Handler am Ende nicht erneut aufgerufen wird
13
    I2SIntClear(I2S0_BASE, ulStatus);
14
15
16
    // Status des uDMA I2S Kanals abfragen, primäre Kontrollstruktur
17
    ulMode = uDMAChannelModeGet(UDMA_CHANNEL_I2S0RX | UDMA_PRI_SELECT);
18
19
    // Wenn der uDMA den Transfer abgeschlossen hat, stoppt er automatisch
20
    if(ulMode == UDMA_MODE_STOP)
21
    {
22
23
      oldIndex = (index-1) & (RING_BUFFER_SIZE-1);
24
25
      /*
26
       * Konfiguriert die Übertragungsparamter für den nächsten Transfer des I2SRx Kanals für den uDMA
27
       * Primäre Kontrollstruktur (Ping Pong Modus, Buffer1), Datenquelle ist der I2SRx FIFO
28
       */
29
      uDMAChannelTransferSet(UDMA_CHANNEL_I2S0RX | UDMA_PRI_SELECT,
30
                  UDMA_MODE_PINGPONG, (void*) I2S_SOURCE,
31
                  (void*)I2SPuffer[index], BUFFER_SIZE);
32
33
34
      uDMAChannelEnable(UDMA_CHANNEL_I2S0RX);
35
36
      index++;
37
      index = index & (RING_BUFFER_SIZE-1);
38
      bufferVoll = 1;
39
    }
40
41
42
    // Status des uDMA I2S Kanals abfragen, primäre Kontrollstruktur
43
    ulMode = uDMAChannelModeGet(UDMA_CHANNEL_I2S0RX | UDMA_ALT_SELECT);
44
45
    // Wenn der uDMA den Transfer abgeschlossen hat, stoppt er automatisch
46
    if(ulMode == UDMA_MODE_STOP)
47
    {
48
49
      oldIndex = (index-1) & (RING_BUFFER_SIZE-1);
50
51
      /*
52
       * Konfiguriert die Übertragungsparamter für den nächsten Transfer des I2SRx Kanals für den uDMA
53
       * Primäre Kontrollstruktur (Ping Pong Modus, Buffer1), Datenquelle ist der I2SRx FIFO
54
       */
55
      uDMAChannelTransferSet(UDMA_CHANNEL_I2S0RX | UDMA_ALT_SELECT,
56
                  UDMA_MODE_PINGPONG, (void*) I2S_SOURCE,
57
                  (void*)I2SPuffer[index], BUFFER_SIZE);
58
59
60
      uDMAChannelEnable(UDMA_CHANNEL_I2S0RX);
61
      index++;
62
      index = index & (RING_BUFFER_SIZE-1);
63
      bufferVoll = 1;
64
    }
65
}

So, nun bin ich neu, was die STM32 Controller angeht und blicke noch 
nicht vollständig durch die Konfiguration und deren Eigenheiten durch. 
Wie läuft das dort ab? Kann ich da im Prinzip den selben weg gehen? 
Welche ISR wird dort aufgerufen, wenn ein Transfer beendet ist? Die vom 
entsprechenden DMA Stream oder auch die vom I2S? Wie sollte der DMA 
Controller sinnvoll und äquivalent zu dem vom TI konfiguriert werden?

Die nachvollgenden Codeausschnitte spiegeln meine bisherige 
Konfiguration wieder. Macht die in diesem Szenario so Sinn?

Das ist die DMA Konfiguration
1
#define BUFFERSIZE 48
2
#define RINGBUFFERSIZE 8
3
4
volatile signed long I2SPuffer[RINGBUFFERSIZE][BUFFERSIZE];
5
6
void DMAInit()
7
{
8
  DMA_InitTypeDef DMA_InitStructure;
9
  NVIC_InitTypeDef NVIC_InitStructure;
10
11
12
  // DMA 1 mit Takt versorgen
13
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
14
15
16
  /*
17
   * DMA konfigurieren
18
   * SPI2_RX (=I2S_RX): DMA1 Kanal 0, Stream 3
19
   * Double Buffer Mode (Circular Mode muss enable sein)
20
   */
21
  DMA_InitStructure.DMA_Channel = DMA_Channel_0;
22
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
23
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
24
  DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Word;
25
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
26
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
27
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
28
  DMA_InitStructure.DMA_PeripheralBaseAddr = ((unsigned long)SPI2->DR);
29
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
30
  DMA_InitStructure.DMA_BufferSize = 48;
31
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC8;
32
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
33
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
34
  DMA_InitStructure.DMA_Memory0BaseAddr = (signed long) &I2SPuffer[0];
35
36
37
  DMA_Init(DMA1_Stream3,&DMA_InitStructure);
38
39
40
  DMA_DoubleBufferModeConfig(DMA1_Stream3, (signed long) &I2SPuffer[1], DMA_Memory_0);
41
42
  // Double buffer mode aktivieren
43
  DMA_DoubleBufferModeCmd(DMA1_Stream3, ENABLE);
44
45
  // Transfer complete interrupt aktivieren
46
  DMA_ITConfig(DMA1_Stream3, DMA_IT_TC, ENABLE);
47
48
49
  /* Configure and enable DMA interrupt */
50
  NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn;
51
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
52
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
53
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
54
  NVIC_Init(&NVIC_InitStructure);
55
}

Und das als Ergänzung die für die I2S Schnittstelle
1
void I2SInit()
2
{
3
  // I2S und GPIO TypDefs
4
  GPIO_InitTypeDef I2S_GPIOInitStructure;
5
  I2S_InitTypeDef I2S_InitStructure;
6
7
  // Port B mit Takt versorgen
8
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
9
10
  // SPI2 mit Takt versorgen
11
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
12
13
  // Hochgenaue PLL für I2S aktivieren
14
  RCC_PLLI2SCmd(ENABLE);
15
16
  // GPIO Pins für I2S konfigurieren
17
  I2S_GPIOInitStructure.GPIO_Mode = GPIO_Mode_AF;
18
  I2S_GPIOInitStructure.GPIO_OType = GPIO_OType_PP;
19
  I2S_GPIOInitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
20
  I2S_GPIOInitStructure.GPIO_Speed = GPIO_Speed_50MHz;
21
22
  // PB9 konfigurieren, aktivieren und mit I2S_2_WS verknüpfen
23
  I2S_GPIOInitStructure.GPIO_Pin = GPIO_Pin_9;
24
  GPIO_Init(GPIOB,&I2S_GPIOInitStructure);
25
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_SPI2);
26
27
  // PB10 konfigurieren, aktivieren und mit I2S_2_SCK verknüpfen
28
  I2S_GPIOInitStructure.GPIO_Pin = GPIO_Pin_10;
29
  GPIO_Init(GPIOB,&I2S_GPIOInitStructure);
30
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);
31
32
  // PB15 konfigurieren, aktivieren und mit I2S_2_WS verknüpfen
33
  I2S_GPIOInitStructure.GPIO_Pin = GPIO_Pin_15;
34
  GPIO_Init(GPIOB,&I2S_GPIOInitStructure);
35
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_SPI2);
36
37
38
  // I2S Schnittstelle konfigurieren
39
  I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq_8k;
40
  I2S_InitStructure.I2S_DataFormat = I2S_DataFormat_24b;
41
  I2S_InitStructure.I2S_Mode = I2S_Mode_MasterRx;
42
  I2S_InitStructure.I2S_Standard = I2S_Standard_Phillips;
43
  I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
44
  I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Disable;
45
46
  // I2S Schnittstelle initialisieren
47
  I2S_Init(SPI2, &I2S_InitStructure);
48
49
  // I2S2 DMA Interface aktivieren
50
  SPI_I2S_DMACmd(SPI2,SPI_I2S_DMAReq_Rx,ENABLE);
51
52
  // I2S Schnittstelle aktivieren
53
  I2S_Cmd(SPI2, ENABLE);
54
}

Vielen Dank erst mal für das Lesen bis hier hin. Ich würde mich über 
jede Form von Hilfe freuen, die mich näher zu einer Lösung bringt :-)

von A. M. (am85)


Lesenswert?

Hi

Ich habe mal die DMA ISR für den STM32f4 so geschrieben, wie ich mir 
denke, dass es funktionieren müsste/sollte. Macht das so Sinn?
1
void DMA1_Stream3_IRQHandler()
2
{
3
  // Stream3 transfer complete interrupt?
4
  if(DMA_GetITStatus(DMA1_Stream3,DMA_IT_TCIF3))
5
  {
6
    // clear pending interrupt
7
    DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3);
8
9
    if(DMA_GetCurrentMemoryTarget(DMA1_Stream3))
10
    {
11
      // Memory 1 is current target
12
      DMA_MemoryTargetConfig(DMA1_Stream3,I2SBuffer[BufferIndex],DMA_Memory_0);
13
14
      // New buffer index for the next destination
15
      BufferIndex++;
16
      BufferIndex = BufferIndex & (BUFFERSIZE-1);
17
18
    }
19
    else
20
    {
21
      // Memory 0 is current target
22
      DMA_MemoryTargetConfig(DMA1_Stream3,I2SBuffer[BufferIndex],DMA_Memory_1);
23
24
      // New buffer index for the next destination
25
      BufferIndex++;
26
      BufferIndex = BufferIndex & (BUFFERSIZE-1);
27
    }
28
29
  }
30
31
}

Muss dann eigentlich der DMA neu gestartet werden, wenn der Transfer 
abgeschlossen ist oder läuft der automatisch weiter?

von A. M. (am85)


Lesenswert?

Hat noch jemand einen Tipp?

von Manuel (Gast)


Lesenswert?

Hi,

hast du noch Probleme bei dem Thema bzw. liegt es auf Eis oder hast du 
es bereits verworfen.

Ich hatte ähnliche Probleme mit dem I2S und vl. kann ich da 
weiterhelfen.

- Manuel

von A. M. (am85)


Lesenswert?

Manuel schrieb:
> Hi,
>
> hast du noch Probleme bei dem Thema bzw. liegt es auf Eis oder hast du
> es bereits verworfen.
>
> Ich hatte ähnliche Probleme mit dem I2S und vl. kann ich da
> weiterhelfen.
>
> - Manuel

Interesse hätte ich grundsätzlich schon, allerdings bin ich mittlerweile 
nicht mehr ganz so sehr in der Thematik drin, weil ich das Problem dann 
mit der anderen Plattform gelöst hatte. Vielleicht kannst du mir aber 
einfach noch mal sagen, welche Problematik du hattest und wie du sie 
lösen konntest. Interessieren würde mich eine Lösung grundsätzlich auch 
weiterhin.

Schöne Grüße

von Manuel (Gast)


Lesenswert?

Also grundsätzlich war dein Ansatz der Initialisierung und des 
Interrupts fast richtig.

Bei meinen I2S Routinen hatte ich eine FullDuplex konfiguration.
Senden verlief Problemlos, ich hatte aber Probleme mit dem Datenempfang.

Wichtig ist, dass die Memory Buffer im SRAM definiert werden ansonsten 
geht der DMA in keinen Interrupt (so wars jedenfalls bei mir mit dem 
STM32F405). Warum dass so ist konnt ich leider nicht rausfinden, bzw. 
nahm ich mir nicht die Zeit dazu.
1
__attribute__ ((section(".sram2"))) singed long...
Nur so hat es bei mir funktioniert.



Für mich sieht dein Code bis auf ein paar Stellen funktionstüchtig aus:
Die Zeile
1
DMA_InitStructure.DMA_PeripheralBaseAddr = ((unsigned long)SPI2->DR);
gehört in jedenfall mit dem Adressoperator &
1
DMA_InitStructure.DMA_PeripheralBaseAddr = ((unsigned long)&SPI2->DR);


In der ISR kannst du die Zeilen
1
DMA_MemoryTargetConfig(DMA1_Stream3,I2SBuffer[BufferIndex],DMA_Memory_0);
und
1
DMA_MemoryTargetConfig(DMA1_Stream3,I2SBuffer[BufferIndex],DMA_Memory_1);
weglassen. Das Target auf das Memory wird automatisch bei dem TC 
Interrupt geswitched.


Wie gesagt, ansonsten fällt mir nicht mehr auf und es müsste eigentlich 
funktionieren, ich hatte meine Routinen allerdings im FullDuplexMode und 
als I2S Masters.

LG
 - Manuel

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.