Przedstawiamy prostą aplikację na mikrokontroler STM32F4 obsługującą kamerę modCAM_2MP_2 i wyświetlacz modTFT32T, które podłączono do zestawu ZL41ARM. Dodatkowo dodano także obsługę komunikacji z komputerem PC przez port USB, dzięki czemu można łatwo konfigurować parametry kamery z poziomu aplikacji i obserwować efekty w czasie rzeczywistym bez konieczności programowania mikrokontrolera.
Schemat elektryczny
Schemat elektryczny przedstawiający sposób podłączenia modułu z kamerą oraz wyświetlacza LCD z panelem dotykowym do komputera jednopłytkowego z mikrokontrolerem STM32F417VG przedstawiono na rysunku 1 (pozostała część schematu znajduje się w dokumentacji zestawu ZL41ARM). Obecność dużej liczby połączeń powoduje, że w czasie pracy układu mogą występować w nich zakłócenia mające wpływ zarówno na odbierane dane z kamery jak i wysyłane do wyświetlacza. Jednym ze sposobów ich zmniejszenia jest przeplatanka przewodów połączeniowych jak widać na fotografii 1. Również ważnym jest aby chronić tylną część kamery przed światłem, gdyż w przeciwnym wypadku obraz może być w pewnych miejscach nieprawidłowy (inny kolor, dziwne piksele) lub prześwietlony.
Rys. 1. Schemat elektryczny podłączenia kamery i wyświetlacza
Do zasilania kamery potrzebne jest napięcie maksymalnie 3 V (na płytce komputera występuje napięcie 3,3 V), dlatego został wykorzystany zewnętrzny stabilizator LM2950. Dodatkowo linie D+ oraz D- z gniazdka USB typu mini A zostały na stałe połączone z odpowiednimi wyprowadzeniami mikrokontrolera poprzez przylutowanie cienkich przewodów, ponieważ fabrycznie takich połączeń nie ma (w tabeli 1 przedstawiono jak to zostało zrobione). Interfejs USB pracuje w trybie Full Speed i nie jest wymaganym umieszczanie zewnętrznego rezystora podciągającego na linii D+ (jest on wbudowany w mikrokontroler).
Tab. 1. Opis zewnętrznych wyprowadzeń gniazdka USB typu mini A
Numer | Oznaczenie | Opis |
1 | VCC | Napięcie zasilania +5 V |
2 | D- | Linia transmisji danych podłączona do nóżki PA11 mikrokontrolera |
3 | D+ | Linia transmisji danych podłączona do nóżki PA12 mikrokontrolera |
4 | NC | Nie podłączone |
5 | GND | Masa |
Moduł wyświetlacza modTFT32T posiada kolorowy wyświetlacz z rozdzielczością 240RGBx320 pikseli sterowany przez kontroler SSD1289 oraz panel dotykowy obsługiwany przez kontroler ADS7843. Do komunikacji z kontrolerem SSD1289 wykorzystuje się interfejs równoległy typu (Intel) 8080 w którym transmisja odbywa się asynchronicznie i który posiada następujące linie sterujące/danych:
- linia wyboru chipu – CS,
- linia wyboru rejestru/pamięci RAM – RS,
- linia wyboru trybu zapisu – nWS,
- linia wyboru trybu odczytu – nRD,
- linia sygnału zerującego – nRESET,
- 16-bitowa szyna danych – D0:D15.
Do poprawnej pracy wyświetlacza oprócz zasilania podawanego na wejścia +5 V <–> GND wymagane jest jeszcze włączenie podświetlenia LED: BLVDD(+5 V) <–> BLGND(GND). Brak podświetlenia uniemożliwia zobaczenie jakichkolwiek efektów zapisu do pamięci GDDRAM (Graphics Display Data RAM). Komunikacja z kontrolerem wyświetlacza odbywa się za pośrednictwem kontrolera FSMC (Flexible Static Memory Controller) przeznaczonym do zapisu/odczytu do/z zewnętrznych pamięci takich jak pamięć SRAM lub FLASH, a także do obsługi wyświetlaczy z interfejsem równoległym zgodnym ze standardem 6800 lub i8080.
Rys. 2. Podłączenie wyświetlacza do FSMC
Komunikacja z komputerem przez USB
Gotowy projekt obsługi komunikacji z wykorzystaniem portu USB został skopiowany z pliku archiwalnego o nazwie STM32F105/7, STM32F2 and STM32F4 USB on-the-go Host and device library (wersja 2.1.0), który jest dostępny na stronie producenta danego mikrokontrolera w zakładce Design support -> Firmware. Jest to projekt urządzenia realizującego funkcję wirtualnego portu COM (znajduje się w katalogu Project -> USB_Device_Examples -> VCP). Część projektu została zmodyfikowana na potrzeby komunikacji z prostą aplikacją PC i zostały dołączone pliki obsługi wyświetlacza LCD minimalnie zmodyfikowane do pracy z kontrolerem SSD1289 (również dostępne w powyższym pliku archiwalnym).
Komunikacja między aplikacją PC, a układem ZL41ARM sprowadza się do zapisu/odczytu rejestrów/zmiennych sterownika kamery, a funkcje które to umożliwiają po stronie mikrokontrolera znajdują się w pliku usbd_cdc_vcp.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
uint16_t VCP_DataTx (uint8_t* Buffer, uint32_t Length) { uint32_t i; //loop through buffer for(i = 0; i < Length; i++) { APP_Rx_Buffer[APP_Rx_ptr_in] = (uint8_t) Buffer[i]; //increase pointer value APP_Rx_ptr_in++; /* To avoid buffer overflow */ if(APP_Rx_ptr_in == APP_RX_DATA_SIZE) { APP_Rx_ptr_in = 0; } } return USBD_OK; } static uint16_t VCP_DataRx (uint8_t* Buffer, uint32_t Len) { if(Buffer[0] == MT9D111_RD_REG_CMD){ MT9D111_WriteReg(PAGE_SELECT_REG, Buffer[2]); // Select Page reg_value = MT9D111_ReadReg(Buffer[1]); // Select and Read Register TxBuffer[0] = reg_value >> 8; // MS Byte TxBuffer[1] = reg_value & 0xFF; // LS Byte VCP_DataTx(TxBuffer,2); // Write to USB output buffer } else if(Buffer[0] == MT9D111_WR_REG_CMD){ reg_value = (Buffer[3] << 8) + Buffer[4]; // Get 16-bit value MT9D111_WriteReg(PAGE_SELECT_REG, Buffer[2]); // Select Page MT9D111_WriteReg(Buffer[1], reg_value); // Select and Write Register */ } else if(Buffer[0] == MT9D111_WR_VAR_CMD){ var_addr = (Buffer[1] << 8) + Buffer[2]; // Get 16-bit address var_data = (Buffer[3] << 8) + Buffer[4]; // Get 16-bit value MT9D111_WriteReg(PAGE_SELECT_REG, 0x1); // Select Page MT9D111_WriteReg(UC_VAR_ADDR_REG, var_addr); // Select Variable MT9D111_WriteReg(UC_VAR_DATA_REG, var_data); // Write Variable } else if(Buffer[0] == MT9D111_RD_VAR_CMD){ var_addr = (Buffer[1] << 8) + Buffer[2]; MT9D111_WriteReg(PAGE_SELECT_REG, 0x1); // Select Page MT9D111_WriteReg(UC_VAR_ADDR_REG, var_addr); // Select Variable var_data = MT9D111_ReadReg(UC_VAR_DATA_REG); // Read Variable TxBuffer[0] = var_data >> 8; // MS Byte TxBuffer[1] = var_data & 0xFF; // LS Byte VCP_DataTx(TxBuffer,2); // Write to USB output buffer } return USBD_OK; } |
Funkcja VCP_DataRx() jest wywoływana automatycznie za każdym razem jak tylko zostały odebrane nowe dane z portu USB komputera i wykonywane jest w niej przetworzenie rodzaju komendy (zapis/odczyt, rejestr/zmienna) oraz jej argumentów (adres, dane). Natomiast funkcja VCP_DataTx() jest wywoływana jawnie w celu przekopiowania danych do endpointa IN urządzenia, w wyniku czego zostaną one wysłane do komputera.
Wyświetlacz LCD
Konfiguracja linii GPIO związanych z układem FSMC i do których jest podłączony wyświetlacz wygląda następująco (plik PeripheralsConfig.c):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/****** Configures the FSMC GPIOs ******/ /****** SRAM Data lines, NOE and NWE configuration ******/ /*** Connect FSMC pins to AF12 ***/ /* D0..3,13..15, NOE(PD4), NWE(PD5), NE1(PD7), A16(PD11) */ GPIO_PinAFConfig(GPIOD, GPIO_PinSource0, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource1, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource4, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource5, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource7, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource8, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource9, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource10, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource11, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource14, GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOD, GPIO_PinSource15, GPIO_AF_FSMC); /* D4..12 */ GPIO_PinAFConfig(GPIOE, GPIO_PinSource7 , GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOE, GPIO_PinSource8 , GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOE, GPIO_PinSource9 , GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOE, GPIO_PinSource10 , GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOE, GPIO_PinSource11 , GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOE, GPIO_PinSource12 , GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOE, GPIO_PinSource13 , GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOE, GPIO_PinSource14 , GPIO_AF_FSMC); GPIO_PinAFConfig(GPIOE, GPIO_PinSource15 , GPIO_AF_FSMC); /*** FSMC GPIOs configuration ***/ /* D0..3,13..15, NOE(PD4), NWE(PD5), NE1(PD7), A16(PD11) */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_14 | GPIO_Pin_15 | GPIO_Pin_4 |GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOD, &GPIO_InitStructure); /* D4..12 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; GPIO_Init(GPIOE, &GPIO_InitStructure); /* LCD_RESET line configuration */ GPIOD -> MODER |= GPIO_MODER_MODER13_0; GPIOD -> OSPEEDR |= GPIO_OSPEEDER_OSPEEDR13_1; |
Konfiguracja parametrów komunikacji z zewnętrzną pamięcią typu SRAM (plik LCD.c):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
void LCD_FSMCConfig(void) { FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure; FSMC_NORSRAMTimingInitTypeDef p; p.FSMC_AddressSetupTime = 1; p.FSMC_AddressHoldTime = 0; p.FSMC_DataSetupTime = 9; p.FSMC_BusTurnAroundDuration = 0; p.FSMC_CLKDivision = 1; p.FSMC_DataLatency = 0; p.FSMC_AccessMode = FSMC_AccessMode_B; FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM1; FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_SRAM; FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b; FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable; FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low; FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable; FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState; FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable; FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable; FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable; FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable; FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable; FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &p; FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &p; FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure); /* Enable FSMC NOR/SRAM Bank1 */ FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); } |
Natomiast inicjalizacja wyświetlacza polega na zapisaniu szeregu wartości do odpowiednich rejestrów i można ją znaleźć w tym samym pliku w funkcji LCD_Init()(szczegóły w dokumentacji kontrolera SSD1289).
W projekcie dodano również obsługę dotknięcia panelu dotykowego jako prostej metody interakcji z użytkownikiem. Informację o tym zdarzeniu dostarcza sygnał na linii TP_IRQ – jeżeli jej stan odpowiada logicznemu zeru to panel dotykowy jest dotknięty (dowolne miejsce), a jeżeli logicznej jedynce to panel nie jest dotknięty. Konfiguracja linii mikrokontrolera do której ten sygnał jest dostarczany wygląda następująco:
1 2 3 4 5 6 |
/*** PC13 configuration ***/ GPIO_InitStructure.GPIO_Pin = TP_IRQ_pin; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(TP_IRQ_port, &GPIO_InitStructure); |
Konfiguracja przerwania zewnętrznego generowanego na zboczu narastającym i opadającym:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/* Connect EXTI Line to TP IRQ pin */ SYSCFG_EXTILineConfig(TP_IRQ_EXTI_port, TP_IRQ_EXTI_pin); /* Configure EXTI Line */ EXTI_InitStructure.EXTI_Line = TP_IRQ_EXTI_line; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); /* Enable and set EXTI Line13 Interrupt to the (0,4) priority level */ NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); |
Obsługa zewnętrznego przerwania w funkcji EXTI15_10_IRQHandler():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
void EXTI15_10_IRQHandler(void) { if(EXTI_GetITStatus(TP_IRQ_EXTI_line) != RESET) { /* Clear the EXTI line 13 pending bit */ EXTI_ClearITPendingBit(TP_IRQ_EXTI_line); if(GPIO_ReadInputDataBit(TP_IRQ_port, TP_IRQ_pin) == RESET){ TP_Pressed(); } else{ TP_Released(); } } } |
oraz związane z tym funkcje w pliku main.c:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
void waitForTouch(void){ while( !(Is_TP_Pressed()) ); // Czekaj na dotkniecie panelu TP_status &= ~(TP_PRESSED); while( !(Is_TP_Released()) ); // Czekaj na odpuszczenie panelu TP_status &= ~(TP_RELEASED); } // Sprawdzenie czy panel dotykowy jest wciśnięty uint8_t Is_TP_Pressed(){ uint16_t count = 0xAFFF; // Prosty mechanizm debouncera while(count--){ if(!(TP_status & TP_PRESSED)) return 0; } return TP_PRESSED; } // Sprawdzenie czy panel dotykowy jest odpuszczony uint8_t Is_TP_Released(){ uint16_t count = 0xAFFF; // Prosty mechanizm debouncera while(count--){ if(!(TP_status & TP_RELEASED)) return 0; } return TP_RELEASED; } void TP_Pressed(){ TP_status |= TP_PRESSED; } void TP_Released(){ TP_status |= TP_RELEASED; } |
Krótka charakterystyka kamery MT9D111
W projekcie wykorzystano moduł z 2-megapikselową kamerą posiadającą czujnik obrazu kolorowego MT9D111 z matrycą typu CMOS (fotografia 5). System jaki jest zintegrowany w tym czujniku (SOC, system-on-a-chip) umożliwia łatwe konfigurowanie bogatej listy parametrów oraz oferuje wiele funkcji, jakie są dostępne w fotograficznych aparatach cyfrowych, takie jak ożywianie, korekcja kolorów, korekcja gammy, automatyczny balans bieli, automatyczna ekspozycja oraz wiele innych pozwalających uzyskać zdjęcia możliwie wysokiej jakości.
Fot. 3. Moduł z kamerą MT9D111
Strukturę blokową systemu SOC przedstawiono na rysunku 4, można w niej wyróżnić cztery najważniejsze elementy: jądro czujnika (Sensor Core), zaawansowany procesor obrazów przepływu (IFP, Image Flow Processor), koder JPEG czasu rzeczywistego oraz mikrokontroler (MCU). Zarejestrowany obraz z matrycy CMOS należącej do jądra czujnika przepływa (gruba biała strzałka) przez poszczególne bloki systemu SOC, w których jest poddawany kolejnym etapom obróbki, a jedną z cech charakterystycznych systemu jest możliwość wybrania bloku z którego dane obrazu będą wysyłane na wyjście modułu. Natomiast sterowanie pracą poszczególnych elementów systemu odbywa się za pomocą wewnętrznej szyny (Internal Register Bus).
Rys. 4. Schemat blokowy systemu SOC
Wybrany moduł z kamerą jest wyposażony w szeregowy interfejs sterujący oraz równoległy interfejs danych wideo z sygnałami synchronizującymi. Na rysunku 5 pokazano rozmieszczenie sygnałów na płytce modułu, a tabela 2 zawiera szczegółowy ich opis. Do zasilania kamery zaleca się podanie 3 V, gdyż napięcia wyższe (nawet 3,3 V), jeśli nawet nie są tragiczne w skutkach, to mogą powodować grzanie się elementów kamery.
Rys. 5. Wyprowadzenia modułu z kamerą (widok od strony obiektywu kamery)
Tab. 2. Opis wyprowadzeń modułu kamery
Nazwa wyprowadzenia | Rodzaj wyprowadzenia | Opis |
VCC | Power | Napięcie zasilania kamery, max. 3,1 V |
GND | Ground | Masa |
SCL | Input | Linia zegarowa magistrali I2C |
SDA | Input/Output | Linia danych magistrali I2C |
VSYNC | Output | Sygnał synchronizujący (FrameValid); stan wysoki – aktywny |
HREF | Output | Sygnał synch. (LineValid, DataValid, HSYNC); stan wysoki – aktywny |
PCLK | Output | Wyjściowy sygnał zegarowy taktujący kolejne piksele obrazu |
XCLK | Input | Wejściowy sygnał zegarowy |
D7-D0 | Output | 8-bitowa szyna danych wyjściowych |
STANDBY | Input | Linia sterująca trybem standby |
DRIVER_IN | Output | Sygnał PWM pochodzący z czujnika kamery |
DRIVER_EN | Input | VCM (Voice Coil Motor) driving Voltage from Driver |
Sposób w jaki ramki wideo są wysyłane przez synchroniczny interfejs równoległy jest łatwy w zrozumieniu. Każdy obraz generowany przez kamerę posiada określoną szerokość oraz wysokość i niech będzie to wymiar 320 x 240 pikseli (matryca pikseli o wymiarach 320 kolumn na 240 wierszy). Do opisu pojedynczego piksela obrazu potrzebnych jest zazwyczaj dwa bajty (np. w formacie RGB565), a szyna danych jest 8-bitowa, a więc aby przesłać jedną całkowitą ramkę obrazu wideo z kamery potrzebnych jest 76 800 x 2 = 153 600 taktów sygnału zegarowego PIXCLK. Oprócz sygnału zegarowego i 8-bitowej szyny danych występują jeszcze dwa sygnały synchronizujące, VSYNC (lub Frame_Valid) oraz HSYNC (lub Line_Valid). Pierwszy sygnał będąc w stanie aktywnym (wysoki poziom napięcia) informuje odbiornik, że w danej chwili jest wysyłana ramka obrazu, ale dopiero drugi sygnał ostatecznie określa czy kolejne bajty na szynie danych powinny być odczytywane przy narastającym zboczu sygnału PIXCLK (lub opadającym w zależności od konfiguracji, rysunek 6 ).
Rys. 6. Przebiegi czasowe sygnałów interfejsu danych wideo
Czas trwania stanu aktywnego linii Line_Valid jest określony przez okres sygnału PIXCLK oraz liczbę kolumn wyjściowej matrycy pikseli razy dwa. W konkretnym przypadku, jeżeli częstotliwość wyjściowego sygnału zegarowego wynosi 25 MHz (okres = 40 ns), to przesłanie 320 pikseli (640 bajtów) jednego wiersza zajmie 25,6 us, a ponieważ wierszy w matrycy jest 240 to przesłanie całej ramki zajmie 6,144 ms. W rzeczywistości jednak pomiędzy stanem aktywnym zarówno sygnału Line_Valid jak i Frame_Valid występują przedziały czasowe zwane Horizontal Blanking oraz Vertical Blanking odpowiednio. Służą one nie tylko po to by zmniejszyć częstotliwość wychodzących z kamery ramek obrazu, ale również dają pewien okres czasu na przetworzenie przez czujnik danych z matrycy CMOS. Stąd też proces odczytu ramki wideo obrazowo można przedstawić jako czytanie właściwych danych oraz odczekiwanie przez okres w którym trwa Horizontal lub Vertical Blanking (rysunek 7).
Rys. 7. Odczyt obrazu z kamery
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
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:
1 2 3 4 |
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:
1 2 3 4 5 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/*** 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/*** 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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ę:
1 2 3 |
/* Enable DCMI interface then start image capture */ DCMI_Cmd(ENABLE); DCMI_CaptureCmd(ENABLE); |
Konfiguracja kamery
Do komunikacji z kamerą po magistrali I 2 C wykorzystywane są cztery funkcje:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Odczyt rejestru pod adresem regAddr ushort MT9D111_ReadReg(uchar regAddr); // Zapis do rejestru pod adresem regAddr wartosci regVal uchar MT9D111_WriteReg(uchar regAddr, ushort regVal); // Odczyt wartosci zmiennej znajdujacej sie w sterowniku driverID, przesunietej // o wartosc varOffset od poczatku struktury i rozmiarze varSize bajtow ushort MT9D111_ReadVar(uchar driverID, uchar varOffset, uchar varSize); // Podobnnie jak wyzej, ale z zapisem uchar MT9D111_WriteVar(uchar driverID, uchar varOffset, uchar varSize, ushort varData); |
Konfigurację parametrów obrazów wyjściowych i sposobu w jaki są one wykonywane można dokonać na wiele sposobów wykorzystując sterowniki obecne w SOC (co jest łatwiejsze, ale nie pozwala poznać głębiej funkcjonowania kamery) lub też operując bezpośrednio na rejestrach. Tutaj zostały przedstawione podstawowe wiadomości na ten temat niezbędne do uzyskania żądanego obrazu drugą metodą. Aby była możliwa swobodna zmiana wartości rejestrów należy w pierwszej kolejności wyłączyć mikrokontroler przez wprowadzenie go w stan resetu (w nim są zaimplementowane sterowniki różnych funkcji jak np. autoekspozycja):
1 |
R0xC3:1[0] := (zapisanie jedynki do tego bitu wprowadza mikrokontroler w stan reset) |
Matryca elementów światłoczułych (pixel array) znajdująca się w bloku jądra czujnika (sensor core) jest zbudowana z 1688 kolumn i 1256 wierszy (rysunek 8). Pierwsze 52 i ostatnie 4 kolumny oraz pierwsze i ostatnie 20 wierszy matrycy pikseli nie reagują na padające na nie światło (zawsze są optycznie interpretowane jako czarne) – wykorzystuje się je do automatycznej regulacji poziomu czerni.
Rys. 8. Matryca pikseli CCD
W konfiguracji domyślnej generowany obraz ma rozdzielczość 1600 x 1200 pikseli (UXGA – Ultra Extended Graphics Array) i jest pobierany z matrycy począwszy od 28 wiersza i 60 kolumny. Zmianę pozycji i wymiarów ramki obrazu można dokonać za pomocą zapisu odpowiednich wartości do następujących rejestrów znajdujących się w banku 0 czujnika (Page 0 – Sensor Core):
1 2 3 4 |
R0x01:0 := 328 (numer pierwszego wiersza obrazu) R0x02:0 := 444 (numer pierwszej kolumny obrazu) R0x03:0 := 600 (liczba wierszy/wysokość obrazu) R0x04:0 := 800 (liczba kolumn/szerokość obrazu) |
Przy wypełnianiu tych rejestrów należy pamiętać, aby nie wchodzić w obszar czarnych pikseli oraz to, że minimalna wysokość obrazu wynosi 2 wiersze, a szerokość 9 (przy wykorzystaniu jednego ADC) oraz 17 (w trybie z dwoma ADC) kolumn.
Na matrycę czujnika jest nałożona siatka filtrów Bayera w wyniku obecności której każdy piksel jest czuły tylko na pewne spektrum światła i na wyjściu otrzymuje się intensywność jednego z trzech podstawowych kolorów (rysunek 9). Wiersze parzyste matrycy zawierają zielone i czerwone piksele, a nieparzyste – zielone i niebieskie. Podobnie jest dla kolumn, parzyste – zielone i niebieskie piksele, nieparzyste – zielone i czerwone piksele. Dlatego przy podawaniu numerów pierwszego wiersza i/lub kolumny obrazu należy się kierować zasadą „co drugi element”, czyli podawanie parzystych (lub nieparzystych w zależności od konfiguracji) numerów wierszy/kolumn. Istnieje również inny sposób polegający na przełączeniu bitów kolejności kolorowych pikseli:
1 2 3 4 |
R0x08:1[0] := 0 (najpierw idą wiersze z niebieskim pikselami) := 1 (najpierw idą wiersze z czerwonymi pikselami) R0x08:1[1] := 0 (najpierw idą kolumny z zielonymi pikselami) := 1 (najpierw idą kolumny z czerwonymi/niebieskimi pikselami) |
Rys. 9. Układ pikseli RGB w matrycy CCD
Wyjściowe dane z bloku Color Pipeline mogą mieć format YUV lub RGB, a za wybór odpowiada 5 bit rejestru R0x08:1 (ustawiony – RGB, wyzerowany – YUV). Oprócz tego istnieje możliwość ustawienia czasu przetwarzania pikseli lub przesłony elektronicznej (Shutter Width). Im jej wartość jest większa, tym obraz będzie jaśniejszy, ale generowany z mniejszą częstotliwością:
1 |
R0x9:0 := 700 (wartość przesłony) |
Ponieważ wyświetlacz LCD może przedstawić obraz o maksymalnych wymiarach 320×240 pikseli, więc wyjściowy obraz z kamery również powinien mieć taką rozdzielczość. Można to zrobić modyfikują wyżej opisane rejestry, jednak wtedy pole widzenia będzie niewielkie. Alternatywny sposób polega na wykorzystaniu decymatora, czyli funkcji skalującej obraz do żądanych rozmiarów. Za jej włączenie odpowiada 8 bit rejestru R0x08:1, a parametry decymacji zapisuje się zgodnie z poniższym:
1 2 3 4 5 6 |
R0x11:0 := 0 (położenie X pierwszego punktu decymacji na obrazie wejściowy) R0x12:0 := 0 (położenie Y pierwszego punktu decymacji na obrazie wejściowy) R0x13:0 := 800 (szerokość obrazu wejściowego) R0x14:0 := 600 (wysokość obrazu wejściowego) R0x16:0 := 820 (waga dla horyzontalnej decymacji) R0x17:0 := 820 (waga dla wertykalnej decymacji) |
Waga decymacji jest wyznaczana ze wzoru:
Dodatkowo czujnik kamery udostępnia funkcje binningu oraz pomijania wierszy i kolumn. Pierwszy sposób zmniejszenia rozdzielczości polega na wyznaczeniu średniej wartości dla 4 pikseli o tym samym kolorze pobranych z kwadratowego obszaru i powinno się stosować raczej tą metodę, gdyż pomijanie wprowadza efekt aliasingu. Jednocześnie można wybrać tryb pracy z dwoma przetwornikami ADC w celu nie tylko skrócenia czasów przetwarzania obrazu z matrycy, ale również zwiększenia częstotliwości wysyłanych ramek o 2 razy:
1 |
R0x21:0 :=0 (binning wył., tryb 2xADC, brak pomijania wierszy/kolumn) |
Standardowo po włączeniu zasilania sygnał PIXCLK posiada częstotliwość dwa razy mniejszą niż wejściowy sygnał XCLK (pracuje tylko jeden przetwornik ADC). W przypadku gdy koniecznym jest zwiększenie lub zmniejszenie tej częstotliwości można wykorzystać podblok PLL wbudowany w blok jądra czujnika. Należy jednak pamiętać by zachowane były pewne warunki (tablica 4).
Tab. 4. Warunki stawiane sygnałom taktującym w systemie kamery
Również moduł DCMI mikrokontrolera wprowadza ograniczenie maksymalnej częstotliwości taktowania pikseli do 54 MHz. Uwzględniając powyższe informacje konfiguracja bloku PLL wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//*************************** // Fin = 56 MHz // Fpfd = 8 MHz // Fvco = 232 MHz // Fout = 37.3MHz //*************************** R0x65:0 := 0xA000 //Clock: <15> PLL BYPASS = 1 --- omin bloku PLL R0x65:0 := 0xE000 //Clock: <14> PLL OFF = 1 --- wylacz blok PLL R0x66:0 := 0x1C06 //PLL Control 1: <15:8> M = 28, <5:0> N = 6 R0x67:0 := 0x0502 //PLL Control 2: <6:0> P = 2 R0x65:0 := 0xA000 //Clock: <14> PLL OFF = 0 --- wlacz blok PLL Delay(10); //Odczekaj 10 ms R0x65:0 := 0x2000 //Clock: <15> PLL BYPASS = 0 --- wybierz sygnal z bloku PLL //jako glowny zegar systemu |
Ostatecznie wszystkie powyższe ustawienia zostały zapisane w tablicy struktur wykorzystywanej w funkcji inicjalizującej MT9D111_Init():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
struct MT9D111_REG_STRUCT MT9D111_InitStructure[] = { //************************ // Konfiguracja bloku PLL //************************ //*************************** // Fin = 56 MHz // Fpfd = 8 MHz // Fvco = 232 MHz // Fout = 37.3MHz //*************************** {0, 0x65, 0xA000}, //Clock: <15> PLL BYPASS = 1 --- make sure that PLL is bypassed {0, 0x65, 0xE000}, //Clock: <14> PLL OFF = 1 --- make sure that PLL is powered-down {0, 0x66, 0x1C06}, //PLL Control 1: <15:8> M = 28, <5:0> N = 6 {0, 0x67, 0x0502}, //PLL Control 2: <6:0> P = 2 {0, 0x65, 0xA000}, //Clock: <14> PLL OFF = 0 --- PLL is powered-up {MT9D111_DELAY,0,10},//Delay 10 ms {0, 0x65, 0x2000}, //Clock: <15> PLL BYPASS = 0 --- enable PLL as master clock //**************************************** // Konfiguracja pozycji i wymiarow obrazu //**************************************** {0, 0x01, 0x0148}, //Row Start = 328 {0, 0x02, 0x01BC}, //Column Start = 444 {0, 0x03, 0x0258}, //Row Width = 600 {0, 0x04, 0x0320}, //Column Width = 800 {1, 0x08, 0x01F8}, //Decimator enabled {1, 0x12, 0x0320}, //X1 Coordinate for Crop Window +1; width = 800 {1, 0x14, 0x0258}, //Y1 Coordinate for Crop Window +1; height = 600 {1, 0x16, 0x0334}, //Weight for H Decimation = 820 {1, 0x17, 0x0334}, //Weight for V Decimation = 820 //****************************************** // Konfiguracja parametrow wykonania obrazu //****************************************** {0, 0x09, 0x02BC}, //Shutter Width = 700 --- Ustawienie wartosci przeslony {0, 0x20, 0x0300}, //Read Mode (B): binning disabled, use both ADCs, normal UXGA size, column/row skip disabled {0, 0x21, 0x0000}, //Read Mode (A): binning disabled, use both ADCs, column/row skip disabled //********************************************** // Konfiguracja formatu obrazu wyjsciowego //********************************************** {1, 0x09, 0x0009}, //Factory bypass: Data from SOC going to Dout pads {1, 0x97, 0x0022}, //Output Format Config: RGB output, swap odd/even bytes {MT9D111_TERM,0,0} }; |
Główna pętla programu
W funkcji main(), po skonfigurowaniu wszystkich peryferii oraz wyświetlacza i kamery, wykonywana jest pętla while(), w której okresowo sprawdzane jest czy został dotknięty panel dotykowy i jeżeli tak to następuje przełączenie aktualnego ekran (wyświetlanej zawartości). Dostępnych jest 3 ekrany: informacyjny (wszelkie wiadomości tekstowe), podglądu obrazu z kamery oraz wykonanie zdjęcia (które może być zapisane na kartę SD – wymaganym jest dodanie odpowiedniego kodu obsługi).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
int main(void){ __IO uint32_t i = 0; /*!< At this stage the microcontroller clock setting is already configured, this is done through SystemInit() function which is called from startup file (startup_stm32fxxx_xx.s) before to branch to application main. To reconfigure the default setting of SystemInit() function, refer to system_stm32fxxx.c file */ /* Konfiguracja modulu USB */ USBD_Init(&USB_OTG_dev, USB_OTG_FS_CORE_ID, &USR_desc, &USBD_CDC_cb, &USR_cb); RCC_Config(); /* Wlaczenie sygnalow taktujacych peryferia oraz konfiguracja SysTick */ NVIC_Config(); /* Konfiguracja kontrolera NVIC */ GPIO_Config(); /* Konfiguracja wyprowadzen */ EXTI_Config(); /* Konfiguracja zewnetrznych zrodel przerwania */ DCMI_Config(); /* Konfiguracja modulu DCMI */ I2C_Config(); /* Konfiguracja kontrolera I2C1 */ LCD_Init(); // Inicjalizacja wyswietlacza LCD_Clear(BG_Color); // Czyszczenie ekranu LCD_SetBackColor(BG_Color); // Ustawienie koloru tla LCD_SetTextColor(White); // Ustawienie koloru tekstu LCD_SetFont(&Font8x8); // Ustawienie czcionki 8x8 pikseli Delay(10); // Wait 100ms MT9D111_WriteReg(PAGE_SELECT_REG, 0x0); // Select Page 0 - Sensor Core if(MT9D111_ReadReg(0x00) == 0x1519){ // Camera MT9D111 detected sprintf(camera_state_info, " MT9D111 not connected. "); } else{ // Camera MT9D111 not detected sprintf(camera_state_info, " MT9D111 connected. "); } // Wymagana sekwencja umozliwiajaca swobodne operowanie bezpośrednio // na rejestrach czujnika kamery MT9D111_WriteReg(PAGE_SELECT_REG,0x1); // Select Page 1 - IFP regVal = MT9D111_ReadReg(UC_BOOT_MODE_REG); MT9D111_WriteReg(UC_BOOT_MODE_REG, (regVal | 0x01)); // Reset microcontroller Delay(10); // Wait 100 ms MT9D111_Init(); // Inicjalizacja kamery current_screen = 0; update_screen_x = 1; while(1){ // Tutaj mozna umiescic dodatkowe funkcje wymagajace okresowego wykonania // niezaleznie od tego ktory ekran jest wyswietlany aktualnie i czy TP jest wcisniety // Sprawdz czy wcisnieto TP if( Is_TP_Pressed() == TP_PRESSED){ TP_status = 0; // Czysczenie flagi // Czekaj na odpuszczenie TP while( Is_TP_Released() != TP_RELEASED ); TP_status = 0; // Czysczenie flagi ++current_screen; // Wybor nastepnego ekranu if( current_screen > 2 ){ current_screen = 0; } } switch(current_screen){ // Ekran informacyjny case 0:{ if(update_screen_x & 0x1){ update_screen_x <<= 1; current_line = 1; // Czyszczenie ekranu LCD_Clear(BG_Color); LCD_SetTextColor(White); LCD_DisplayStringLine(LINE(current_line++), (uint8_t *) camera_state_info); LCD_SetTextColor(Green); LCD_DisplayStringLine(LINE(current_line++), " Touch to continue..."); } break; } // Podglad case 1:{ if(update_screen_x & 0x2){ update_screen_x <<= 1; // Ustawienie pozycji i wymiarow obszaru wyswietlacza dla obrazu z kamery LCD_SetDisplayWindow(239, 319, 240, 320); LCD_SetCursor(0,319); LCD_WriteRAM_Prepare(); // Prepare to write GRAM MT9D111_WriteReg(PAGE_SELECT_REG, 0x0); // Select Page 0 - Sensor Core MT9D111_WriteReg(SHUTTER_WIDTH_REG, 700); // Ustawienie wartosci przeslony DCMI_Cmd(DISABLE); // Wylacz DCMI DCMI->CR &= 0xFFFD; // Wybierz tryb podgladu DCMI_Cmd(ENABLE); // Wlacz DCMI DCMI_CaptureCmd(ENABLE); // Wlacz przyjmowanie danych obrazu } break; } // Wykonanie zdjecia case 2:{ if(update_screen_x & 0x4){ //update_screen_x <<= 1; update_screen_x = 1; DCMI_CaptureCmd(DISABLE); // Wylacz przyjmowanie danych obrazu DCMI_Cmd(DISABLE); // Wylacz DCMI // Ustawienie pozycji i wymiarow obszaru wyswietlacza dla obrazu z kamery LCD_SetDisplayWindow(239, 319, 240, 320); LCD_SetCursor(0,319); LCD_WriteRAM_Prepare(); // Prepare to write GRAM MT9D111_WriteReg(PAGE_SELECT_REG, 0x0); // Select Page 0 - Sensor Core MT9D111_WriteReg(SHUTTER_WIDTH_REG, 2000);// Ustawienie wartosci przeslony Delay(10); // Czekaj 100ms DCMI->CR |= DCMI_CaptureMode_SnapShot; // Wybierz tryb wykonania zdjecia DCMI_Cmd(ENABLE); // Wlacz DCMI DCMI_CaptureCmd(ENABLE); // Wlacz przyjmowanie danych obrazu while(DCMI->CR & 0x1); // Czekaj na zakonczenie odbioru // Tutaj mozna wykonac odczyt obrazu z pamieci RAM wyświetlacza // i zapisanie go na karte SD } break; } default:{break;} } Delay(10); // Czekaj 100ms } } |
Aplikacja PC
Podczas realizacji projektu była tworzona prosta aplikacja (rysunek 10) w środowisku QtCreator, umożliwiająca łatwą i szybką konfigurację parametrów kamery. Pozwoliła ona na łatwiejsze zrozumienie zależności działania kamery od wartości zapisywanych do rejestrów i zmiennych sterowników funkcji.
Rys. 10. Prosta aplikacja konfigurująca kamerę
System operacyjny może nie rozpoznać właściwie podłączonego układu do portu USB komputera i wtedy należy ze strony pobrać plik o nazwie libusb-win32-bin-1.2.6.0 i z katalogu bin uruchomić program inf-wizard pozwalający dodać potrzebny sterownik (na liście powinien być element o nazwie STMicroelectronics Virtual COM Port).
Jan Szemiet