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
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)
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 | }
|
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
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?
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).
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
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
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
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.