Comment un microcontrôleur peut lire des données à 1,6 Gbit / s

Bonne journée à tous! ce n'est jamais arrivé et ici encore . Assez de temps s'est écoulé depuis mon dernier article , et cela pose de nouveaux défis. Et si j'avais l'habitude de transférer des données à une vitesse de 100 Mbps, maintenant je devais balancer à 1600 Mbps ...

Sur KDPV - le héros de notre roman - il a pu lire les données à une telle vitesse!



Donc, mon prochain projet a exigé de lire un flux de données 32 bits à une vitesse de 50 MHz (ce sera d'ailleurs le même 1,6 Gbps) dans une quantité connue à l'avance - que ce soit 10000. Ce serait bien de lire immédiatement en utilisant DMA à partir d'un port - mais, malheureusement, il n'y avait pas de processeurs appropriés (j'espère que quelqu'un corrige ce problème dans les commentaires), pour une raison quelconque, tous les ports adaptés à la vitesse sont pour une raison quelconque 16 bits.

Mais une telle bagatelle ne nous arrêtera pas - nous lirons à partir de deux ports à la fois! Certes, cela dans le cas général ne sera pas toujours possible avec le degré de contrôle et de synchronisme nécessaire, mais dans notre cas, tout n'est pas si triste - il y a un signal après lequel 20 ns de données seront conservées sur le port.

Et puisque le processeur que nous avons est stm32h750 à 400 MHz, et le bus et les minuteries à 200 MHz, alors tout devrait fonctionner.

Cela semblerait un cas simple - pour déclencher une seule transmission DMA sur un signal. Mais seul DMA n'a pas une telle opportunité - le port peut émettre une interruption, mais il ne peut pas contrôler DMA. Mais notre processeur a une bonne chose - DMAMUX, dans lequel il y a un générateur d'événements pour le canal DMA, mais ce générateur a deux options appropriées - soit utiliser l'interruption EXTIT0 ou le signal du temporisateur TIM12 (les développeurs de puces avaient un fantasme si étrange).

Nous n'avons pas le temps pour l'interruption - nous avons même besoin d'environ 47 cycles d'horloge pour travailler à vide, et notre cycle d'horloge est de 2,5 ns ...

Mais à temps pour une minuterie. Il ne reste plus qu'à clouer le minuteur à partir d'un signal externe de 100 MHz et à régler la longueur du minuteur à 1 et la sortie TRGO déclenchera le générateur DMAMUX, puis il émettra une commande pour envoyer le DMA et il lira le port et enverra les données en mémoire.

Mais arrête! Le port est de 16 bits, mais nous en avons 32 ... Eh bien, vous pouvez essayer de lire un autre deuxième port ... Seulement pour cela, nous avons besoin d'un deuxième canal DMA, et cela prendra le même bus - c'est-à-dire que nous aurons le temps de lire, mais nous pouvons n'ont pas le temps d'écrire des données en mémoire. Eh bien, théoriquement, ce processeur a différents types de mémoire, et dans une grande image de la structure du processeur, vous pouvez voir que la mémoire DMA et RAM_D1 sont assis sur le même bus avec une fréquence de 200 MHz. Reste à vérifier dans la pratique.

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; 

Et bien sûr, vous devez placer les tableaux de données et data2 dans le segment de mémoire souhaité, cela se fait comme suit:

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

et dans le fichier de l'éditeur de liens, indiquez:

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

Pour vérifier, eh bien, et comme première option, une copie stupide a été implémentée en utilisant
CPU (toujours 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; } 

Pour vérification, les données cpudata étaient situées dans une mémoire différente, la DTCMRAM mémoire la plus rapide (enfin, il est vrai, seulement 64 Ko) était la mémoire la plus rapide (également 400 MHz).

Résultats


Au cours des tests, il s'est avéré qu'avec l'aide du CPU, il était possible de lire à une vitesse de 12,5 MHz à partir de deux ports. Et 25 MHz d'un. Donc, l'option ne fonctionne pas ...

Avec l'aide de DMA et d' une telle mère, TIM12 a pu lire avec succès à une vitesse de 50 MHz, et en quelques heures il n'y a pas eu un seul test d'erreur. Les deux ports ont été lus, mais il n'était pas encore possible de mesurer jusqu'où la lecture sur le deuxième DMA est en retard ...

Donc, dans mon cas (légèrement dégénéré), j'ai réussi à atteindre la vitesse de transfert des informations vers le processeur stm32h750 à une vitesse de 32x50 = 1600 Mbps.

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


All Articles