Wie ein Mikrocontroller Daten mit 1,6 Gbit / s lesen kann

Guten Tag an alle! es ist nie passiert und hier wieder . Seit meinem letzten Artikel ist genug Zeit vergangen , und es stellt uns vor neue Herausforderungen. Und wenn ich früher Daten mit einer Geschwindigkeit von 100 Mbit / s übertragen wollte, musste ich jetzt mit 1600 Mbit / s schwingen ...

Auf KDPV - dem Helden unseres Romans - konnte er Daten mit einer solchen Geschwindigkeit lesen!



Mein nächstes Projekt verlangte also, einen 32-Bit-Datenstrom mit einer Geschwindigkeit von 50 MHz (dies entspricht übrigens den gleichen 1,6 Gbit / s) in einer im Voraus bekannten Menge zu lesen - sei es 10000. Es wäre in Ordnung, sofort mit DMA von einem Port zu lesen - aber leider gab es keine geeigneten Prozessoren (ich hoffe, jemand korrigiert diese Angelegenheit in den Kommentaren), aus irgendeinem Grund sind alle Ports, die für die Geschwindigkeit geeignet sind, aus irgendeinem Grund 16-Bit.

Aber eine solche Kleinigkeit wird uns nicht aufhalten - wir werden von zwei Ports gleichzeitig lesen! Zwar wird dies im allgemeinen Fall nicht immer mit dem erforderlichen Maß an Kontrolle und Synchronität möglich sein, aber in unserem Fall ist nicht alles so traurig - es gibt ein Signal, nach dem 20 ns Daten auf dem Port gespeichert werden.

Und da der Prozessor, den wir haben, stm32h750 bei 400 MHz und der Bus und die Timer bei 200 MHz ist, sollte alles klappen.

Es scheint ein einfacher Fall zu sein - eine einzelne DMA-Weiterleitung für ein Signal auszulösen. Aber nur DMA hat keine solche Möglichkeit - der Port kann einen Interrupt ausgeben, aber er kann DMA nicht steuern. Aber unser Prozessor hat eine gute Sache - DMAMUX, in dem es einen Ereignisgenerator für den DMA-Kanal gibt, aber dieser Generator hat zwei geeignete Möglichkeiten - entweder den EXTIT0-Interrupt oder das Signal vom TIM12-Timer (dies war eine seltsame Fantasie für die Chip-Entwickler).

Wir haben keine Zeit für Unterbrechungen - wir brauchen sogar ungefähr 47 Taktzyklen, um leer zu arbeiten, und unser Taktzyklus beträgt 2,5 ns ...

Aber rechtzeitig für einen Timer. Es bleibt nur, den Timer von einem externen Signal von 100 MHz anzuheften und die Länge des Timers auf 1 zu setzen. Der TRGO-Ausgang löst den DMAMUX-Generator aus und gibt dann einen Befehl zum Senden des DMA aus. Er liest den Port und sendet die Daten an den Speicher.

Aber hör auf! Der Port ist 16-Bit, aber wir haben 32 ... Nun, Sie können versuchen, einen anderen zweiten Port zu lesen ... Nur dafür benötigen wir einen zweiten DMA-Kanal, und es wird der gleiche Bus benötigt - das heißt, wir werden Zeit zum Lesen haben, aber wir können Sie haben keine Zeit, Daten in den Speicher zu schreiben. Theoretisch hat dieser Prozessor verschiedene Speichertypen, und in einem großen Bild der Prozessorstruktur können Sie sehen, dass sowohl der DMA- als auch der RAM_D1-Speicher mit einer Frequenz von 200 MHz auf demselben Bus sitzen. Es bleibt in der Praxis zu überprüfen.

DMA1->LIFCR |= ~0; DMA1_Stream0->CR = (0b11 << DMA_SxCR_PL_Pos) | (0b01 << DMA_SxCR_MSIZE_Pos) | (0b01 << DMA_SxCR_PSIZE_Pos) | DMA_SxCR_MINC; DMA1_Stream0->M0AR = (uint32_t) data; DMA1_Stream0->PAR = (uint32_t) &(GPIOE->IDR); DMA1_Stream0->NDTR = 10000; DMA1_Stream1->CR = (0b11 << DMA_SxCR_PL_Pos) | (0b01 << DMA_SxCR_MSIZE_Pos) | (0b01 << DMA_SxCR_PSIZE_Pos) | DMA_SxCR_MINC; DMA1_Stream1->M0AR = (uint32_t) data2; DMA1_Stream1->PAR = (uint32_t) &(GPIOD->IDR); DMA1_Stream1->NDTR = 10000; DMAMUX1_Channel0->CCR = DMAMUX_CxCR_EGE | (1); DMAMUX1_Channel1->CCR = DMAMUX_CxCR_EGE | (2); DMAMUX1_RequestGenerator0->RGCR = DMAMUX_RGxCR_GE | (0b01 << DMAMUX_RGxCR_GPOL_Pos) | (7); DMAMUX1_RequestGenerator1->RGCR = DMAMUX_RGxCR_GE | (0b01 << DMAMUX_RGxCR_GPOL_Pos) | (7); DMA1_Stream0->CR |= DMA_SxCR_EN; DMA1_Stream1->CR |= DMA_SxCR_EN; TIM12->CNT = 0; TIM12->CCMR1 |= TIM_CCMR1_CC2S_0; TIM12->CR2 = (0b010 << TIM_CR2_MMS_Pos); TIM12->CR1 |= TIM_CR1_CEN; while (DMA1_Stream0->NDTR) i++; TIM12->CR1 &= ~TIM_CR1_CEN; 

Und natürlich müssen Sie die Daten und data2-Arrays im gewünschten Speichersegment platzieren. Dies geschieht folgendermaßen:

 __attribute__((section(".dma_buffer"))) uint16_t data[10240],data2[10240]; 

und in der Datei für den Linker angeben:

 .dma_buffer : { *(.dma_buffer) } >RAM_D1 

Um dies zu überprüfen, wurde als erste Option nur dummes Kopieren mit implementiert
CPU (immer noch 400 MHz):

  uint16_t * ptr = cpudata; volatile uint16_t * src = &(GPIOE->IDR); volatile uint16_t * src2 = &(GPIOD->IDR); for (register int i = 0; i < 10000; i++) { *ptr++ = *src; *ptr++ = *src2; } 

Zur Überprüfung befanden sich die cpudata-Daten in einem anderen Speicher. Der schnellste (es ist wahr, nur 64 KB) war der schnellste DTCMRAM-Speicher (ebenfalls 400 MHz).

Ergebnisse


Bei den Tests stellte sich heraus, dass mit Hilfe der CPU von zwei Ports mit einer Geschwindigkeit von 12,5 MHz gelesen werden kann. Und 25 MHz von einem. Die Option funktioniert also nicht ...

Mit Hilfe von DMA und einer solchen Mutter konnte TIM12 erfolgreich mit einer Geschwindigkeit von 50 MHz lesen, und in wenigen Stunden gab es keinen einzigen Fehlertest. Beide Ports wurden gelesen, aber es war noch nicht möglich zu messen, wie weit der Messwert auf dem zweiten DMA zurückbleibt ...

In meinem (leicht entarteten) Fall gelang es mir, die Geschwindigkeit der Informationsübertragung zum stm32h750-Prozessor mit einer Geschwindigkeit von 32 x 50 = 1600 Mbit / s zu erreichen.

Source: https://habr.com/ru/post/de467459/


All Articles