STM32F4Discovery – подключаем камеру по интерфейсу DCMI
Когда-то, подключая камеру от мобильного телефона к микроконтроллеру STM32F407VGT6 (который имеет место быть на плате STM32F4Discovery), я даже не думал о том, что данный контроллер имеет специальный аппаратный интерфейс для данного дела. Может быть, невнимательно читал даташит, но я всегда считал, что интерфейс DCMI имеется только у чипов в корпусах UFBGA176 и LQFP от 144 ног. Однако, не так давно, открыл для себя озвученную деталь: 100-ногий STM32F407 также имеет DCMI на борту.
Являясь большим любителем изучения и совместного запуска различного мобильного железа (в частности, LCD и камер) с МК, мимо такого открытия я просто так пройти не смог, и решил восполнить данный пробел в изучении периферии STM32. Собственно, данный материал и посвящен описанию осуществления возникшей затеи.
Совсем немножко теории.
Прежде всего, нужно представлять, о чем идет речь – а точнее, что такое CMOS-камера, и с чем ее едят.
Данный вид камер осуществляет вывод информации с сенсора в цифровом виде: RGB, YCbCr, а также в сжатом виде – JPEG. У различных камер имеются свои нюансы в плане возможностей, я буду рассматривать вполне конкретный случай камеры с небольшим разрешением (VGA, 640x480), вытащенной мною в незапамятные времена из телефона «Siemens C72» (сенсор PixelPlus PO2030N). Данная камера является наиболее подходящей для изучения в виду простоты функционирования и принадлежности к типу более-менее распространенному. Давным-давно я вытравил для нее небольшую плату (для большего удобства подключения) – со стабилизатором на 2.8 В и подтягивающими резисторами на шине I2C. Вот она (шлейф и разъем камеры скрыты под кожухом).
Кроме нюансов в области формата данных, камеры также могут отличаться в области количества выводов синхронизации. У большинства (по моему мнению) сенсоров в наличии имеются специальные выводы строчной и кадровой синхронизации; но есть камеры, имеющие только лишь вывод строба пикселя, а о начале новой строки/кадра они дают знать с помощью специальных передаваемых кодов (к примеру, 0x00 или 0xFF). Камера, что есть у меня в наличии, имеет выводы внешней синхронизации.
Можно прикинуть примерное схематическое изображение камеры в виде блока.
По большей части CMOS-камеры управляются по интерфейсу I2C (хотя я встречал устройства, управляющиеся и по UART). По I2C производится настройка различных параметров, таких как: разрешение, цветовая гамма, формат данных на выходе, и т.д.
Вывод EXTCLK – тактирование камеры, которым нужно обеспечить ее извне. DCLK – строб-сигнал, по переднему или заднему фронту которого на шине данных камеры фиксируются данные (к примеру, байт данных одного пикселя матрицы, либо байт данных «полупикселя», если камера работает в режиме RGB565). HSYNC – сигнал горизонтальной синхронизации, свидетельствующий о начале новой строки, а VSYNC – сигнал синхронизации, активный уровень которого указывает на начало нового кадра. Выводы D0..D7 – шина данных; как правило, у подобных камер она восьмиразрядная.
Теперь подробнее о сигналах синхронизации.
На графиках видно, что камера настроена на активность сигнала DCLK только в активную фазу HSYNC (а именно эта фаза нас и интересует, тактовый сигнал в период «перевода строки» нам не интересен). Если камера настроена на разрешение 320x240, то в период каждого импульса HSYNC укладывается 320 импульсов DCLK, а в период VSYNC – 240 HSYNC.
При увеличении масштаба, видим, что творится на шине данных.
По переднему фронту (в данном случае) с шины данных снимается байт, который можно отправлять сразу на дисплей для отображения, либо «складывать» в буфер для последующей обработки.
В теории все более-менее понятно, теперь об интерфейсе DCMI микроконтроллера STM32.
Интерфейс DCMI способен работать с шиной данных шириной до 14 разрядов, поддерживает как аппаратную, так и программную синхронизацию, а также форматы данных: YCbCr, RGB и JPEG.
Кроме того, DCMI содержит буфер FIFO, имеет возможность настройки прерываний (в том числе и по заполнению регистра данных) и настройки работы через DMA.
Прерывания от DCMI могут вызываться при наступлении следующих условий: окончание линии, окончание кадра, переполнение приемного буфера, обнаружение ошибки синхронизации (при внутренней синхронизации).
В некоторое недоумение меня ввело отсутствие специального вывода тактирования камеры. Я не знаю, по каким причинам разработчики из SGS Microelectronics от него отказались, но по мне, было бы весьма удобно иметь, к примеру, настраиваемый источник тактовой частоты.
Лично я задействовал таймер-счетчик общего назначения, включенный в режиме ШИМ на генерацию меандра частотой 4 МГц. Большого FPS, конечно, с такой тактовой не получить, но сразу оговорюсь – дисплей, который я использую, подключен не к FSMC, поэтому самая длительная функция во всей цепи – функция вывода на LCD, следовательно, при бОльшей частоте происходит срыв вывода изображения на экран. Посему перед выгрузкой я глушу таймер, а после нее – включаю таймер снова.
Аппаратный модуль DCMI содержит, кроме регистра данных, десять регистров управления/статуса. Это: регистр управления (DCMI_CR), регистр состояния (DCMI_SR), регистр состояния прерываний (DCMI_RIS), регистр разрешения прерываний (DCMI_IER), регистр маски прерываний (DCMI_MIS), регистр сброса флагов прерываний (DCMI_ICR), регистр кодов внутренней синхронизации (DCMI_ESCR), регистр сброса маски кодов внутренней синхронизации (DCMI_ESUR), регистр стартовых значений при захвате части кадра (DCMI_CWSTRT) и регистр величины фрагмента кадра в режиме CropWindow (DCMI_CWSIZE). И, само собой, регистр данных – DCMI_DR.
В данном случае регистры, относящиеся к захвату части кадра и внутренней синхронизации нас не интересуют. Прерывания я тоже решил пока оставить в покое, поэтому рассмотреть подробнее стоит только регистр управления DCMI_CR и регистр состояния DCMI_SR.
Регистр управления дает нам возможность полностью настроить формат взаимодействия с камерой: размер шины данных, активные уровни линий HSYNC и VSYNC, и т.д.
По порядку. Бит ENABLE – само собой разумеется, включение интерфейса в работу. Поле EDM (extended data mode) – размер шины данных; шина у моей камеры восьмиразрядная, так что это поле следует установить в значение «00». Поле FCRC (frame capture rate control) дает возможность немного регулировать FPC: 00 – захватываются все приходящие кадры, 01 – каждый второй кадр, 10 – каждый четвертый. Биты VSPOL и HSPOL – активные уровни линий кадровой и строчной синхронизации. Активные уровни игнорируются, и данные в периоды активности не захватываются, это следует учитывать. PCKPOL – бит активного уровня строба пикселя – по какому фронту сигнала считывать данные с шины: переднему, или заднему. ESS – бит выбора способа синхронизации: внешняя, либо внутренняя. JPEG – выбор формата приходящих данных – сжатый, или нет. CROP – бит выбора захвата фрагмента кадра (crop window). Если данный бит установить в единицу, то интерфейс будет захватывать данные в окне, определяемом значениями в регистрах DCMI_CWSTRT и DCMI_CWSIZE.
Итак, настраиваем. Так как я привык использовать стандартную библиотеку периферии от ST (хотя в первых итерациях работы с новой периферией никогда ее не использую, пока не поковыряюсь в регистрах «ручками»), настройку привожу именно с использованием библиотеки.
void DCMIInitialRoutine(void) {
DCMI_InitTypeDef DCMI_CamType;
DCMI_DeInit();
DCMI_CamType.DCMI_CaptureMode = DCMI_CaptureMode_Continuous;
DCMI_CamType.DCMI_CaptureRate = DCMI_CaptureRate_All_Frame;
DCMI_CamType.DCMI_ExtendedDataMode = DCMI_ExtendedDataMode_8b;
DCMI_CamType.DCMI_SynchroMode = DCMI_SynchroMode_Hardware;
DCMI_Init(&DCMI_CamType);
DCMI_CaptureCmd(ENABLE);
DCMI_Cmd(ENABLE);
return;
}
Собственно, для моих нужд можно было не трогать ни одного бита в регистре DCMI_CR – по умолчанию они сброшены – кроме битов CAPTURE и ENABLE.
Интерфейс сконфигурирован и готов к работе. После подачи тактового сигнала камере, интерфейс начнет принимать данные, которые нам необходимо обрабатывать.
Задачу для начала я поставил перед собой максимально простую – выводить изображение на дисплей, так что и обработка данных будет минимальной.
В своевременном считывании данных из приемного буфера нам поможет статусный регистр DCMI_SR.
Для чтения доступно весьма скудное количество битов – всего три. Биты HSYNC и VSYNC сигнализируют о состоянии соответствующих линий: активная фаза, либо перевод строки; самым интересным является бит FNE. Он указывает нам на заполнение буфера данными. Или на не заполнение.
Проверяя в постоянном цикле состояние бита FNE в DCMI_SR, узнаем о приходе данных в приемный тридцатидвухразрядный буфер. В моем случае данные будут располагаться так:
При установке бита FNE в регистре состояния DCMI_SR в приемном буфере будут содержаться четыре байта, данные двух соседних пикселей: Byte0 и Byte1 – 16 разрядов пикселя n, а Byte2 и Byte3 – 16 разрядов пикселя n+1. Мне останется только их объединить и отправить для отображения на дисплей. Итак, вот каким образом выглядит основной цикл:
while (1) {
while ((DCMI_GetFlagStatus(DCMI_FLAG_FNE)) == RESET); //Waiting for the buffer
TIM_Cmd(TIM3, DISABLE); //Disable CAM clock
cam_grab = (DCMI->DR); //Reading buffer
SendDataByte_LCD (cam_grab);
cam_grab = (DCMI->DR)>>16; //Reading 2nd part of the buffer
SendDataByte_LCD (cam_grab);
TIM_Cmd(TIM3, ENABLE); //Enable CAM clock again
}
То есть, я жду установки бита FNE в регистре состояния DCMI_SR, а после – в два захода выгружаю по 16 бит данных на дисплей.
На этом моменте хотелось бы подойти к логическому завершению, но не тут-то было.
После прошивки и перезапуска МК на дисплее я увидел… нет, я увидел вполне себе знакомую собственную физию, но в черно-синих оттенках. Красный и зеленый цвета отсутствовали напрочь.
После недолгих разборок с дебагером было обнаружено следующее: регистр данных интерфейса содержал лишь 16 бит данных одного пикселя, причем младшие 8 бит располагались на месте Byte0 (см. рис. выше), а старшие – на месте Byte2. Пространства Byte1 и Byte3 же были пусты. До сих пор я не понял, откуда такое несоответствие документации действительности, и, возможно, обращусь в STM.
В итоге удалось получить изображение с камеры с помощью интерфейса DCMI, хотя и не без некоторых сложностей. На рисунке привожу фотографию дисплея, на который выводилось изображение демо-борды STM32F3Discovery с моей камеры.
А вот что увидим на выводах EXTCLK, PIXCK, HSYNC и VSYNC, если подключить логический анализатор.
Всё выглядит именно так, как и ожидалось: 240 импульсов HSYNC укладывается в длительность одного VSYNC, 320 PIXCK – в одном HSYNC. В активную фазу HSYNC камера не выдает сигналов PIXCK – именно так, как она была настроена.
Вообще говоря, интерфейс меня несколько разочаровал. Отсутствие «штатной» ноги тактирования камеры, отсутствие мало-мальски интересных встроенных фишек (а как насчет аппаратного кодера JPEG?), да еще и танцы с бубном вокруг располовиненного FIFO…
Организуя работу с камерой на прерываниях PIXCK, HSYNC и VSYNC я не имел столько головняка, сколько поимел, работая с камерой с помощью аппаратного DCMI.
Тем не менее, в ближайшее время буду пробовать осуществлять захват кадра, сжатие оного в JPEG, и пробовать писать картинку на SD карту.
PS. На всякий случай даю ссылку на проект для «Code::Blocks» — вдруг пригодится кому.
www.dropbox.com/sh/nfjdwqsdzlr7djm/9v2mQM8uYV
Автор: Sub_Dia