LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
Artykuły

Interfejs DCMI w STM32: jak dołączyć kamerę CCD do mikrokontrolerów STM32F2 i STM32F4

Interfejs sterujący kamery jest podobny do interfejsu I2C i umożliwia dostęp do wszystkich rejestrów związanych z poszczególnymi blokami SOC. Adresy układu podrzędnego kamery jakie należy podawać przy transmisji po szynie I2C zestawiono w tabeli 3 i w danym konkretnym przypadku modułu będą to adresy 0xBA (zapis) oraz 0xBB (odczyt). Za adresem jest przesyłany numer  rejestru (jeden bajt), który ma być zapisany/odczytany po czym mogą iść dane (dwa bajty).

Tab. 3 Adresy układu podrzędnego kamery

Tab. 3 Adresy układu podrzędnego kamery

W systemie SOC do konfiguracji i sterowania pracą kamery udostępniane są:

  • rejestry,
  • zmienne sterowników,
  • rejestry specjalnego przeznaczenia,
  • pamięć SRAM mikrokontrolera.

Rejestry są podzielone na 3 banki (pierwszy jest związany z blokiem jądra czujnika, drugi z blokiem Color Pipeline, a trzeci z JPEG i FIFO), a przełączanie się między nimi odbywa się poprzez zapis do rejestru 0xF0 (występuje we wszystkich bankach) numeru docelowego banku.

Zmienne również są podzielone według realizowanych funkcji i przypisane do odpowiednich sterowników. Aby uzyskać do nich dostęp wykorzystuje się rejestr R198 (rejestr adresu) oraz R200 (rejestr danych) w banku 1. W rejestrze adresu podawany jest ID sterownika, względne położenie zmiennej w strukturze oraz jej rozmiar. Przykładowo, aby zapisać (odczytać) do zmiennej ae.Target wartość 50 należy do rejestru adresu wpisać wartość 0xA206, a do rejestru danych wartość 50 (lub odczytać ten rejestr), ponieważ:

  • zmienna znajduje się w sterowniku, ID którego wynosi 2 –> R198:1[12:8] = 2,
  • zmienna jest przesunięta w strukturze o 6 bajtów –> R198:1[7:0] = 6,
  • wykonywany jest logiczny dostęp do zmiennej –> R198:1[14:13] = 01,
  • rozmiar zmiennej wynosi 8 bitów –> R198:1[15] = 1.

 

Konfiguracja interfejsu DCMI

Pierwszą czynnością jaką należy wykonać jest włączenie sygnałów taktujących porty GPIO, moduł DCMI oraz kontroler DMA:

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_GPIOB |
                       RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOE, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI, ENABLE);

Dalej może być umieszczona konfiguracja przerwań od wybranych zdarzeń generowanych przez moduł DCMI:

NVIC_InitStructure.NVIC_IRQChannel = DCMI_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

Następnie należy skonfigurować odpowiednie linie portów GPIO oraz przypisać im funkcję alternatywną związaną z modułem DCMI:

/*** Connect DCMI pins to AF13 ***/
/* HSYNC(PA4), PIXCLK(PA6) */
GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_DCMI);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_DCMI);
/* D5(PB6), VSYNC(PB7) */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_DCMI);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_DCMI);
/* D0..1(PC6/7) */
GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_DCMI);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_DCMI);
/* D2..4,6..7(PE0/1/4/5/6) */
GPIO_PinAFConfig(GPIOE, GPIO_PinSource0, GPIO_AF_DCMI);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource1, GPIO_AF_DCMI);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource4, GPIO_AF_DCMI);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource5, GPIO_AF_DCMI);
GPIO_PinAFConfig(GPIOE, GPIO_PinSource6, GPIO_AF_DCMI);

/*** DCMI GPIOs configuration ***/
/* HSYNC(PA4), PIXCLK(PA6) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_6;
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(GPIOA, &GPIO_InitStructure);
/* D5(PB6), VSYNC(PB7) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* D0..1(PC6/7) */      
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* D2..4,6..7(PE0/1/4/5/6) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6;
GPIO_Init(GPIOE, &GPIO_InitStructure);

W tym miejscu należy również skonfigurować linię PA8 za pomocą której zostanie wyprowadzony na zewnątrz mikrokontrolera sygnał taktujący potrzebny do poprawnego działania czujnika kamery (podawany na wejście XCLK). Sygnał ten może pochodzić z zewnętrznego generatora szybkich przebiegów HSE (8MHz), albo też można wykorzystać systemowy zegar z pętli PLL po zmniejszeniu jego częstotliwości:

/*** Connect MCO1 pin to AF0 ***/
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_MCO);

/*** MCO1 GPIO configuration ***/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
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_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* Configure MCO1 output clock */
RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_3); //F_XCLK = 56MHz

Częstotliwość 56 MHz wynika z tego, że systemowy sygnał zegarowy posiada częstotliwość 168 MHz i jest on przepuszczony przez preskaler o wartości 3.

Właściwa konfiguracja modułu DCMI oraz kontrolera DMA wygląda następująco:

void DCMI_Config(){
  DCMI_InitTypeDef DCMI_InitStructure;
  DMA_InitTypeDef  DMA_InitStructure;

  /*** Configures the DCMI to receive data from MT9D111 ***/
  DCMI_DeInit();

  /* DCMI configuration */
  DCMI_InitStructure.DCMI_CaptureMode = DCMI_CaptureMode_Continuous;
  DCMI_InitStructure.DCMI_SynchroMode = DCMI_SynchroMode_Hardware;
  DCMI_InitStructure.DCMI_PCKPolarity = DCMI_PCKPolarity_Rising;
  DCMI_InitStructure.DCMI_VSPolarity  = DCMI_VSPolarity_Low;
  DCMI_InitStructure.DCMI_HSPolarity  = DCMI_HSPolarity_Low;
  DCMI_InitStructure.DCMI_CaptureRate = DCMI_CaptureRate_All_Frame;
  DCMI_InitStructure.DCMI_ExtendedDataMode = DCMI_ExtendedDataMode_8b;
  DCMI_Init(&DCMI_InitStructure);

  /* Enable interrupts from DCMI interface */
  /* OPCJONALNIE */
  DCMI_ITConfig(DCMI_IT_FRAME, ENABLE);
  DCMI_ITConfig(DCMI_IT_VSYNC, ENABLE);
  DCMI_ITConfig(DCMI_IT_LINE, ENABLE);
  DCMI_ITConfig(DCMI_IT_OVF, ENABLE);
  DCMI_ITConfig(DCMI_IT_ERR, ENABLE);
  /* OPCJONALNIE */


  /*** Configures the DMA2 to transfer data from DCMI DR to LCD RAM ***/

  /* DMA2 Stream1 DeInit */
  DMA_DeInit(DMA2_Stream1);
  while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE);

  /* DMA2 Stream1 configuration */
  DMA_InitStructure.DMA_Channel = DMA_Channel_1;  
  DMA_InitStructure.DMA_PeripheralBaseAddr = DCMI_DR_ADDRESS; // Rejestr 32-bitowy
  DMA_InitStructure.DMA_Memory0BaseAddr = FSMC_LCD_ADDRESS;   // Rejestr 16-bitowy
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  DMA_InitStructure.DMA_BufferSize = 1;
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // 32-bit
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;     // 16-bit
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  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);

  /* Enable DMA2 Stream 1 */
  DMA_Cmd(DMA2_Stream1, ENABLE);
  while (DMA_GetCmdStatus(DMA2_Stream1) != ENABLE);
}

Pierwsze pole DCMI_CaptureMode struktury inicjującej wybiera tryb pracy układu DCMI i może przyjmować wartości:

  • DCMI_CaptureMode_Continuous – ciągły odbiór ramek obrazu (np. tryb podglądu),
  • DCMI_CaptureMode_ SnapShot – odbiór tylko jednej ramki obrazu (np. tryb wykonania zdjęcia).

Wartość pola DCMI_SynchroMode informuje układ o tym jaka metoda synchronizacji będzie wykorzystywana przy określaniu początku/końca ramki/linii obrazu:

  • DCMI_SynchroMode_Embedded – kody synchronizujące są przesyłane liniami danych,
  • DCMI_SynchroMode_Hardware – wykorzystanie oddzielnych linii synchronizujących HSYNC oraz VSYNC.

Następne pole DCMI_PCKPolarity wybiera aktywne zbocze sygnału taktującego kolejne bajty danych PIXCLK:

  • DCMI_PCKPolarity_Falling – pobieranie danych na zboczu opadającym,
  • DCMI_PCKPolarity_Rising – pobieranie danych na zboczu narastającym.

Kolejne dwa parametry DCMI_VSPolarity i DCMI_HSPolarity informują układ DCMI o stanach linii synchronizujących VSYNC oraz HSYNC, które powinny być rozumiane jako nieaktywne i w czasie trwania których dane na szynie DATA są nieprawidłowe.

Przedostatnie pole DCMI_CaptureRate przyjmuje jedną z następujących wartości:

  • DCMI_CaptureRate_All_Frame – wszystkie ramki będą przyjmowane,
  • DCMI_CaptureRate_1of2_Frame – każda druga ramka będzie przyjmowana,
  • DCMI_CaptureRate_1of4_Frame – każda czwarta ramka będzie przyjmowana.

Ostatnie pole wybiera rozdzielczość szyny danych interfejsu, która może być 8-, 10-, 12- i 14-bitowa. W danym przypadku moduł kamery udostępnia 8 linii danych, dlatego jest wybierany tryb 8-bitowy.

Kontroler DMA został skonfigurowany do pracy w roli transportera danych z 32-bitowego rejestru danych DCMI_DR do wewnętrznej pamięci wyświetlacza, która jest interpretowana przez układ jako zewnętrzna pamięć typu SRAM (dzięki interfejsowi FSMC). Transfer danych odbywa się kanałem 1 kontrolera DMA2, a ponieważ rejestr źródła i rejestr przeznaczenia nie mają takich samych rozmiarów, więc najpierw z rejestru DCMI_DR są przesyłane najmłodsze 16 bitów, a dopiero potem najstarsze 16 bitów. Natomiast sam rejestr danych jest zapełniany od najmniej do najbardziej znaczącego bajtu (od prawej do lewej),

Obsługa przerwań od układu DCMI znajduje się w pliku stm32f4xx_it.c i została w niej na wszelki wypadek umieszczona instrukcja ustawienia kursora w pozycji początkowej po odebraniu pełnej ramki obrazu co pozwoli uniknąć ewentualnego przesuwającego się obrazu na wyświetlaczu:

void DCMI_IRQHandler(void){
  // Przerwanie generowane po odebraniu pelnej ramki
  if(DCMI_GetITStatus(DCMI_IT_FRAME) == SET){
    DCMI_ClearITPendingBit(DCMI_IT_FRAME);
  }

  // Przerwanie generowane przy zmianie stanu sygnalu VSYNC
  // z aktywnego na nieaktywny (VPOL = Low)
  if(DCMI_GetITStatus(DCMI_IT_VSYNC) == SET){
    DCMI_ClearITPendingBit(DCMI_IT_VSYNC);

    // Czekaj, az DMA zakonczy transfer do pamieci RAM wyswietlacza
    while(DMA_GetFlagStatus(DMA2_Stream1,DMA_FLAG_TCIF1) == RESET);
    LCD_SetCursor(0, 319);    // Ustaw w pozycji lewego gornego rogu
    LCD_WriteRAM_Prepare();   // Prepare to write GRAM
  }

  // Przerwanie generowane przy zmianie stanu sygnalu HSYNC
  // z aktywnego na nieaktywny (HPOL = Low)
  if(DCMI_GetITStatus(DCMI_IT_LINE) == SET){
    DCMI_ClearITPendingBit(DCMI_IT_LINE);
  }

  // Przerwanie generowane gdy stare dane (32-bitowe) w rejestrze DCMI_DR
  // nie zostaly calkowicie przeslane przed nadejsciem nowych danych
  if(DCMI_GetITStatus(DCMI_IT_OVF) == SET){
    DCMI_ClearITPendingBit(DCMI_IT_OVF);
  }
}

Aby rozpocząć przyjmowanie ramek obrazu należy włączyć układ DCMI oraz wydać stosowną komendę:

/* Enable DCMI interface then start image capture */
DCMI_Cmd(ENABLE);
DCMI_CaptureCmd(ENABLE);

 

Autor: Jan Szemiet