Когда-то, подключая камеру от мобильного телефона к микроконтроллеру 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
способен работать с шиной данных шириной до 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 .
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
» - вдруг пригодится кому.
Когда-то, подключая камеру от мобильного телефона к микроконтроллеру 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
способен работать с шиной данных шириной до 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 .
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
» - вдруг пригодится кому.
Подключение карты проводится с помощью двух функций:
1. С помощью функции disk_initialize
карта инициализируется.
2. Далее необходимо ее примонтировать f_mount
.
После успешного выполнения этих действий можно осуществлять различные операции с картой, такие как:
1. f_open
– открытие файла,
2. f_close
– закрытие файла;
3. f_mkdir
– создание директории;
4. f_chdir
– выбор директории;
5. f_write
– запись в файл;
6. f_read
– чтение из фала.
Работу с камерой и SD картой я описал, теперь рассмотрим работу программы в целом:
1. Инициализируем карту SD;
2. Инициализируем модуль камеры;
3. Открываем файл time.txt (в файле должна быть одна строчка формата 01.01.2014_12:00
), в котором хранится время для отсчета таймера RTC;
4. Считываем время и дату из файла и настраиваем RTC;
5. Закрываем файл time.txt;
6. Создаем директорию для хранения фотографий «LinkSpritePhoto»;
7. При нажатии кнопки PA0 на плате делаем фото и сохраняем на карточку (Пока делается фотография, горит синий светодиод);
8. В случае невыполнения какой либо из функций, сваливаемся в бесконечный цикл и мигаем зеленым светодиодом.
Для запуска камеры в непрерывном режиме необходимо раскомментировать в main.c следующие строчки:
//EnabledButtonStart = 101; // Для запуска в цикле раскомментировать эту строку
//Delay(300); // Задержка для запуска в цикле
И закомментировать эту строку:
EnabledButtonStart = 0; // Для запуска в цикле закомментировать эту строку
Тогда при включении, камера начнет писать фотографии непрерывно.
В целом, описание того как работает данная камера я составил, теперь можно и показать результат работы.
Или замедленная киносъёмка - это киносъёмка с частотой, меньшей стандартной частоты съемки и проекции в 24 кадра в секунду.
Начав изучать микроконтроллеры STM32 и написав «HellowWorld» с миганием светодиодом, я понял, что для лучшего понимания работы STM32 мне необходимо реализовать что-то более сложное с использованием большего количества периферии микроконтроллера. Так возникла идея создания Time-lapse камеры.
Разработанная мной камера будет делать фотографии примерно раз в 5 секунд и сохранять их на SD карту в формате jpeg. Далее их необходимо объединить на компьютере в видео файл.
Для создания камеры я использовал следующие компоненты:
Подключение карты проводится с помощью двух функций:
1. С помощью функции disk_initialize
карта инициализируется.
2. Далее необходимо ее примонтировать f_mount
.
После успешного выполнения этих действий можно осуществлять различные операции с картой, такие как:
1. f_open
– открытие файла,
2. f_close
– закрытие файла;
3. f_mkdir
– создание директории;
4. f_chdir
– выбор директории;
5. f_write
– запись в файл;
6. f_read
– чтение из фала.
Работу с камерой и SD картой я описал, теперь рассмотрим работу программы в целом:
1. Инициализируем карту SD;
2. Инициализируем модуль камеры;
3. Открываем файл time.txt (в файле должна быть одна строчка формата 01.01.2014_12:00
), в котором хранится время для отсчета таймера RTC;
4. Считываем время и дату из файла и настраиваем RTC;
5. Закрываем файл time.txt;
6. Создаем директорию для хранения фотографий «LinkSpritePhoto»;
7. При нажатии кнопки PA0 на плате делаем фото и сохраняем на карточку (Пока делается фотография, горит синий светодиод);
8. В случае невыполнения какой либо из функций, сваливаемся в бесконечный цикл и мигаем зеленым светодиодом.
Для запуска камеры в непрерывном режиме необходимо раскомментировать в main.c следующие строчки:
//EnabledButtonStart = 101; // Для запуска в цикле раскомментировать эту строку
//Delay(300); // Задержка для запуска в цикле
И закомментировать эту строку:
EnabledButtonStart = 0; // Для запуска в цикле закомментировать эту строку
Тогда при включении, камера начнет писать фотографии непрерывно.
В целом, описание того как работает данная камера я составил, теперь можно и показать результат работы.
Когда-то, подключая камеру от мобильного телефона к микроконтроллеру 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
» - вдруг пригодится кому.