Category: Новости

November 11, 2019 finereader No comments exist

Камера OV7670. Трудный путь

Это уже серьёзная задача, граничащая местами с профессиональной разработкой. Готовьтесь к трудному пути. 

 

Хотя, конечно, камера то уже есть, и надо только научиться ей пользоваться

Изучаем усилия предшественников, коим хвала – computerNerd, статья и статья и ещё статья.

Изучаем как цифровая камера работает – ПЗС матрица… Для программиста это просто двумерный массив с данными пикселей, который надо как-то с камеры забрать, пользуясь строчной и кадровой синхронизацией.

Нужно посмотреть кодировки цвета пикселя RGB, YUV (мы возьмём именно YUV, где на один пиксель приходится 2 байта).

 

Ну и надо понимать, что такой сложный прибор, в котором есть автоматические функции управления временем экспозиции, балансом белого, гаммой, резкостью и контрастом, фильтр 50Гц мерцания, корректировка смещения центра линзы и много чего ещё потому и сложный изнутри, чтобы быть простым для внешнего управления. Камера внутри себя содержит DSP (ЦОС) процессор. Для программиста задача сводится к начальной конфигурации камеры – установке определенных битов нужных регистров, а затем просто собираем байты пикселей изображения.

Камера способна выдать VGA картинку 640х480 со скоростью до 30 кадров/с, но для ардуины лучше-меньше, 320х240 еще ничего.

Если делаете плохо, т.е. как на картинке ниже – позаботьтесь о состоянии портов перед началом, чтобы точно ничего на выход не вылилось (и не было пинов, сконфигурированных на выход и подтягивающих резисторов), а то сгорит это чудо полупроводниковой техники.

Лучше подключать вот так (скопировал с одного блога, который пропал куда-то, ну не пропадать же хорошей картинке)

Разъём камеры

Сигнал такта на входе и выходе камеры

Итак, чтобы оживить любое электронное устройство нужен тактирующий импульс или Clock. На ардуине на 11 пине выдадем 8 МГц ШИМ на пин камеры XCLK.

Теперь можно убедиться в работоспособности камеры – на выходе PCLK – те же 8 МГц. Ардуино не сможет обрабатывать байтовые посылки с такой скоростью, поэтому воспользуемся внутренним (для камеры) делителем частоты – максимальное значение 31 – оно хранится в регистре с именем CLKRC.

Расчет внутренней частоты (умножать не будем PLL_Multiplier=1):

Fint = 8 000 000 / (2*31+1) = 125 000 Гц – с такой частотой будут выдаваться байты кадра в ардуину.

Кадр 320х240 = 76800 пикселей. Для RGB и YUV пиксель кодируется двумя байтами. Байт же передаётся за один такт параллельно по D0-D7.

125 000 / (76800 * 2) = 0.8 кадров/сек.

 

Настройка камеры

Далее необходимо сконфигурировать регистры камеры – установить формат изображения 320х240, задать цветовую кодировку, установить делитель частоты и пр. Для этого используется интерфейс SCCB  – аналог I2C (в даташите МК AVR он ещё называется TWI).

Собственно, производитель привел регистры (адрес-значение), которые надо изменить для перехода в режим QVGA (320×240)

Ёще разберемся в них отдельно, а CLKRC тут для 30 кадров/с.

Обработка видео потока

Осталось только забрать видео поток.

Начинается новый кадр – ждём переключения VSYNC с 1 на 0.

Строчную синхронизацию можно брать по HREF, а можно просто по счетчику длины строки.

По байтовому такту PCLK (переход в 0 – начало нового байта) забираем байт с параллельного порта D0-D7 (наверное, совсем правильно будет забирать байт в момент перехода PCLK из 0 в 1). Для YUV первый байт содержит информацию о пикселе в градациях серого, второй – о цвете. Для простоты пока второй байт не берем и просто ждем PCLK 1-0-1.

 

Самая захватывающая (дух и само изображение) часть программы выглядит так:

Большое отступление. Загадочные регистры

Регистры процессора (микроконтроллера) — блок ячеек памяти, образующий сверхбыструю оперативную память внутри процессора. Есть регистры, используемые самим процессором для вычислений, например, при выборке из памяти очередной команды она помещается в регистр команд, а при выборке переменных они помещаются в регистры операндов.

Но в микроконтроллере есть ещё и другие регистры – хранящие настройки разных периферийных устройств. С ними в основном и имеет дело программист. Например регистр PORTB, хранящий состояние выводов (пинов), или регистр DDR, хранящий настройки, находятся ли выводы (пины) в состояние входа или выхода.

В случае с камерой, регистр – это служебная очень быстрая память, например ячейки по 8, 16 бит, к которой можно обратиться по адресу. На стороне камеры работает программа, которая управляет камерой в соответствии с настройками, хранящимися в этих регистрах. Эта программа, в том числе, принимает посылки по I2C – первые 8 бит – адрес, следующие 16 бит – значение регистра. Вот и все. Такой вот протокол обмена.

 

Конфигурировать регистры будем булевыми операциями – AND (&) и OR(|).

Ну, говорить о том, что представлять содержание регистра удобнее всего в шестнадцатеричной или двоичной системе не будем

Также умолчим о возможности калькулятора (вид->инженерный) переводить числа между разными системами

Вот таблица истинности – правила двоичной арифметики, ну это уж почти всем известно.

Вот тут незадачливые программисты найдут наконец применение булевой логике, так ненавистной им в школе

Ну раз регистр это просто ячейка, например, из 8 бит, то выставим в регистре с именем PORTB восемь единичек:

Или все нули:

А теперь, как выставить 1 в первом бите, не изменив состояние других – при помощи OR (операция выполняется побитово):

Как же выставить 0 в любом бите – при помощи AND

Выставляем 0 в первом бите, не изменив состояние других – при помощи AND (операция выполняется побитово):

Если не понятно, прочитайте статью на customelectronics.ru

Начнём с настройки ардуино

Заглянув в datasheet на микроконтроллер ATmega8 семейства AVR (да, да, именно такой и в вашей ардуине), в ужасе ничего не понимаешь понимаешь, что выходы соответствуют трем портам B, C, D – PB0-5, PC0-5, PD0-7. В Arduino эти названия переименованы в цифровые выводы D0-D13 и аналоговые входы A0-A5.

Выводы управляются регистрами PORTх и DDRх.

Например, вывод PB5 управляется пятым битом в регистрах PORTB (состояние вывода – 0 или 1, 0В или 5В) и DDRB (настройка вывода – на вход(0) и выход(1));

Итак, чтобы подать 1 (5В) на 13 пин ардуино (PB5):

Надо настроить пин на работу в режиме выхода

Выставить 1 в 5 бите регистра PORTB

Вот настройки портов ардуино (тут не всё, но многое)

Подробнее о настройке камеры

Всего настроечных регистров более 100, но для них есть стандартные значения. В принципе, менять нужно только то, что требуется для перехода в требуемый режим.

Все адреса и рекомендуемые значения регистров – уже подготовлены и хранятся в .h файлике в виде #define RegName RegAddress.

Перед началом работы производитель рекомендует все регистры сбросить в состояние «default values» выставив 7-й бит в регистре СОМ7 в 1 (СОМ7[7] = 0x80) используя функцию wrReg(reg_addr, reg_val).

Механизм изменения размера картинки (разрешения) выглядит так:

Чтобы получить, например, 256х128 размера картинку, надо сначала передискретизировать (down sampling)VGA – взять каждый второй пиксель и получить 320х240, а затем использовать масштабирование (zoom) с коэффициентами 0.8 по горизонтали и 0,53 по вертикали сделает 256х128.

 

Чтобы изменить разрешение на 320х240 нужен только down sampling

Для этого надо разрешить масштабирование (в нашем случае только down sampling), выставив в 1 второй бит регистра СОМ3

Устанавливаем коэффициент 2 передискретизации по вертикали и горизонтали – 1 в битах [4] и [0] регистра SCALING_DCWCTR

Устанавливаем делитель такта PCLK на 2 на выходе из DSP в регистре SCALING_PCLK_DIV (1 в нулевой бит) – видимо, из-за пониженной скорости выдачи пикселей с матрицы (берем каждый второй) и частота работы DSP должна быть понижена

Разрешая масштабирование в регистре СОМ3, не забыть про СОМ14[3]

Далее вертикальные и горизонтальные размеры окна в регистрах VSTART, VSTOP, HSTART, HSTOP – ничего менять не надо.

Все требуемые для изменения регистры объединены в структуры, которые посылаются по IIC функцией wrSensorRegs(QVGA_OV7670).

October 12, 2019 siteprovisor No comments exist

Программируем по настоящему. Загадочные регистры

Немного о том, что знают настоящие не “ардуино-зависимые” программисты о микроконтроллерах

И еще о том, зачем в школе мучают булевой алгеброй

Регистры процессора (микроконтроллера) — блок ячеек памяти, образующий сверхбыструю оперативную память внутри процессора. Есть регистры, используемые самим процессором для вычислений, например, при выборке из памяти очередной команды она помещается в регистр команд, а при выборке переменных они помещаются в регистры операндов.

Но в микроконтроллере есть ещё и другие регистры – хранящие настройки разных периферийных устройств. С ними в основном и имеет дело программист. Например регистр PORTB, хранящий состояние выводов (пинов), или регистр DDR, хранящий настройки, находятся ли выводы (пины) в состояние входа или выхода.

 

Регистр – это служебная очень быстрая память, например ячейки по 8, 16 бит, к которой можно обратиться по адресу. В МК работает программа, которая управляет его функционированием в соответствии с настройками, хранящимися в этих регистрах. (Например, частота ЦПУ, скорость обмена по СОМ порту – UART и проч.)

Конфигурировать регистры будем булевыми операциями – AND (&) и OR(|).

Ну, говорить о том, что представлять содержание регистра удобнее всего в шестнадцатеричной или двоичной системе не будем.

Также умолчим о возможности калькулятора (вид->инженерный) переводить числа между разными системами

.

Вот таблица истинности – правила двоичной арифметики, ну это уж почти всем известно

Ну раз регистр это просто ячейка, например, из 8 бит, то выставим в регистре с именем PORTB восемь единичек:

Или все нули:

А теперь, как выставить 1 в первом бите (обратите внимание на нумерацию битов в регистре – начинается 0), не изменив состояние других – при помощи OR (операция выполняется побитово):

Как же выставить 0 в любом бите – при помощи AND

Выставляем 0 в первом бите, не изменив состояние других – при помощи AND (операция выполняется побитово):

Вот целый курс по программированию МК AVR “правильно” таким hard core способом, который в общем то и используют настоящие разработчики (customelectronics.ru)

Зажжем диод на ардуино, заодно попрактикуемся с регистрами.

Заглянув в datasheet на микроконтроллер ATmega8 семейства AVR (да, да, именно такой и в вашей ардуине), в ужасе ничего не понимаешь понимаешь, что выходы соответствуют трем портам B, C, D – PB0-5, PC0-5, PD0-7. В Arduino эти названия переименованы в цифровые выводы D0-D13 и аналоговые входы A0-A5.

Выводы управляются регистрами PORTх и DDRх.

Например, вывод PB5 управляется пятым битом в регистрах PORTB (состояние вывода – 0 или 1, 0В или 5В) и DDRB (настройка вывода – на вход(0) и выход(1));

Итак, чтобы подать 1 (5В) на 13 пин ардуино (PB5):

Вот так просто

August 10, 2019 finereader No comments exist

Проектируем и травим плату

Делаем с учениками небольшое электронное устройство – плату-коммутатор с управлением от Arduino.


Плата должна включать/выключать (коммутировать) большие нагрузки во внешней цепи (например, управлять лампочкой в сети 220В или электродвигателем в 12В сети автомобиля).

 

В качестве коммутирующего элемента (ключа) будем использовать электро-механическое реле. В нем контакты замыкаются под действием магнитной силы, только магнит там не обычный (постоянный), а электромагнит, работающий вот так:

 

Итак, пропуская ток по катушке нашего реле можно притягивать металлические контакты друг к другу, тем самым замыкая/размыкая внешнюю цепь.

Только вот кто будет пропускать или не пропускать ток по катушке – для этого нужна ещё одна цепь (внутренняя). 

 

Поскольку ножка Arduino не способна самостоятельно запитать катушку реле, запитаем катушку (обмотку) от отдельного источника. Обмотка и этот источник образуют отдельную цепь (мы назвали её внутренней), которую необходимо замыкать/размыкать каким-то ключом. Этим ключом будет служить транзистор (биполярный), управляемый ножкой Ардуино.

 

Транзистор – это такой электронный прибор с изменяемым сопротивлением (как потенциометр, например).

 

Ну вот, скажите вы, нужен ключ, а тут какое-то сопротивление, еще зачем-то и переменное.


Ну так замкнутый ключ – это почти нулевое сопротивление, разомкнутый – почти бесконечное.

Транзистор умеет менять свое сопротивление в этих (почти) пределах.

 

Для взятого транзистора (bc337) ток в 5 мА, текущий через Базу (можно сравнить Базу с управляющим движком потенциометра или рукояткой ключа), делает сопротивление транзистора на участке Э-К (Эмиттер-Коллектор) практически нулевым.

 

Электрическую схему разработаем в EagleCAD (бесплатная версия для плат, меньших 120х80мм). Там есть привязка электрической схемы к проектируемой плате.
Электрическую схему можно смоделировать в Multisim (есть бесплатная версия для студентов).

Толщины дорожек 0,5мм, диаметр отверстий 0,8-1мм.


Кстати, хорошие уроки по Eagle – easyelectronics.ru (уроки по Eagle)

 

Печатаем рисунок на глянцевой фотобумаге 85г/см и дальше при помощи технологии джедайского утюга или ЛУТ (описание ЛУТ на easyelectronics.ru) переносим тонер на предварительно зашкуренную и обезжиренную ацетоном медь и травим в хлорном железе.

На видео видно, что красный защитный диод загорается только в момент отключения цепи, когда ток через обмотку реле перестает течь. Почему же он горит?
Дело в том, что обмотка-катушка обладает свойством индуктивности, то есть сопротивляется изменению тока через себя. При попытке разомкнуть цепь и уменьшить ток до нуля, катушка некоторое время будет пытаться сохранить течение тока за счет запасенной в ней энергии магнитного поля. Катушку можно сравнить с маховиком, которого и разогнать и остановть мгновенно не получается.
Такая попытка сохранить ток в начальный момент в уже разомкнутой цепи приведет к возникновению очень большого напряжения на катушке (как на источнике ЭДС), что может вызвать разряд в воздушном зазоре ключа, пробой транзистора. Поэтому на этот случай надо предусматривать разрядную цепь с диодом.

IMG_20190422_234559

 

 

 

July 11, 2019 finereader No comments exist

Робот манипулятор. Собираем готовый проект

 

Программная часть готового проекта :

  • код для платы Arduino, получающей по COM-порту от компьютера пакет с тремя углами поворота серводвигателей
  • программа в среде Processing, работающая на ПК, которая сначала определяет координаты мишени (обрабатывая изображение с камеры), а затем рассчитывает углы для Arduino

 

 

Только для начала, было бы неплохо отработать всю систему без камеры, просто указывая координаты мышкой на экране. Для этого и предназначена вот эта программа для Processing. Работает с той же программой для Arduino.

July 11, 2019 finereader No comments exist

Робот манипулятор. Техническое зрение

В нашем проекте по созданию робота мы уже разработали кинематику движения, конструкцию звеньев и алгоритм движения.

Система управления робота, выполняющая наш алгоритм, рассчитает углы поворота звеньев если мы дадим ей координаты точки, в которую необходимо привести схват робота.

Робот будет охотиться за мишенями характерного цвета, помещенными в его рабочую зону.

Повесим веб-камеру над рабочей зоной, в Processing захватим видео с этой камеры.

Обработка в Processing будет представлять чтение изображения с камеры несколько раз в секунду, а затем выделения пикселей требуемого цвета. Найдя все подходящие пиксели, зная их координаты, определим “центр масс” всей фигуры – это и есть точка, целеуказание для робота.


/*Программа реализует захват изображения 
с камеры и определение объекта цветовым фильтром
в пространстве HSB (в нем намного удобнее сравнивать объекты,
чем в RGB-пространстве)
Выбор цвета для работы фильтра производится указанием
мыши на пиксель*/

import processing.video.*;

Capture cam;
   
   //Переменные для хранения выбранного цвета
   float huesel = 0.0; //Оттенок цвета
   float satsel = 0.0;
   float brsel = 0.0;
  
  //Переменные для расчета центра фигуры
  int ii=0; //счетчик найденных пикселей
  float sumX=0.0; //сумма Х-координат найденных пикселей выбранного цвета
  float sumY=0.0; //сумма Y-координат найденных пикселей выбранного цвета

int w=320; //640
int h=240; //480

void setup() {
  
  size(320, 240);
  //size(640, 480);
  String[] cameras = Capture.list();

  if (cameras == null) {
    println("Failed to retrieve the list of available cameras, will try the default...");
    cam = new Capture(this, w, h);
  } if (cameras.length == 0) {
    println("There are no cameras available for capture.");
    exit();
  } else {
    println("Available cameras:");
    printArray(cameras);
    
    //Интерфейс камеры с нужным разрешением(320х240)
   // cam = new Capture(this, cameras[9]);
    cam = new Capture(this, cameras[12]);
    cam.start();
  }
}

void draw() {
  //Захватываем изображение с камеры
  if (cam.available() == true) {
    cam.read();
  }
  
  //Выводим изображение в окно
  image(cam, 0, 0, width, height);
  
  //Пробегаем по всем пикселям изображения
  ii=0; sumX=0.0; sumY=0.0;
  for (int i=0;i<w;i+=1){
    for(int j=0; j<h;j+=1){
       //получаем цвет текущего пикселя
       color cc = get(i,j);
       //и переводим его в HSB-пространство
       float h = hue(cc);
       float s = saturation(cc);
       float br = brightness(cc);
       //Сравниваем тек. пиксель с выбранным с учетом 
       //некоторого допуска на отличие в цвете
        if (h<=(huesel+15) && h>=(huesel-15) &&
            s<=(satsel+15) && s>=(satsel-15) &&
            br<=(brsel+150) && br>=(brsel-150))
        {    
          //добавляем координату к общей сумме
          sumX+=i;
          sumY+=j;
          ii++;   
          //делаем найденный пиксель красным
          color cch = color(255,0,0);
          set(i,j,cch); //set(i,j-1,cch);set(i-1,j-1,cch);
        }

    }
  }
  
  //вычисляем центр фигуры из найденных пикселей
  float cX = sumX/ii;
  float cY = sumY/ii;
  
  //рисуем перекрестие
  strokeWeight(5);
  stroke(255,0,0);
  point(cX,cY);
    strokeWeight(1);
    line(cX,0,cX,height);
    line(0,cY,width,cY);
  
  //перевод координат в мм
  float cXmm = cX*167/320;
  float cYmm = cY*125/240;
  fill(230,0,0);
  text("X"+" "+cXmm,width-50,20);
  text("Y"+" "+cYmm,width-50,40);
  
  //Запись кадров
 //saveFrame("frames2/####.tiff");  
}

//обработчик указания мыши - захват цвета указанного пикселя
void mouseClicked(){
  color col = get(mouseX,mouseY);
  huesel = hue(col);
  satsel = saturation(col);
  brsel  = brightness(col);
}

 

Сама обработка захваченной картинки (функция draw() ) заключается в обходе всех пикселей в двойном цикле for. Внешний цикл по столбцам по координате Х экрана – перебираем все по счетчику i от нулевого до ширины экрана width.

Внутренний цикл по координате Y перебирает все пиксели данного столбца (выбранного во внешнем цикле) двигаясь по счетчику j от нулевой строки до height.

Получить цвет пикселя можно по его координатам на экране функцией get(i,j)

Сравнивая цвет текущего пикселя с заданным цветом, в блоке if решаем, принадлежит этот пиксель к выбранной мишени или нет. 

 

Функция mouseclicked() нужна для указания требуемого цвета.

Используемая кодировка цвета – HSB. В этой кодировке собственно оттенок цвета задан первым значением, по которому удобно сравнивать пиксели.

 

Центр фигуры определяется как отношение суммы координат точек к их числу.

 

В конце надо только не забыть рассчитать смещение координат. В Processing координаты отсчитываются от верхнего левого угла и координаты мишени для робота необходимо пересчитать, так как ноль координат в системе робота был в центре его основания.