Как стать автором
Обновить

Ввод данных в STM32F4xx с параллельного АЦП через DCMI

Время на прочтение8 мин
Количество просмотров14K

Известно, что семейство микроконтроллеров STM32F4xx, имея на борту достаточно производительные ядра, вполне подходящие для «не мясорубочных» задач ЦОС не имеют полноценного интерфейса ввода данных с простейшей параллельной шины в режиме «pipe-line» (clk-data). «Покурив» «dm00037051.pdf», нашел не специфичный, но на первый взгляд подходящий вариант – интерфейс DCMI (Digital camera interface).

Конечно, использование для нагруженной классической ЦОС (КИХ, БИХ, FFT) микроконтроллеров STM32, не совсем является оптимальным вариантом, но если вдруг так легли карты и все-таки возможностей данного микроконтроллера вполне достаточно, плюс нужно достаточное количество низкоскоростных интерфейсов. Об этом под катом.





На одном из проектов с «горячими» сроками и бюджетом была необходимость реализовать «железку» с оптимальными: массой, габаритами, потреблением. В качестве базовой функции требовалась цифровая обработка сигнала (фильтрация и статистический анализ), поступающего с АЦП в режиме «мягкого» реального времени. Для обработки хотелось иметь плавающую точку одинарной точности. Сигнал с АЦП поступал на промежуточной частоте 48 МГц. Полоса сигнала 1 МГц. Реализовать переноса спектра сигнала с промежуточной частоты в ноль, желательно с использованием субдискретизации широкополосного АЦП. Также, нужно было принимать и передавать информацию по интерфейсам Ethernet, SPI, UART, I2C и работать с прерываниями.


Сроки на реализацию и специфичные функции ЦОС не позволили использовать FPGA для этих целей. С широко известным сигнальным процессором семейства Blackfin от всем известной Analog Devices не было опыта общения и отсутствовали в ближайшем доступе отладочные средства и демоплаты. Имелся только опыт тесного и длительного общения с дорогущим, некогда флагманом ЦОС, процессора ADSP-TS201 TigerSHARC. К тому-же в Blackfin отсутствует аппаратная реализация IEEE-754. Также, требовалось принимать непрерывный блок данных с АЦП размером 128 Кбайт, плюс 30 Кбайт накладные расходы на обработку и уже без внешней памяти сложно было все впихнуть во что-то более менее бюджетное.


Вообщем, под рукой были только платы STM32F407 (Discovery и кастомные с Китая). Как я подозреваю у многих, кто занимается смежной тематикой сейчас под рукой такие универсальные выручалочки имеются. Также имелась плата ADA-HSMC от Terasic, на которой установлено двухканальное АЦП AD9228 (12-bit, Fd=65 msps, bandwidth = 315 MHz).


Не специфичный, но вполне подходящий вариант – интерфейс DCMI (Digital camera interface), аппаратно реализованный в STM32F4.


Работа данного интерфейса описана в RM0090, DocID018909, стр. 454/1718. Последующие четыре рисунка приведены из данного документа.


Итак, заявленная частота поступления входных данных до 54 МГц. Что вполне достаточно — наша частота субдискретизации 10 МГц. Вот набор используемых сигналов интерфейса DCMI:





Примечание: D13, D14 реализованы только в 144-контактном корпусе. Мы имеем 100-контактный, но нам они и не нужны. (хотя у Analog Devices существует аналогичное 14 bit АЦП — AD9248).


Вот обобщенная временная диаграмма работы интерфейса:





А это временная диаграмма работы интерфейса в режиме формата кадра JPEG:





Данный режим работы и будем использовать т.к. он нам наиболее подходит.


В этом режиме сигнал VSYNC подтягиваем к питанию. HSYNC будем использовать как внешний сигнал разрешения запуска приема данных по интерфейсу.


Мы использовали микроконтроллер STM32F407RGT6 в корпусе LQFP100.


У АЦП AD9238 есть вход режима отключения (энергосбережения) соответствующего канала PDWN_A(B), и разрешения по выходу OEB_A(B). Логично их завести с каких-либо пинов контроллера. В итоге схема соединения выводов будет выглядеть вот так:



Так как у данного АЦП нет выходного тактового сигнала, необходимо использовать размножитесь (тактовый буфер). Мы использовали LMK00101 от Texas Instruments – хорошее соотношение цены, низкого джиттера, а главное, опять-же — под рукой ).


В обработке учитываем, что данные на параллельной шине АЦП появляются с задержкой на 7 тактов относительно входного тактового сигнала.


Прием данных будем осуществлять (конечно-же) через DMA. Вот исходный код инициализации DCMI и DMA.


Включаем тактирование нужных нам портов, DCMI и 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);

Этим пином (PA5) будем имитировать разделение на кадры – HSYNC. Инициализируем на выход


  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	 

Настраиваем соответствующие выводы в режиме 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);

Здесь самое интересное – настройка DCMI в режиме, соответствующего 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; 

Настройка 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);

Настройка прерываний по окончании приема данных с соответствующего канала DMA


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

Код нашего обработчика, в котором отключаем прием данных по DCMI и выставляет флаг готовности данных.


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
}

С настройками все. Теперь включаем наш канал DMA, блок DCMI, стартуем прием данных по DCMI в режиме кадров JPEG.


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

Цикл основной программы. Здесь опрос флага и перезапуск приема данных.


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

Примечание: если необходимо принимать и обрабатывать данные в жестком реальном времени с двойной буферизацией в stm32f4 есть механизм прерывания при заполнении половины буфера. В настройках DMA тогда необходимо задать непрерывный циклический режим приема данных. Например:


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

В обработчике прерывания тогда необходимо сбрасывать бит окончания DMA только после заполнения всего буфера, и пользовательской программе обработки указывать номер текущего буфера, в который закончился прием данных. Примерно вот так:


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

Вот в принципе и все, что необходимо для приема данных с нашего АЦП по интерфейсу DCMI.


К сожалению, в данный момент не могу в живую продемонстрировать весь этот механизм т.к. железка уже года 3 как в эксплуатации ))). Могу только привести сохраненные данные тестовой регистрации с тех времен.


Вот это гармонический сигнал с генератора SMB100A на частоте 48.001 МГц, равный нашей ПЧ с отстройкой на 1 КГц:





А это его спектр:





Для проверки максимальных характеристик на стандартных макетных проводах длиной примерно 200 мм, с помощью которых были соединены ADA-HSMC и STM32F4 Discovery, корректный прием данных осуществлялся на частоте тактирования 40 МГц.
На изготовленной под данную задачу «родной» плате, через плоский шлейф длиной 100 мм, при комнатной температуре, получилось частоту дискретизации поднять до максимальной — 54 МГц.
На нужной нам частоте дискретизации 10 МГц работоспособность была проверена на промышленном диапазоне: от -40 до +60.


Собственно все. Спасибо за внимание!




Теги:
Хабы:
Всего голосов 17: ↑17 и ↓0+17
Комментарии4

Публикации