Entrée de données dans STM32F4xx à partir d'ADC parallèle via DCMI

Il est connu que la famille de microcontrôleurs STM32F4xx, ayant à bord des cœurs suffisamment productifs qui sont tout à fait adaptés aux tâches de «non-découpe de viande», les DSP n'ont pas d'interface d'entrée de données à part entière avec le bus parallèle le plus simple en mode «pipeline» (données clk). Après avoir fumé dm00037051.pdf, j'ai trouvé une option non spécifique, mais à première vue, appropriée - l'interface DCMI (Digital camera interface).

Bien sûr, l'utilisation de microcontrôleurs STM32 pour un DSP classique chargé (FIR, IIR, FFT) n'est pas une option optimale, mais si les cartes se déposent soudainement et que les capacités de ce microcontrôleur sont suffisantes, en plus vous avez besoin d'un nombre suffisant d'interfaces à faible vitesse. À ce sujet sous la coupe.





Sur l'un des projets avec des délais et un budget «chauds», il fallait mettre en place un «morceau de fer» aux optimaux: masse, dimensions, consommation. En tant que fonction de base, un traitement numérique du signal (filtrage et analyse statistique), provenant de l'ADC en mode temps réel doux, était nécessaire. Pour le traitement, je voulais avoir une virgule flottante simple précision. Le signal de l'ADC a été reçu à une fréquence intermédiaire de 48 MHz. Bande de signal 1 MHz. Pour mettre en œuvre le transfert du spectre du signal de la fréquence intermédiaire à zéro, en utilisant de préférence un sous-échantillonnage de l'ADC à large bande. En outre, il était nécessaire de recevoir et de transmettre des informations via Ethernet, SPI, UART, I2C et de travailler avec des interruptions.


Les délais de mise en œuvre et les fonctions DSP spécifiques n'ont pas permis l'utilisation de FPGA à ces fins. Avec le célèbre processeur de signaux de la famille Blackfin, les célèbres Analog Devices n'avaient aucune expérience en communication et il n'y avait pas d'outils de débogage et de cartes de démonstration à proximité. Il n'y avait qu'une expérience de communication étroite et longue avec le cher, une fois le processeur phare DSP ADSP-TS201 TigerSHARC. De plus, Blackfin n'a pas d'implémentation matérielle d'IEEE-754. En outre, il était nécessaire d'accepter un bloc de données continu avec un ADC de 128 Ko, plus 30 Ko de traitement et sans mémoire externe, il était difficile de tout mettre dans quelque chose de moins de budget.


En général, il n'y avait que des cartes STM32F407 (Discovery et custom de Chine) à portée de main. Comme je le soupçonne, beaucoup de ceux qui traitent de sujets connexes ont maintenant une aide universelle à portée de main. Il y avait également une carte Terasic ADA-HSMC sur laquelle était installé l'ADC double canal AD9228 (12 bits, Fd = 65 msps, bande passante = 315 MHz).


Une option non spécifique mais tout à fait appropriée est l'interface DCMI (Digital camera interface), matériellement implémentée dans STM32F4.


Le fonctionnement de cette interface est décrit dans RM0090, DocID018909, p. 454/1718. Les quatre figures suivantes sont extraites de ce document.


Ainsi, la fréquence d'entrée revendiquée va jusqu'à 54 MHz. Ce qui est tout à fait suffisant - notre taux de sous-échantillonnage est de 10 MHz. Voici l'ensemble des signaux d'interface DCMI utilisés:





Remarque: D13, D14 sont uniquement disponibles en boîtier 144 broches. Nous avons un 100 broches, mais nous n'en avons pas besoin. (bien que les appareils analogiques aient un ADC - AD9248 14 bits similaire).


Voici un chronogramme généralisé de l'interface:





Et voici le chronogramme de l'interface en mode de format d'image JPEG:





Nous utiliserons ce mode de fonctionnement comme il nous convient le mieux.


Dans ce mode, le signal VSYNC est tiré à la puissance. Nous utiliserons HSYNC comme signal externe pour permettre le démarrage de la réception des données via l'interface.


Nous avons utilisé le microcontrôleur STM32F407RGT6 dans le boîtier LQFP100.


L'AD9238 ADC possède une entrée pour le mode d'arrêt (économie d'énergie) du canal correspondant PDWN_A (B), et l'autorisation de sortie est OEB_A (B). Il est logique de les obtenir à partir de toutes les broches du contrôleur. Par conséquent, le schéma de connexion des broches ressemblera à ceci:



Étant donné que cet ADC n'a pas de signal d'horloge de sortie, il est nécessaire d'utiliser la multiplication (tampon d'horloge). Nous avons utilisé le LMK00101 de Texas Instruments - bon rapport qualité-prix, faible gigue, et surtout, encore une fois - à portée de main).


Lors du traitement, nous tenons compte du fait que les données sur le bus parallèle ADC apparaissent avec un retard de 7 cycles d'horloge par rapport au signal d'horloge d'entrée.


Nous recevrons des données (bien sûr) via DMA. Voici le code source pour initialiser DCMI et DMA.


Nous activons la synchronisation des ports dont nous avons besoin, DCMI et DMA2


GPIO_InitTypeDef GPIO_InitStructure; /* Enable DCMI GPIOs clocks */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOD, ENABLE); /* Enable DCMI clock */ RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); 

Cette broche (PA5) simulera la division en trames - HSYNC. Initialiser à la sortie


  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_ResetBits(GPIOA, GPIO_Pin_5); //HSYNC_PA4 -PA5 -> GND 

Configurer les broches correspondantes en mode DCMI


  /* PCLK */ GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_DCMI); /* D0-D7 */ GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_DCMI); //D0* GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_DCMI); //D1* GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_DCMI); //D2* GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_DCMI); //D3* GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_DCMI); //D4* GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_DCMI); //D5* GPIO_PinAFConfig(GPIOE, GPIO_PinSource5, GPIO_AF_DCMI); //D6* GPIO_PinAFConfig(GPIOE, GPIO_PinSource6, GPIO_AF_DCMI); //D7* GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_DCMI); //D8* GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_DCMI); //D9* GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_DCMI); //D10* GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_DCMI); //D11* /* VSYNC */ GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_DCMI); /* HSYNC */ GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_DCMI); /* DCMI GPIO configuration **************************************************/ /* D0,D1,D2,D3,D4,D8,D9 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOC, &GPIO_InitStructure); /* D6,D7*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6; GPIO_Init(GPIOE, &GPIO_InitStructure); /* D10, D5, VSYNC(PB7) */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_Init(GPIOB, &GPIO_InitStructure); /* D11(PD2) */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_Init(GPIOD, &GPIO_InitStructure); /* PCLK(PA6) HSYNC(PA4)*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); 

La chose la plus intéressante ici est de configurer DCMI dans le mode correspondant aux trames JPEG.


  DCMI_InitStructure.DCMI_CaptureMode = DCMI_CaptureMode_Continuous; DCMI_InitStructure.DCMI_SynchroMode = DCMI_SynchroMode_Embedded; DCMI_InitStructure.DCMI_PCKPolarity = DCMI_PCKPolarity_Rising; DCMI_InitStructure.DCMI_VSPolarity = DCMI_VSPolarity_Low; DCMI_InitStructure.DCMI_HSPolarity = DCMI_HSPolarity_High; DCMI_InitStructure.DCMI_CaptureRate = DCMI_CaptureRate_All_Frame; DCMI_InitStructure.DCMI_ExtendedDataMode = DCMI_ExtendedDataMode_12b; 

Configuration DMA


  DCMI_Init(&DCMI_InitStructure); DMA_InitStructure.DMA_Channel = DMA_Channel_1; DMA_InitStructure.DMA_PeripheralBaseAddr = DCMI_DR_ADDRESS; //0x50050028 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DCMI_PendingData; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize = MAX_DOWBLE_BUF; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA2_Stream1, &DMA_InitStructure); 

Définition des interruptions à la fin de la réception des données du canal DMA correspondant


  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 

Le code de notre gestionnaire, dans lequel nous désactivons la réception de données DCMI et définit l'indicateur de données prêtes.


 void EXTI1_IRQHandler() { EXTI_ClearFlag(EXTI_Line1); DMA_ClearITPendingBit(DMA2_Stream1, DMA_IT_TCIF1); DMA_Cmd(DMA2_Stream1, DISABLE); DCMI_Cmd(DISABLE); dma_recv_f = 1; //GPIO_ResetBits(GPIOE, GPIO_Pin_0); //VSYNC reset } 

Tout est dans les réglages. Maintenant, nous activons notre canal DMA, le bloc DCMI, nous commençons à recevoir des données DCMI en mode de trame JPEG.


  /* Enable DMA transfer */ DMA_Cmd(DMA2_Stream1, ENABLE); /* Enable DCMI interface */ DCMI_Cmd(ENABLE); /* Start Image capture */ DCMI_CaptureCmd(ENABLE); DCMI_JPEGCmd(ENABLE); 

Le cycle du programme principal. Ici, interrogation du drapeau et redémarrage de la réception des données.


 uint8_t dma_recv_f = 0; //,     . uint16_t DCMI_PendingData[8500]; //    DCMI int main(void) { DCMI_Config(); //        __enable_irq(); //  while(1) { while(dma_recv_f!=1){}; //    dma_recv_f = 0; //  /*     */ Re_DMA_Config(DCMI_PendingData, glob_cnt); // DMA } } 

Remarque: si vous devez recevoir et traiter des données en temps réel dur avec une double mise en mémoire tampon, stm32f4 dispose d'un mécanisme d'interruption lorsque la moitié de la mémoire tampon est pleine. Dans les paramètres DMA, il est alors nécessaire de définir le mode cyclique continu de réception des données. Par exemple:


  // DMA2 Stream0 channel0 configuration ************************************** DMA_InitStructure.DMA_Channel = DMA_Channel_1; DMA_InitStructure.DMA_PeripheralBaseAddr = DCMI_DR_ADDRESS; //0x50050028 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)DCMI_PendingData; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_BufferSize = buf_size; <b>DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;</b> //     DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; <b>DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;</b> //  DMA     DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; 

Dans le gestionnaire d'interruption, il est alors nécessaire de réinitialiser le bit de fin DMA uniquement après avoir rempli la totalité du tampon, et le programme utilisateur d'indiquer le numéro du tampon actuel dans lequel la réception de données s'est terminée. Quelque chose comme ça:


 if(DMA_GetITStatus(DMA2_Stream1,DMA_IT_TCIF1)){ DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF1); num_buf = 1; } else{ num_buf = 0; } 

C'est essentiellement tout ce qui est nécessaire pour recevoir des données de notre ADC via DCMI.


Malheureusement, pour le moment, je ne peux pas démontrer tout ce mécanisme en direct. le morceau de fer est déjà en service depuis environ 3 ans))). Je ne peux apporter que les données d'enregistrement de test enregistrées à partir de ces moments.


Voici un signal harmonique du générateur SMB100A à une fréquence de 48,001 MHz, égal à notre IF avec un décalage de 1 KHz:





Et voici son spectre:





Pour vérifier les performances maximales sur des fils de maquette standard d'une longueur d'environ 200 mm, avec lesquels l'ADA-HSMC et le STM32F4 Discovery ont été connectés, les données correctes ont été reçues à une fréquence d'horloge de 40 MHz.
Sur une carte «native» fabriquée pour cette tâche, à travers un câble plat de 100 mm de long, à température ambiante, il s'est avéré augmenter la fréquence d'échantillonnage à un maximum de 54 MHz.
À la fréquence d'échantillonnage souhaitée de 10 MHz, les performances ont été testées sur la gamme industrielle: de -40 à +60.


En fait, tout. Merci de votre attention!




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


All Articles