Forum: Mikrocontroller und Digitale Elektronik STM32 feature DMAMUX


von Markus M. (adrock)


Lesenswert?

Hi,

ich habe beim Stöbern im STM32G0xx Refmanual den DMAMUX gefunden.

Kann mir jmd. in verständlichen Worten erklären, was man damit genau 
machen kann?

Ich habe mir die englischsprachige Präsentation und die AN5224 
angesehen, aber so ganz verstehe ich es noch nicht.

Gut, man kann über externe Eingänge wie z.B. EXTI0 oder auch über einen 
Timer dann DMA-Transfers triggern.

Aber was noch? Speziell das "Multiplexing" erschließt sich mir noch 
nicht.

Gruß
Markus

von Kevin M. (arduinolover)


Lesenswert?

Ohne nachgelesen zu haben meine ich das es darum geht jeden DMA Kanal 
für jede Peripherie nutzen zu können, wenn ich mich richtig erinnere.

Das geht bei den älteren STM32 nicht, da konnten für SPI z.B. nur 
gewisse DMA Kanäle genutzt werden.

von Ben S. (bensch123)


Lesenswert?

Beim neuen STM32WB55 nutze ich diesen. Damit kann ich quasi auf einen 
DMA Kanal eine beliebige Peripherie mappen.

Antwort ohne Gewehr (peng). Aber eine Begrenzung ist mir spontan nicht 
bekannt.

von Markus M. (adrock)


Lesenswert?

Also es ist offenbar tatsächlich das (freie) Mapping zwischen der 
Peripherie und einem DMA-Kanal, zusätzlich die anderen Zusatzfunktionen 
wie triggern durch externes Event etc.

So wie ich es sehe bzw. ausprobiert habe, MUSS man den sogar benutzen, 
sonst kommt der DMA-Request von der Peripherie garnicht beim DMA-Kanal 
an.

: Bearbeitet durch User
von ES (Gast)


Lesenswert?

Hi

Ich versuche aktuell meinen SPI Treiber DMA-gesteuert mit meinem 
STM32G031 zum Laufen zu bringen. Aktuell tut sich da leider noch gar 
nichts und ich habe den Verdacht, dass die Peripheral Request nicht 
wirklich beim DMA Controller ankommt und die einzige Geschichte, die ich 
noch nicht so wirklich verstanden habe ist diejenige des DMAMUX.

Kurz zum Verständnis, wenn ich für SPI1_Rx den Channel 3 vom DMA und für 
SPI1_Tx den Channel 4 vom DMA verwenden will, muss ich dann im DMAMUX 
request line multiplexer channel x configuration register (DMAMUX_CxCR) 
ebenfalls die Channels 3 und 4 nutzen oder fungieren die unabhängig 
voneinander?

SPI1_Rx und SPI1_Tx haben gemäss Reference Manual den DMA request MUX 
input 16 bzw 17. Folglich müsste meiner Meinung nach eine 
Initialisierung des Registers so aussehen:
1
DMAMUX1_Channel3->CCR |= DMAMUX_CxCR_DMAREQ_ID_4 | DMAMUX_CxCR_EGE;
2
DMAMUX1_Channel4->CCR |= DMAMUX_CxCR_DMAREQ_ID_4 | DMAMUX_CxCR_DMAREQ_ID_0 | DMAMUX_CxCR_EGE;

Zumindest habe ich aus dem Reference Manual verstanden, dass das so 
genügen dürfte, weiss allerdings auch nicht, ob der Wurm wirklich hier 
begraben ist, ich nehme an es gibt keine Möglichkeit zu überprüfen, ob 
vom SPI eine Request am DMA ankommt

von Markus M. (adrock)


Lesenswert?

Okay, hier mal mein Code (ist nur SPI Übertragung, Empfang dürfte dann 
aber entsprechend gehen):

Initialisierung, nicht die Clocks vergessen!
1
void spi_init(void) {
2
3
    SET_BIT(RCC->IOPENR, RCC_IOPENR_GPIOAEN_Msk);   // Enable GPIOA clock
4
    SET_BIT(RCC->APBENR2, RCC_APBENR2_SPI1EN_Msk);  // Enable SPI1 clock
5
    SET_BIT(RCC->AHBENR, RCC_AHBENR_DMA1EN_Msk);    // Enable DMA1 clock
6
7
    // Initalize GPIO
8
9
    MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE12_Msk, GPIO_MODER_MODE12_1);  // PA12 == AF
10
//    SET_BIT(GPIOA->OTYPER, GPIO_OTYPER_OT12_Msk);   // PA12 == OpenDrain
11
    CLEAR_BIT(GPIOA->AFR[1], GPIO_AFRH_AFSEL12_Msk); // AF0 == SPI1 MOSI
12
13
    // Initialize SPI
14
15
    WRITE_REG(SPI1->CR1, SPI_CR1_SSM_Msk|SPI_CR1_SSI_Msk|(0b010<<SPI_CR1_BR_Pos)|
16
        SPI_CR1_SPE_Msk|SPI_CR1_MSTR_Msk);
17
    MODIFY_REG(SPI1->CR2, SPI_CR2_DS_Msk, (7<<SPI_CR2_DS_Pos));
18
}

Übetragung:
1
/** \brief Transmit SPI data block by using DMA
2
 *
3
 * \param data    Pointer to data
4
 * \param size    Size of data to send
5
 */
6
void spi_txdma(uint8_t *data, uint16_t size) {
7
8
    // Disable DMA first and clear any status flags, just in case
9
10
    CLEAR_BIT(SPI1->CR2, SPI_CR2_TXDMAEN_Msk);
11
    CLEAR_BIT(DMA1_Channel1->CCR, DMA_CCR_EN_Msk);
12
    SET_BIT(DMA1->IFCR, DMA_IFCR_CGIF1_Msk);
13
14
    // Setup DMA channel #1 as mem -> peripheral
15
16
    WRITE_REG(DMA1_Channel1->CPAR, ((uint32_t) &(SPI1->DR)) + 1);
17
    WRITE_REG(DMA1_Channel1->CMAR, (uint32_t) data);
18
    WRITE_REG(DMA1_Channel1->CNDTR, size);
19
    WRITE_REG(DMA1_Channel1->CCR, DMA_CCR_MINC_Msk|DMA_CCR_DIR_Msk);
20
    WRITE_REG(DMAMUX1_Channel0->CCR, 17); // Assign SPI1 TX to DMA1_Channel0 using DMAMUX
21
    SET_BIT(DMA1_Channel1->CCR, DMA_CCR_EN_Msk);
22
23
    // Start new transfer
24
25
    SET_BIT(SPI1->CR2, SPI_CR2_TXDMAEN_Msk);
26
}

Ist also so ähnlich wie bei Dir und funktioniert.

: Bearbeitet durch User
von ES (Gast)


Lesenswert?

Hmm vielleicht übernehme ich das mal so und schaue, was passiert. Kann 
ja aktuell nicht mal transmitten.
1
WRITE_REG(DMA1_Channel1->CPAR, ((uint32_t) &(SPI1->DR)) + 1);

Die Zeile verstehe ich nicht ganz, warum "+1", ich nahm an im CPAR 
Register soll die Adresse des Datenregisters des entsprechenden 
Peripherals (In unserem Fall ja das SPI Datenregister) gespeichert 
werden?

von ES (Gast)


Lesenswert?

Der Code funktioniert leider ebenfalls nicht. Bei mir sieht das 
folgendermassen aus:
1
void spi_init(void)
2
{
3
// enable DMA1, SPI1 Clock
4
    SET_BIT(RCC->IOPENR, RCC_IOPENR_GPIOAEN);     // Enable GPIOA clock
5
    SET_BIT(RCC->AHBENR, RCC_AHBENR_DMA1EN);      // DMA1 clock enable
6
    SET_BIT(RCC->APBENR2, RCC_APBENR2_SPI1EN);    // SPI1 clock enable
7
    
8
    // Configuration of SPI
9
    // Configuring GPIO for MOSI, MISO and SCK pins
10
    MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE5, GPIO_MODER_MODE5_1);
11
    MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE6, GPIO_MODER_MODE6_1); 
12
    MODIFY_REG(GPIOA->MODER, GPIO_MODER_MODE7, GPIO_MODER_MODE7_1);
13
    WRITE_REG(GPIOA->AFR[0], 0x0);  // Connecting the I/Os PA5-PA7 to the 
14
                                       desired alternate function (AF0)
15
16
    // Writing to the SPI_CR1 register
17
    WRITE_REG(SPI1->CR1, SPI_CR1_BR_0 + SPI_CR1_MSTR);
18
19
    // Writing to the SPI_CR2 register
20
    WRITE_REG(SPI1->CR2, SPI_CR2_FRXTH + SPI_CR2_SSOE + SPI_CR2_TXDMAEN +        
21
              SPI_CR2_RXDMAEN);
22
23
    // Write proper DMA registers: Configure DMA streams dedicated for SPI 
24
    // Tx and Rx in DMA registers
25
    // Configuration of DMA
26
    // Set peripheral register address int the DMA_CPARx register
27
    WRITE_REG(DMA1_Channel3->CPAR, (uint32_t)&SPI1->DR);
28
    WRITE_REG(DMA1_Channel4->CPAR, (uint32_t)&SPI1->DR);
29
30
    // Configure the parameters in the DMA_CCRx register
31
    // For Receive Channel RX
32
    WRITE_REG(DMA1_Channel3->CCR, DMA_CCR_PL_1 + DMA_CCR_MINC +  
33
              DMA_CCR_TCIE);
34
35
    // For Transmit Channel TX
36
    WRITE_REG(DMA1_Channel4->CCR, DMA_CCR_DIR +  DMA_CCR_MINC);
37
38
    // DMA Channel Remap
39
    MODIFY_REG(DMAMUX1_Channel3->CCR, DMAMUX_CxCR_DMAREQ_ID, 16);
40
    SET_BIT(DMAMUX1_Channel3->CCR, DMAMUX_CxCR_EGE);
41
    MODIFY_REG(DMAMUX1_Channel4->CCR, DMAMUX_CxCR_DMAREQ_ID, 17);
42
    SET_BIT(DMAMUX1_Channel4->CCR, DMAMUX_CxCR_EGE);
43
44
    NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);   // Enable DMA Interrupt
45
}
1
uint32_t spi_transfer(uint8_t* pu8_dataTransmit, uint8_t* pu8_dataReceive, uint16_t u16_count)
2
{
3
    SET_BIT(SPI1->CR1,SPI_CR1_SPE);           // SPI enable
4
5
    // CMAR --> Channel x memory address register
6
    // CMAR contains the base address of the memory from/to which the data 
7
    // will be read/written
8
    WRITE_REG(DMA1_Channel3->CMAR, (uint32_t)pu8_dataReceive);
9
    WRITE_REG(DMA1_Channel4->CMAR, (uint32_t)pu8_dataTransmit);
10
11
    // Number of data to transfer (0 to 2^16 - 1)
12
    WRITE_REG(DMA1_Channel3->CNDTR, u16_count);
13
    WRITE_REG(DMA1_Channel4->CNDTR, u16_count);
14
15
    SET_BIT(DMA1_Channel3->CCR, DMA_CCR_EN);  // enable RX DMA
16
    SET_BIT(DMA1_Channel4->CCR, DMA_CCR_EN);  // enable TX DMA
17
18
    CLEAR_BIT(DMA1_Channel3->CCR, DMA_CCR_EN);  // disable RX DMA    
19
    CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR_EN);  // disable TX DMA
20
21
    CLEAR_BIT(SPI1->CR1,SPI_CR1_SPE);           // SPI disable
22
}

Ich habe es mir vorerst noch erspart, irgendwelche RXNE/TXE oder BSY 
Bits abzufragen, denn noch findet ohnehin keine Kommunikation statt, die 
Bits sind also so oder so nicht gesetzt. Ich vermute nach wie vor, das 
beim DMA Controller gar keine Request ankommt, da das TXE bit gesetzt 
bleibt, der Buffer also stets empty ist und offenbar keine Daten ins SPI 
Datenregister geschrieben werden.

von Markus M. (adrock)


Lesenswert?

ES schrieb:

>
1
> WRITE_REG(DMA1_Channel1->CPAR, ((uint32_t) &(SPI1->DR)) + 1);
2
>
>
> Die Zeile verstehe ich nicht ganz, warum "+1", ich nahm an im CPAR
> Register soll die Adresse des Datenregisters des entsprechenden
> Peripherals (In unserem Fall ja das SPI Datenregister) gespeichert
> werden?

Stimmt. Eigentlich ist es tatsächlich falsch bzw. funktioniert wohl 
trotzdem aufgrund der Gutmütigkeit der HW in meinem Projekt (ich benutze 
diesen Code seit vielen Stunden in einem Deko-Projekt mit WS2812-LEDs 
ohne jeglichen Fehler).

Ich glaube mein Gedanke war, dass ich ja nur 8-Bit übertrage, das 
Register aber 16-Bit groß ist. Mir war allerdings entgangen, dass der 
Speicher als little endian organisiert ist, also die Bits 0-7 des 
Registers tatsächlich im ersten Byte liegen.

Muss ich bei Gelegenheit mal testen ob es ohne das "+1" bei mir auch 
noch funktioniert.

von Markus M. (adrock)


Lesenswert?

Also ich habe das "+1" tatsächlich aus meinem Code entfernen können und 
er funktioniert nach wie vor problemlos.

Da scheint die Hardware intelligent genug zu sein, je nachdem ob man in 
das High-oder Low-Byte schreibt wird das dann entsprechend in den TX 
FiFo übernommen.
1
    SET_BIT(DMA1_Channel3->CCR, DMA_CCR_EN);  // enable RX DMA
2
    SET_BIT(DMA1_Channel4->CCR, DMA_CCR_EN);  // enable TX DMA
3
    CLEAR_BIT(DMA1_Channel3->CCR, DMA_CCR_EN);  // disable RX DMA    
4
    CLEAR_BIT(DMA1_Channel4->CCR, DMA_CCR_EN);  // disable TX DMA

Warum löscht Du denn eigentlich die Enable-Bits gleich wieder nachdem Du 
sie gesetzt hast? Da kann es doch eigentlich garnicht funktionieren.

Du müsstest schon warten bis der DMA-Transfer fertig ist.

von RG (Gast)


Lesenswert?

Meinen Dank in die Runde, diese Antworten Code-Schnipsel haben auch mir 
geholfen. Da ich ein leicht anderes Problem hatte (und Google mich 
hierher geführt hat), möchte ich dieses explizit betonen:

Bekannt ist, dass je ein Dmamux-Kanal hart verdrahtet ist mit je einem 
Dma-Kanal. Bei älteren Stm32Fxxx gibt es salopp gesagt einen Dma-Kanal, 
der nur Spi kann, und einen weiteren für Uart. Die G-Familie hat einen 
Dmamux, wo jeder Dma-Kanal die zugehörige Peripherie einstellbar hat, 
mittels DMAMUX > CxCR > DMAREQ_ID.

Woran ist es bei mir gescheitert? Hier ein Auszug aus der stm32g031xx.h:
1
#define DMA1_Channel1       ((DMA_Channel_TypeDef *) DMA1_Channel1_BASE)
2
#define DMA1_Channel2       ((DMA_Channel_TypeDef *) DMA1_Channel2_BASE)
3
#define DMA1_Channel3       ((DMA_Channel_TypeDef *) DMA1_Channel3_BASE)
4
#define DMA1_Channel4       ((DMA_Channel_TypeDef *) DMA1_Channel4_BASE)
5
#define DMA1_Channel5       ((DMA_Channel_TypeDef *) DMA1_Channel5_BASE)
6
#define DMAMUX1                ((DMAMUX_Channel_TypeDef *) DMAMUX1_BASE)
7
#define DMAMUX1_Channel0       ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel0_BASE)
8
#define DMAMUX1_Channel1       ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel1_BASE)
9
#define DMAMUX1_Channel2       ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel2_BASE)
10
#define DMAMUX1_Channel3       ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel3_BASE)
11
#define DMAMUX1_Channel4       ((DMAMUX_Channel_TypeDef *) DMAMUX1_Channel4_BASE)

DMA1_Channel1 ist hart verdrahtet mit DMAMUX1_Channel0, DMA1_Channel5 
ist hart verdrahtet mit DMAMUX1_Channel4, allgemein: DMA1_Channel[x+1] 
ist hart verdrahtet mit DMAMUX1_Channel[x]. Umgekehrt: Ich habe 
DMA1_Channel1 und DMAMUX1_Channel1 (sowie jew. 4) konfiguriert, der 
Compiler kann den Fehler nicht sehen und meine Events laufen ins Leere.

In meiner spi.h ist das wie folgt gelöst:
1
#define DMA_Channel_BP_Rx DMA1_Channel1
2
#define DMA_Channel_BP_Tx DMA1_Channel4
3
#define DMA_Channel_BP_Rx_IRQn DMA1_Channel1_IRQn
4
// Note, that stm32g031xx.h enumerates DMA1_Channel1..5 and DMAMUX1_Channel0..4 so DMAMUX1_Channel(x) applies to DMA1_Channel(x+1)
5
#define DMAMUX_Channel_BP_Rx DMAMUX1_Channel0
6
#define DMAMUX_Channel_BP_Tx DMAMUX1_Channel3
7
8
#define BP_Rx_IRQHandler DMA1_Channel1_IRQHandler
9
#define DMA_ISR_BP_Rx DMA_ISR_TCIF1
10
#define DMA_IFCR_BP_Rx DMA_IFCR_CTCIF1

Notiz am Rande: Die Low-Level-Funktionen von CubeMx haben ja durchaus 
ihre Berechtigung. Geschickt ist, dass die LL_DMA_SetPeriphRequest() auf 
den Dmamux zielt, aber als Parameter den Dma-Kanal nutzt. Damit steht in 
der spi.c steht immer der gleiche Kanal, also niemals ein "+1". Das 
funktioniert, weil die Werte der defines alle beim Offset 0 starten.

Interessant, dass ich auch einen Fehler mit diesem "+1" hatte.

Nochmals Danke in die Runde! Ich hoffe, dass meine Antwort hier 
niemandem hilft und euch mein Ärger erspart blieb.

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.