Forum: Mikrocontroller und Digitale Elektronik STM32 USARt3 DMA Rx kopiert keine Daten


von Ada J. Quiroz (inschnier)


Lesenswert?

Hallo,

ich nutze einen STM32F413VGT6 und die USART3. Ich habe einen n Byte 
großen Buffer, in dem circular die empfangenen Daten kopiert werden 
sollen.

In der Main Loop schicke ich sekündlich über die USART Daten raus und 
kann diese mit Putty lesen, das klappt also schonmal.

Ebenfalls zyklische schaue ich, ob NDTR sich verändert hat, also Zeichen 
angekommen sind. Wenn ich in putty ein Zeichen eintippe sehe ich auch, 
dass einmal in die If Verzweigung gesprungen wird und NDTR um +1 
verringert wird, es ist also etwas passiert. Putty schickt kein CR oder 
LF mit. Die richtige Kombi aus Channel und Stream scheine ich auch 
gewählt zu haben, andernfalls sollte ja nichts passieren.

Die DMA Fifo ist beim Start initial deaktiviert.

Es gab mal bei manchen STM32 die Notwendigkeit, irgendwas mit (volatile 
uint8_t*) zu casten, da habe ich aber auch schon rumprobiert.

Im Buffer sind aber nur '\0' zu sehen. Ein Zeichen klassisch ohne DMA zu 
lesen mittels folgendem funktioniert:
1
while (!(oConfig.oUSART->SR & USART_SR_RXNE));
2
return oConfig.oUSART->DR;

Hier ein paar Code Ausschnitte:

USART Initialisierung:
1
    memset(buf_rx, 0, BUFFER_SIZE);
2
3
    DMA1->LIFCR = 0xffffffff;
4
    DMA2->LIFCR = 0xffffffff;
5
6
    dma = DMA1_Stream1;
7
    oUSART = USART3;
8
9
    dma->CR &= ~DMA_SxCR_EN;
10
    dma->CR |= 4 << DMA_SxCR_CHSEL_Pos;
11
    dma->PAR = (uint32_t)&oUSART->DR;
12
    dma->M0AR = (uint32_t)&buf_rx[0];
13
    dma->M1AR = (uint32_t)&buf_rx[0];
14
    dma->NDTR = BUFFER_SIZE;
15
    dma->CR |= DMA_SxCR_PL_1;
16
    dma->CR |= DMA_SxCR_PL_0;
17
    dma->CR |= DMA_SxCR_MINC;
18
    dma->CR |= DMA_SxCR_CIRC;
19
    //dma->CR |= DMA_SxCR_PSIZE_1;
20
    //dma->CR |= DMA_SxCR_MSIZE_1;
21
    dma->CR |= DMA_SxCR_EN;
22
23
    oUSART->CR1 &= ~USART_CR1_UE;
24
    oUSART->CR3 |= USART_CR3_DMAR;
25
    oUSART->CR1 = USART_CR1_TE | USART_CR1_RE;
26
    oUSART->CR1 |= USART_CR1_UE;
27
28
    setBaudrate(oConfig.u32Baudrate);

Main loop:
1
uint16_t USART::dataAvailabe() {
2
    uint16_t z = (BUFFER_SIZE - dma->NDTR);
3
4
    if (pos != z) {
5
        if (z > pos) {
6
            return z - pos;
7
        }
8
        else {
9
            return BUFFER_SIZE - pos + z;
10
        }
11
    }
12
13
    return 0;
14
}
15
16
uint8_t USART::get() {
17
    uint8_t data;
18
    //while (!(oConfig.oUSART->SR & USART_SR_RXNE));
19
    //return oConfig.oUSART->DR;
20
    if (dataAvailabe()) {
21
        data = buf_rx[pos];
22
        pos++;
23
        if (pos >= BUFFER_SIZE) {
24
            pos = 0;
25
        }
26
    }
27
28
    return data;
29
}
30
31
32
while(1){
33
        if(oHardware.oPort[0].dataAvailabe()){
34
            oHardware.oPort[0].send(oHardware.oPort[0].get());
35
        }
36
37
        if(t.Ready()){
38
            oHardware.oPort[0].enable(oHardware.oPort[0].getEnable() ? false : true);
39
40
            t.StartMs(1000);
41
            String s = "ADC: ";
42
            s += oHardware.oPort[0].getVoltage();
43
            s += "mV\r\n";
44
            oHardware.oPort[0].send(s.c_str());
45
        }
46
    }

: Bearbeitet durch User
von Wastl (hartundweichware)


Lesenswert?

Womit hast du denn deinen Code geschrieben? Ausser ein
paar Code-Fragmenten wissen wir (fast) nichts.

Du hast zwar ein Problem (halbwegs) dokumentiert, aber was
soll der geneigte Leser denn damit anfangen? Was wäre denn
dein Anliegen bzw. deine Fragestellung?

Ich sehe nichts von einer Interrupt-Service-Routine die
aufgerufen werden müsste wenn dein DMA-Kanal etwas zu
melden hätte. Das sind - in STM32-Sprachraum - die
Funktionen

void DMA1_Stream1_IRQHandler(void) // Rx Handler
void DMA1_Stream3_IRQHandler(void) // Tx Handler

für den USART3.

von Harald K. (kirnbichler)


Lesenswert?

Ist DMA für UART-Empfang nicht ein kleines bisschen übertrieben? Oder 
hast Du in Deiner eigentlichen Anwendung vor, mit sehr hohen Baudraten 
und großen, mehr oder weniger lückenlos übertragenen Datenblöcken zu 
arbeiten?

Für simple Benutzerinteraktion und sehr, sehr vieles mehr reicht ein 
stinknormaler Interrupthandler, in dem ein paar Zeilen Code empfangene 
Zeichen aus dem Rx-Buffer der UART rausholen und in ein Rx-Fifo 
(Speicher) eintragen.

Dafür braucht's wirklich kein DMA, es sei denn, die eigentliche 
Anwendung auf dem µC ist so ultrahochtimingkritisch, daß selbst ein 
banaler UART-Interrupt das Ding schon durcheinanderbringt.

Dann aber ist im Design eh' schon irgendwas kaputt.

von Wastl (hartundweichware)


Lesenswert?

Harald K. schrieb:
> Ist DMA für UART-Empfang nicht ein kleines bisschen übertrieben?

So ist es, aber das ist ein anderes Thema. Zuerst muss er ja
erst mal ans Funktionieren kommen, dann kommt vielleicht auch
die Einsicht. Wobei ein DMA beim Senden eher sinnvoll ist.

von Andreas B. (abm)


Lesenswert?

"NDT[15:0]: number of data items to transfer (0 up to 65535)
...
This register decrements after each DMA transfer.
Once the transfer is completed, this register can either stay at zero 
(when the stream is in normal mode) or be reloaded automatically with 
the previously programmed value in the following cases:
– when the stream is configured in circular mode.
..."

Sprich: Im Ringbuffer-Modus ist die Prüfung auf Füllstand mittels NDT
nicht sinnvoll.

von J. S. (jojos)


Lesenswert?

Für eine Ringbuffer Implementierung mit DMA gibt es vom Tilen Majerle 
eine gute Erklärung bzw Code:
https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx

von Ada J. Quiroz (inschnier)


Lesenswert?

Wastl schrieb:
> void DMA1_Stream1_IRQHandler(void) // Rx Handler
> void DMA1_Stream3_IRQHandler(void) // Tx Handler

Ich benötige keine Interrupts. Der DMA schreibt in einen Buffer parralel 
und somit kann ich im Hauptzyklus überprüfen, ob neue Daten eingetroffen 
sind.

Natürlich kann ich das auch mit Interrupts, aber wenn ich bis zu 10 
UARTS habe und diese im Programm auch dynamisch neu zuordne möchte ich 
solch eine statische Programmierung vermeiden, denn der Interrupt muss 
immer in Form einer globalen Variable wissen, wo die Daten hin sollen. 
Mache ich das mit DMA kann ich es einfach am Ort des Aufrufs der 
Initialisierung entscheiden und festlegen mit dem PAR Register.

Andreas B. schrieb:
> Sprich: Im Ringbuffer-Modus ist die Prüfung auf Füllstand mittels NDT
> nicht sinnvoll.

Doch, ich merke mir den vorherigen Zustand vom NDTR Register. Eine 
Änderung lässt darauf schließen, dass Daten eingetroffen sind. Es liegt 
an mir, den Buffer genügend groß zu machen.

Aber darum geht es ja nicht. Es ist funktioniert ja im Grunde alles, bei 
jedem Eintreffen wird der DMA scheinbar ausgeführt, denn NDTR wird 
decrementiert, nur werden keine Daten in den Buffer kopiert.

Und ich bin mir sicher, dass ich einfach irgendwo ein Flags in der 
Hardwarekonfiguration übersehen habe.

J. S. schrieb:
> Für eine Ringbuffer Implementierung mit DMA gibt es vom Tilen Majerle
> eine gute Erklärung bzw Code:
> https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx

Schaue ich mir mal an.

: Bearbeitet durch User
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.