Forum: Mikrocontroller und Digitale Elektronik Konzept DMA STM32 Uart


von rosa Schlumpf (Gast)


Lesenswert?

Gu8ten Morgen,
ich denke über eine Anwendung mit einem STM32 nach.
Dazu möchte ich mich das erste mal an STM32 und an dessen DMA wagen.
Also ein Teil der Arbeit des STM32 soll das füttern und das empfangen 
einer Uart sein.
Ich stelle mir das so vor das der STM das Senden über die DMA in Normal 
Mode macht.So zu sagen das ich im Programm den Start für die Übertragung 
des Protokolls gebe und das die Dma dann denn ganzen Block ohne das ich 
etwas davon mitkriege sendet.
Das Empfangen möchte ich dann in Circular Mode machen. Verstehe aber 
nicht ganz wie das läuft? kann man die Buffergröße des Ringpuffers 
einstellen?
Bekomme ich einen Interrupt wenn dieser Buffer voll ist?
Oder wie macht man das ganze richtig?
Danke für Eure Hilfe

von rosa Schlumpf (Gast)


Lesenswert?

ist es ein komplett falscher Ansatz?

von Thomas E. (picalic)


Lesenswert?

rosa Schlumpf schrieb:
> ist es ein komplett falscher Ansatz?

Nö, Dein Ansatz scheint mir soweit schon richtig zu sein.
Die Puffergrösse wird ins DMA CNT-Register geschrieben. Wenn der 
DMA-Kanal im Circular-Mode betrieben wird, wird diese Counter bei jedem 
DMA-Zugriff 'runtergezählt und bei 0 automatisch wieder auf den 
Anfangswert gesetzt.
Die aktuelle DMA-Adresse ist deshalb (Pufferanfang+Pufferlänge - 
DMAx->CNT)

von Tassilo H. (tassilo_h)


Lesenswert?

Wenn die Anzahl der zu empfangenden Zeichen variabel ist, d.h. man kann 
keine feste Buffergröße für den Empfang verwenden, ist ein Ringbuffer 
richtig:

Du stellst ein wie groß der Ringbuffer ist. Die DMA füllt den Ringbuffer 
(permanent).
Dann brachst du noch einen Hintergrundtask, der regelmäßig ausliest, an 
welcher Adresse die DMA als nächstes schreibt, und einen Lesezeiger. 
Wenn Lesezeiger != Schreibadresse, dann hat die DMA mindestens ein Byte 
weggeschrieben, das jetzt ausgelesen werden kann.

Einen Überlauf des Ringbuffers zu erkennen ist schwierig, aber 
normalerweise kann man ihn groß genug machen (größer als das größte am 
Stück zu erwartende Datenpaket).

Etwa so:
1
static void aioEnableRxDMA()
2
{
3
  uint32_t u32;
4
5
  // disable usart rx interrupt
6
  USART2_RXNEIE = 0;
7
  // DMA2 Stream 2 Channel 5
8
  while (DMA1_Stream5->CR & DMA_SxCR_EN) DMA1_Stream5->CR &= ~DMA_SxCR_EN;
9
  u32 = 4ul << 25; // channel 4
10
  // MBURST: single transfer
11
  // PBURST: single transfer
12
  // CT (current target) = 0
13
  // DBM = 0 (no double buffer)
14
  u32 |= DMA_SxCR_PL_1; // priority level 2 (high)
15
  // PINCOS = 0
16
  // MSIZE = 0 (byte)
17
  // PSIZE = 0 (byte)
18
  u32 |= DMA_SxCR_MINC;
19
  // PINC = 0
20
  u32 |= DMA_SxCR_CIRC;
21
  // DIR = 0 Peripheral to memory
22
  // PFCTRL = 0 DMA is flow controller
23
  // TCIE = 0
24
  // HTIE = 0
25
  // TEIE = 0
26
  // DMEIE = 0
27
  DMA1_Stream5->NDTR = AIO_RXBUF_SIZE;
28
  DMA1_Stream5->PAR = (uint32_t)&(USART2->DR);
29
  DMA1_Stream5->M0AR = (uint32_t)&aioRxBuffer[0];
30
  DMA1_Stream5->FCR = 0;
31
  // reset all irq flags
32
  DMA1->HIFCR = DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CFEIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTCIF5 | DMA_HIFCR_CTEIF5;
33
  DMA1_Stream5->CR = u32;
34
  DMA1_Stream5->CR |= DMA_SxCR_EN;
35
  // enable USART RX DMA
36
  USART2->CR3 |= USART_CR3_DMAR;
37
}
38
39
// read byte from DMA ringbuffer, return false if no data available
40
static bool aioReadByte(uint8_t* data)
41
{
42
  uint32_t pos;
43
44
  // is DMA enabled?
45
  if ((DMA1_Stream5->CR & DMA_SxCR_EN) == 0) aioEnableRxDMA();
46
  // see current DMA address
47
  pos = AIO_RXBUF_SIZE - DMA1_Stream5->NDTR;
48
  if (pos >= AIO_RXBUF_SIZE) pos = 0; // this could happen if NDTR is zero and not reloaded yet
49
  if (aioRxRdPos != pos) {
50
    *data = aioRxBuffer[aioRxRdPos++];
51
    aioRxRdPos %= AIO_RXBUF_SIZE;
52
    return true;
53
  } else {
54
    return false;
55
  }
56
}

von rosa Schlumpf (Gast)


Lesenswert?

Thomas E. schrieb:
> Die Puffergrösse wird ins DMA CNT-Register geschrieben.

In CUBEMX finde ich dazu keine Einstellung.
Oder macht man das mit der Funktion HAL_UART_Receive_DMA ?
Wie weiß ich das das ganze Datenpaket des Protokolls angekommen ist?
Gibt es dazu einen Interrupt? Die Cpu müsste dann ja das Protokoll 
abarbeiten. Bestenfalls schneller als neue Daten eintrudeln.

Danke Lg

von rosa Schlumpf (Gast)


Lesenswert?

Tassilo H. schrieb:
> Wenn die Anzahl der zu empfangenden Zeichen variabel ist, d.h. man kann
> keine feste Buffergröße für den Empfang verwenden, ist ein Ringbuffer
> richtig:

Wenn das protokoll eine feste Länge hat dann nicht?

von Tassilo H. (tassilo_h)


Lesenswert?

rosa Schlumpf schrieb:
> Tassilo H. schrieb:
>> Wenn die Anzahl der zu empfangenden Zeichen variabel ist, d.h. man kann
>> keine feste Buffergröße für den Empfang verwenden, ist ein Ringbuffer
>> richtig:
>
> Wenn das protokoll eine feste Länge hat dann nicht?

Dann könnte man auch DMA auf einen Buffer eben dieser bekannten Länge 
machen. Wenn der DMA-Fertig-Interrupt kommt, ist der Datenblock da, man 
spart sich das periodische Nachsehen und kann evtl. die Daten gleich an 
ihre endgültige Zielposition schreiben lassen.
Dinge wie zu kurze Blöcke muß man dann per Timeout erkennen. Extra-Bytes 
gehen einfach verloren (oder, wenn es nur eins ist, landen dann am 
Anfang des nächsten Blocks, daher ggf. UART leermachen bevor DMA enabled 
wird).

von Thomas E. (picalic)


Lesenswert?

rosa Schlumpf schrieb:
>> Die Puffergrösse wird ins DMA CNT-Register geschrieben.
>
> In CUBEMX finde ich dazu keine Einstellung.

Sorry, das Register heisst nicht CNT (hatte dessen Funktion, aber nicht 
dessen genauen Namen parat). Der Name scheint sich möglicherweise auch 
bei verschiedenen Controllerfamilien zu unterscheiden, z.B. "NDTR" oder 
"CNDTR".

rosa Schlumpf schrieb:
> Wie weiß ich das das ganze Datenpaket des Protokolls angekommen ist?

Das kann die DMA-Logik natürlich nicht feststellen. Entweder, Du weisst, 
wie lang das zu erwartende Paket ist, dann nimmst Du nicht den 
Circular-Mode und kannst einen IRQ feuern lassen, wenn das Paket 
empfangen wurde, oder Du erwartest einen kontinuierlichen Strom und 
musst halt scannen, was aktuell im Puffer steht.
Vielleicht kannst Du mit den USART-Interrupts was machen, z.B. 
Interrupt, wenn eine Pause auftritt (Idle) oder wenn ein bestimmtes 
Zeichen empfangen wurde (geht nicht bei allen USART-Typen).

: Bearbeitet durch User
von rosa Schlumpf (Gast)


Lesenswert?

Tassilo H. schrieb:
> Wenn der DMA-Fertig-Interrupt kommt,

Ok so was gibt es also:)

Tassilo H. schrieb:
> Dinge wie zu kurze Blöcke muß man dann per Timeout erkennen. Extra-Bytes
> gehen einfach verloren (oder, wenn es nur eins ist, landen dann am
> Anfang des nächsten Blocks, daher ggf. UART leermachen bevor DMA enabled
> wird).

Das ist auch ein guter Tipp Danke!
Gibt es in der Hal schon fertige Funktionen zum Leermachen?

Ok gibt es wie bei den Avr eine Interrupt Tabelle?
Oder wie funktioniert das bei den STM32?


Danke für Eure Hilfe

von Thomas E. (picalic)


Lesenswert?

rosa Schlumpf schrieb:
> Ok gibt es wie bei den Avr eine Interrupt Tabelle?
> Oder wie funktioniert das bei den STM32?

Also, mit der Architektur Deines Controllers wirst Du Dich schon durch 
gründlicheres Einlesen in die Hersteller-Dokumentation befassen müssen! 
Davon wird Dich auch CubeMX und HAL und was es da sonst vielleicht noch 
gibt, nicht entbinden.

Und um die Frage noch kurz zu beantworten: Ja, so eine Tabelle gibt es!
Ganz so einfach, wie beim AVR ist es aber nicht - erstes Stichwort zum 
Thema Interrupts heisst "NVIC".

: Bearbeitet durch User
von STK500-Besitzer (Gast)


Lesenswert?

rosa Schlumpf schrieb:
> Gibt es in der Hal schon fertige Funktionen zum Leermachen?

Such mal nach der "HAL und LL driver description" für deinen Controller.
Da steht das alles drin...
Für jeden Treiber ein eigenes Kapitel.

von rosa Schlumpf (Gast)


Lesenswert?

STK500-Besitzer schrieb:
> Such mal nach der "HAL und LL driver description"

Genau dieses Dokoment habe ich gesucht:)
Wenn man weiß was man suchen soll dann ist das Finden einfach!
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.