Реализация протокола PELCO-D на Arduino

Когда-то давно передо мной стояла интересная задача. Необходимо было реализовать управление PTZ-камерой PTD-208z по протоколу PELCO-D причем сделать это надо было именно на Arduino. Коротко зачем все это нужно: камера крепится на специального робота, который катается внутри большой трубы и рассматривает дефекты, пишется все видео на DVR и плюс есть оператор, который управляет роботом (управление роботом, механика и драйверы двигателей тоже были сделаны, но это тема для другой статьи). С этого проекта, который я закончил буквально на днях и началось мое знакомство с Arduino.

Небольшое описание самого протокола и зачем он нужен.

Pelco-D очень популярный PTZ (Pan/Tilt/Zoom) протокол? используемый в CCTV индустрии. Собственно он используется для управления поворотными камерами, т.е. поворотов и зуммирования. В качестве среды передачи для протокола наиболее часто выступает 485-й интерфейс, т.к. в основном его поддерживают все производимые аналоговые камеры.

Формат Pelco-D посылки выглядит следующим образом:

Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7
Sync Camera Address Command 1 Command 2 Data 1 Data 2 Checksum
  • Byte 1 (Sync) — байт синхронизации, по умолчанию имеет значение 0xFF, не изменяем;
  • Byte 2 (Camera Address) — логический адрес управляемой камеры (например камера 1 имеет адрес 0x01);
  • Byte 3 и 4 (Command 1 и 2) — описаны ниже;
  • Byte 5 (Data 1) — байт задает скорость поворота камеры влево/вправо, 0x00 — скорость равна 0, 0x3F — максимально возможная скорость поворота. Некоторые камеры поддерживают «турбо» скорость 0xFF;
  • Byte 6 (Data 2) — байт задает скорость движения камеры вниз/вверх, со скоростями все аналогично байту 5;
  • Byte 7 (Checksum) — байт контрольной суммы, байт синхронизации не учитывается в контрольной сумме.

Разберем байты 3 и 4 (Command 1 и 2)

Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
Command 1 Sense Reserved Reserved Auto / Manual Scan Camera On/Off Iris Close Iris Open Focus Near
Command 2 Focus Far Zoom Wide Zoom Tele Tilt Down Tilt Up Pan Left Pan Right Fixed to 0

НАПРИМЕР:
Pan Left (поворот налево) — 0b00000100 или 0x04 в шестнадцатиричном представлении записываем в байт 4 (Command 2).

Так с протоколом разобрались, едем дальше.

Как же все это сделано на Arduino.

Для начала мы определились с органом управления, он достался нам от другой аналогичной машинки и управляет не только камерой, но и движением робота, а также светом:

Пульт управления роботом Arduino

Джойстики на пульте управления подключены к плате Arduino UNO к аналоговым входам от A0 до A2. Далее для передачи посылки камере реализовано программное последовательное соединение с помощью библиотеки SoftwareSerial.h, это единственная библиотека, которая используется в этом проекте. Использование ее обусловлено тем, что необходимо иметь связь с ПК и одновременно передавать данные камере.

Для реализации интерфейса RS-485 использовалась микросхема SP485EE. Накидаю небольшую схемку ее расключения:

SP485EE

SP485EE схема подключения

Далее приведу листинг программы:

/*Программа для управления камерой*/
#include <SoftwareSerial.h>

int sensorPan = A0;     // канал вправо/влево 
int sensorTilt = A1;    // канал вверх/вниз 
int sensorFocus = A2;   // канал фокус

int xVal = 0;           // переменная для хранения значения с потенциометра вправо/влево
int yVal = 0;           // переменная для хранения значения с потенциометра вниз/вверх
int focus = 0;          // переменная для хранения значения с потенциометра фокуса камеры

const byte tiltDown = 0x10;
const byte tiltUp = 0x08;
const byte panRight = 0x02;
const byte panLeft = 0x04;
const byte focusNull = 0x00;  // Зума нет
const byte focusNear = 0x20;  // Зум +  0x01
const byte focusFar = 0x40;   // Зум -  0x80

SoftwareSerial newSerial = SoftwareSerial(7, 8);

void setup()
{
   pinMode(7, INPUT);
   pinMode(8, OUTPUT);
   newSerial.begin(2400); // включаем uart 
}

// отправка данных камере
// структура посылки Pelco-D
/*

|  byte 1  |   byte 2   |  byte 3  |  byte 4  | byte 5 | byte 6 | byte 7 |
 ---------- ------------ ---------- ---------- -------- -------- --------
|Sync 0xFF | Cam Adress | Command1 | Command2 | Data 1 | Data 2 |Checksum|

*/

void sendData(byte camNum, byte command_1, byte command_2, byte panSpeed, byte tiltSpeed) 
{
   int modSum = 0;                 // начальное нулевое значение контрольной суммы 
   byte dataVal[6] = {0xFF, camNum, command_1, command_2, panSpeed, tiltSpeed}; // вектор формата комманды Pelco-D
   for (int i=0; i<6; i++)         // цикл отправки посылки камере
   {
      newSerial.write(dataVal[i]);  // отправить байт в UART
      if (i > 0)                   // контрольная сумма начинает считаться со второго байта, поэтому первый 0xFF пропускаем
         modSum += dataVal[i];     // суммируем байты контрольной суммы
   }
   modSum %= 100;                  // контрольная сумма делиться по модуля на 256 в dec или по другому на 100 в hex
   newSerial.write(modSum);         // отправить контрольную сумму
}

void halt()
{
   byte dataVal[7] = {0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01};
   for (int i=0; i<7; i++)
   {
      newSerial.write(dataVal[i]);  // отправить байт в UART   
   } 
}

void loop() 
{
   xVal = analogRead(sensorPan);     // считывание значения потенциометра вправо/влево в соответствующую переменную
   yVal = analogRead(sensorTilt);    // считывание значения потенциометра вниз/вверх в соответствующую переменную
   focus = analogRead(sensorFocus);  // считывание значения потенциометра фокуса камеры в соответствующую переменную
   
   if (xVal > 600)  
   { 
      sendData(0x01, focusNull, panRight, 0x3F, 0x00);      // вызывается функция отправки байта
      while (xVal > 600) 
      {
         xVal = analogRead(sensorPan);   
      }
   }
   if (xVal < 450)
   {
      sendData(0x01, focusNull, panLeft, 0x3F, 0x00);      // вызывается функция отправки байта  
      while (xVal < 450) 
      {
         xVal = analogRead(sensorPan);   
      }
   }
 
   if (yVal > 600)
   {
      sendData(0x01, focusNull, tiltUp, 0x00, 0x3F);      // вызывается функция отправки байта
      while (yVal > 600) 
      {
         yVal = analogRead(sensorTilt);   
      }
   } 
   if (yVal < 450)
   {
      sendData(0x01, focusNull, tiltDown, 0x00, 0x3F);      // вызывается функция отправки байта  
      while (yVal < 450) 
      {
         yVal = analogRead(sensorTilt);   
      }
   }  
   
   if (focus > 600)
   {
      sendData(0x01, focusNull, focusNear, 0x00, 0x00);      // вызывается функция отправки байта
      while (focus > 600) 
      {
         focus = analogRead(sensorFocus);   
      }
   } 
   if (focus < 450)
   {
      sendData(0x01, focusNull, focusFar, 0x00, 0x00);      // вызывается функция отправки байта  
      while (focus < 450) 
      {
         focus = analogRead(sensorFocus);   
      }
   } 
   halt();
}

Программа максимально проста и не вызовет затруднений даже у новичков. В функции loop() происходит опрос состояний джойстиков, а также вызов функции sendData() с аргументами вида байтов протокола. Функция sendData() посылает посылку камере на лету подсчитывая контрольную сумму. Функция halt() срабатывает когда нету реакции ни одного из джойстиков. Вот и все.

Испытания системы управления камерой прошли успешно) Полностью робот в сборе уже бегает по трубам и снимает камерой все дефекты)))

21 Ответ в “Реализация протокола PELCO-D на Arduino

  1. Очень интересно. Пока что нихрена не понятно, но интересно)) Хочу тоже разобраться с похожим проектом. Нужно сделать ptz камеру на базе arduino. По всей видимости, это будет мой курсач). Спасибо за информацию. Будемс разбираться.

  2. у меня не заработало. подключил ардуинку через max к камере, джостики подключил.
    вроде переключил на самой камере все переключатели. камера на как не реагирует на мои команды. может кто подскажет что делать? в какое положение переключатели на камере поставить? может я что напортачил?

    1. Привет, для начала вопрос, что за камера? О каких переключателях речь?

      1. ACUMEN ai-sd27 купольная поворотная pzt камера

      2. Не могу по ней найти никакой технической информации, снята с производства и сайт производителя молчит.

      3. на микросхему SP485EE точно 5 вольт на 2 и 3 ножку?

  3. Точно, это высокий уровень для разрешения на передачу данных через интерфейс RS485.

  4. Адрес установлен 0x01? Совпадает с адресом в программе? 4-секционный переключатель должен быть в положении off off on off для режима Pelco-D со скоростью 2400 бит/с.

  5. Здравствуйте, я немного не понял, Вы вот используете библиотеку SoftwareSerial.h для того, чтобы одновременно передавать сигнал и видеокамере и получать сигнал с пк. С передачей сигнала камере, вроде, понятно, а вот с пк не очень. 8 пин Вы в программе определили как передатчик и сделали его выходом, затем этот выход пошел на микросхему для преобразования UART в RS-485. Затем этот сигнал пошел к камере и это все позволит нам ей управлять. 7 пин у Вас сделан приемником, но он нигде не используется. Так и надо? И как эту систему можно подружить с ПК? И вообще, нужна ли была библиотека, которая дублирует UART на цифровые пины, если UART есть на 0 и 1 пине, а мы используем только два пина? (7 и 8).. Предостерегу себя от фейспалмов и скажу, что я полный новичек в этом всем, и если то, что я написал — полнейший бред, то не серчайте. Просто мне нужно в этом разобраться. Спасибо)

    1. Привет, согласен, в статье не совсем очевидно, что и как работает. SoftwareSerial используется для связи с камерой, 8 пин для передачи команд камере, 7 пин по идее для приема, но от камеры мы ничего не получаем, в данном случае протокол однонаправленный, поэтому не используется, но класс SoftwareSerial требует инициализации обоих пинов. Аппаратный Serial интерфейс может использоваться для отладки в данном случае и он так и использовался, потом из кода отладочные строки были убраны мною, а по идее компьютер здесь особо не нужен и можно не заводить SoftwareSerial, а использовать Serial класс для передачи команд. Код старый и я уже не помню всех тонкостей почему именно используется у меня SoftwareSerial. Удачи, если будут вопросы пишите, постараюсь помочь.

  6. ЗДРАВСТВУЙТЕ, достал схему SP485EEN все подключил как вы, скопировал прогу, в установках камеры задал протокол Pelco-D, скорость 2400, данные 8 бит, четности нет, стоп бит -1, но не работает ;/ (камера Wisenet SNZ6320)

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

  7. Добрый день, чтобы отправлять команды на предпозиции, а не просто ездить по сторонам с помощью джойстика, из описания протокола так и не понял какие команды надо передавать в камеру.

    1. Добрый день. Чтобы формировать предпозиции используются, как мне кажется, те же самые команды, только в момент инициализации необходимо выбрать точку начального отсчета координат для движения камеры и уже от этой точки формировать пресеты путем передачи команд для сдвига на соответствующее количество шагов от начала координат.

      1. как я понял предустановки хранятся в камере, а устройство просто посылает номер и все. Из описания протокола расширенные команды (https://umnyjdomik.ru/wp-content/uploads/PELCO-D-Version-2-Revision-1-2003.pdf страница 7 извиняюсь за сторонние ссылки) посылка должна выглядеть так 0xFF, 0x01, 0x00, 0x07, 0x00, 0x01, 0x09 — переход на первую предпозицию поправьте если я что-то не так понял.

  8. Судя по коду, управление не предполагает одновременного перемещения по X,Y и управления фокусировкой.
    Что ж так упрощенно? Все камеры и пульты это позволяют, хватило бы рук.

  9. А не подскажите что означает в скетче:
    #elif defined(CAMERA_MODEL_AI_THINKER)
    #define PWDN_GPIO_NUM 32
    #define RESET_GPIO_NUM -1
    #define XCLK_GPIO_NUM 0
    #define SIOD_GPIO_NUM 26
    #define SIOC_GPIO_NUM 27

    #define Y9_GPIO_NUM 35
    #define Y8_GPIO_NUM 34
    #define Y7_GPIO_NUM 39
    #define Y6_GPIO_NUM 36
    #define Y5_GPIO_NUM 21
    #define Y4_GPIO_NUM 19
    #define Y3_GPIO_NUM 18
    #define Y2_GPIO_NUM 5
    #define VSYNC_GPIO_NUM 25
    #define HREF_GPIO_NUM 23
    #define PCLK_GPIO_NUM 22
    #else
    #error «Camera model not selected»
    #endif

    1. #elif директива условной компиляции. Если у вас где то прописан define CAMERA_MODEL_AI_THINKER, то код между #elif и #else в вашем случае компилируется. Конкретно у вас прописаны определения для GPIO, название и соответствующий номер.

Комментарии отключены.