Entrada de dados no STM32F4xx do ADC paralelo via DCMI

Sabe-se que a família de microcontroladores STM32F4xx, tendo núcleos suficientemente produtivos e adequados para tarefas de "corte de carne", os DSPs não possuem uma interface de entrada de dados completa com o barramento paralelo mais simples no modo "pipe-line" (clk-data). Tendo fumado o dm00037051.pdf, encontrei uma opção não específica, mas à primeira vista, adequada - a interface DCMI (interface da câmera digital).

Obviamente, o uso de microcontroladores STM32 para um DSP clássico carregado (FIR, IIR, FFT) não é uma opção ideal, mas se as placas deitarem repentinamente e ainda os recursos deste microcontrolador forem suficientes, você precisará de um número suficiente de interfaces de baixa velocidade. Sobre isso sob o corte.





Em um dos projetos com prazos e orçamento “quentes”, havia a necessidade de implementar um “pedaço de ferro” com os ótimos: massa, dimensões, consumo. Como função básica, era necessário o processamento de sinal digital (filtragem e análise estatística), proveniente do ADC no modo de tempo real suave. Para processamento, eu queria ter um único ponto flutuante de precisão. O sinal do ADC foi recebido a uma frequência intermediária de 48 MHz. Banda de sinal de 1 MHz. Implementar a transferência do espectro do sinal da frequência intermediária para zero, de preferência usando subamostragem do ADC de banda larga. Além disso, era necessário receber e transmitir informações via Ethernet, SPI, UART, I2C e trabalhar com interrupções.


Os prazos de implementação e funções DSP específicas não permitiram o uso de FPGAs para esses fins. Com o conhecido processador de sinais da família Blackfin, os conhecidos Analog Devices não tinham experiência em comunicação e não havia ferramentas de depuração e placas de demonstração no acesso próximo. Houve apenas experiência de comunicação estreita e longa com o caro, uma vez que o principal processador DSP ADSP-TS201 TigerSHARC. Além disso, o Blackfin não possui uma implementação de hardware do IEEE-754. Além disso, era necessário aceitar um bloco contínuo de dados com um ADC de 128 Kbytes de tamanho, mais 30 Kbytes de sobrecarga de processamento, e já sem memória externa, era difícil colocar tudo em algo mais ou menos orçamentário.


Em geral, havia apenas placas STM32F407 (Discovery e personalizadas da China) disponíveis. Como suspeito, muitos que lidam com tópicos relacionados agora têm essa ajuda universal em mãos. Havia também uma placa Terasic ADA-HSMC na qual o ADC de canal duplo AD9228 estava instalado (12 bits, Fd = 65 msps, largura de banda = 315 MHz).


Uma opção não específica, mas bastante adequada, é a interface DCMI (Digital camera interface), que é o hardware implementado no STM32F4.


A operação desta interface está descrita em RM0090, DocID018909, página 454/1718. As quatro figuras a seguir são fornecidas neste documento.


Portanto, a frequência de entrada reivindicada é de até 54 MHz. O que é suficiente - nossa taxa de subamostragem é de 10 MHz. Aqui está o conjunto de sinais da interface DCMI usados:





Nota: D13, D14 estão disponíveis apenas na embalagem de 144 pinos. Temos 100 pinos, mas não precisamos deles. (embora os dispositivos analógicos possuam um ADC - AD9248 de 14 bits semelhante).


Aqui está um diagrama de tempo generalizado da interface:





E este é o diagrama de tempo da interface no modo de formato de quadro JPEG:





Usaremos este modo de operação como ele nos convém melhor.


Nesse modo, o sinal VSYNC é puxado para a energia. Usaremos o HSYNC como um sinal externo para permitir o início da recepção de dados através da interface.


Usamos o microcontrolador STM32F407RGT6 no pacote LQFP100.


O AD9238 ADC possui uma entrada para o modo de desligamento (economia de energia) do canal correspondente PDWN_A (B) e a permissão de saída é OEB_A (B). É lógico obtê-los de qualquer pino do controlador. Como resultado, o diagrama de conexão dos pinos ficará assim:



Como este ADC não possui um sinal de clock de saída, é necessário usar multiplicar (buffer de clock). Usamos o LMK00101 da Texas Instruments - bom valor pelo preço, baixa instabilidade e, o mais importante, novamente - à mão).


No processamento, levamos em consideração que os dados no barramento paralelo ADC aparecem com um atraso de 7 ciclos de clock em relação ao sinal de clock de entrada.


Nós receberemos dados (é claro) através do DMA. Aqui está o código fonte para inicializar o DCMI e o DMA.


Ativamos o clock das portas que precisamos, DCMI e 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); 

Este pino (PA5) simulará a divisão em quadros - HSYNC. Inicializar na saída


  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 

Configure os pinos correspondentes no modo 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); 

O mais interessante aqui é configurar o DCMI no modo correspondente aos quadros 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; 

Configuração de 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); 

Definir interrupções no final do recebimento de dados do canal DMA correspondente


  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); 

O código do nosso manipulador, no qual desativamos a recepção de dados DCMI e define o sinalizador de pronto para dados.


 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 } 

Tudo está com as configurações. Agora ativamos o canal DMA, o bloco DCMI, e começamos a receber dados DCMI no modo de quadro JPEG.


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

O ciclo do programa principal. Aqui, pesquisando a bandeira e reiniciando a recepção de dados.


 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 } } 

Nota: se você precisar receber e processar dados em tempo real rígido com buffer duplo, o stm32f4 possui um mecanismo de interrupção quando metade do buffer estiver cheio. Nas configurações de DMA, é necessário definir o modo cíclico contínuo de recepção de dados. Por exemplo:


  // 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; 

No manipulador de interrupção, é necessário redefinir o bit final do DMA somente após o preenchimento de todo o buffer e o programa do usuário para indicar o número do buffer atual no qual a recepção de dados terminou. Algo assim:


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

Isso é basicamente tudo o que é necessário para receber dados de nosso ADC via DCMI.


Infelizmente, no momento não posso demonstrar todo esse mecanismo ao vivo. o pedaço de ferro já tem cerca de 3 anos em operação))). Só posso trazer os dados de registro de teste salvos a partir desses momentos.


Este é um sinal harmônico do gerador SMB100A na frequência de 48.001 MHz, igual ao nosso IF com um desvio de 1 KHz:





E este é o seu espectro:





Para verificar o desempenho máximo em cabos de placa de ensaio padrão com um comprimento de aproximadamente 200 mm, com os quais o ADA-HSMC e o STM32F4 Discovery foram conectados, os dados corretos foram recebidos com uma frequência de clock de 40 MHz.
Em uma placa “nativa” fabricada para esta tarefa, através de um cabo plano de 100 mm de comprimento, em temperatura ambiente, acabou aumentando a frequência de amostragem para um máximo de 54 MHz.
Na freqüência de amostragem desejada de 10 MHz, o desempenho foi testado na faixa industrial: de -40 a +60.


Na verdade tudo. Obrigado pela atenção!




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


All Articles