Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Контроллер поворота антенны, совместимый с программным обеспечением для отслеживания

Компоненты и расходные материалы

Arduino UNO
Плата Arduino Uno
× 1
Поворотный потенциометр (общий)
макс. 1 кОм (лучше 500 Ом)
× 2
Поворотный энкодер с кнопкой
× 2
Макет (общий)
× 1
Модуль реле (общий)
2 модуля x 2 реле NO-Com-NC
× 2
N-канал Power MOSFET
модуль питания mosfet (минимум 12 В / 3 А)
× 2

Приложения и онлайн-сервисы

IDE Arduino

Об этом проекте

Последнее обновление ноябрь 2021

Этот проект начинался как развлечение и превратился в серьезное оборудование.

Контроллер допускает ручное позиционирование антенны с помощью двух поворотных энкодеров, азимута и возвышения. Он может автоматически отслеживать спутники при подключении через USB к ПК, на котором запущено программное обеспечение для отслеживания спутников.

Он совместим со всем программным обеспечением для отслеживания, использующим протокол EasyComm2 / 9600 бод. PstRotator, WXtrack, HRD, MacDoppler ... Даже WXtoIMG может управлять ротатором.

Он работает напрямую с Orbitron, с подключаемым модулем DDE из http://tripsintech.com/orbitron-dde-azimuth-elevation-to-serial/

Контроллер выдает ответ по последовательному каналу, чтобы программа слежения отображала реальное положение антенны на экране. Пока это делал только PstRotator.

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

Здесь вы найдете две версии:одну для двигателей постоянного тока и одну для двигателей переменного тока (только реле). Последний может быть подключен к существующим коммерческим ротаторам антенн.

Версия с двигателями постоянного тока имеет преимущество использования ШИМ для более мягкого / плавного движения антенны. Он выводит мощность, пропорциональную угловой ошибке (Цель <-> Антенна). Поэтому, когда антенна начинает двигаться, она постепенно ускоряется, а при приближении к желаемому положению замедляется до полной остановки. Это называется плавный пуск / плавный останов . . Есть регулируемая мертвая зона, где антенна не двигается при малейшем смещении цели.

У меня бета версия с Soft-Start / Soft-Stop для двигателей переменного тока, используя преимущества этого модуля AC-Dimmer, но сейчас он работает только для азимута. Если вы хотите попробовать, дайте мне знать по электронной почте.

Если у вас 180град. система высот, вы в порядке, дайте мне электронное письмо. Также есть версия с 0,1 градусом. точность, но я бы не рекомендовал его, если у вас нет чертовски надежного показания потенциометра и параноидальной конструкции контроллера. Вы найдете другие версии на моей веб-странице.

После завершения строительства необходимо применить процедуры калибровки . .

  • Калибровка потенциометра является обязательным и обеспечивает правильное чтение 0-359deg. / 0-90 градусов, независимо от того, какой потенциометр вы используете.
  • калибровка двигателя предназначен только для настройки плавного пуска-останова особенность. Это необходимо, если вам не нравятся настройки по умолчанию.

Более подробные объяснения в видео. Поскольку код со временем был улучшен, и видео больше не могут быть обновлены, проверьте мою веб-страницу для получения последней информации и личного опыта работы с этим контроллером. https://racov.ro/index.php/2020/12/09/arduino-based-antenna-rotator-part3-software-tracking-update/

Напишите мне по электронной почте, если хотите узнать больше, потому что эта платформа не сообщает мне о новых комментариях, не знаю почему. Я постараюсь как можно лучше решить небольшие проблемы. [email protected]

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

Код

  • ant-rot-DC-nov2021
  • ant-rot-AC-aug2021
  • Процедура калибровки потенциометра
  • Процедура калибровки двигателя
ant-rot-DC-nov2021 Arduino
Этот код предназначен для двигателей постоянного тока с выходом ШИМ плавного пуска и останова
 / * Контроллер поворота антенны AZ / EL для Arduino - двигатели постоянного тока * ========================================================* Использует протокол EasyComm для компьютера - Программное обеспечение для отслеживания * Ручная команда средства двух энкодеров AZ - EL * * Viorel Racoviteannu * https://www.youtube.com/channel/UCiRLZX0bV9rS04BGAyUf-fA * https://racov.ro * [email protected] * * Я не несу ответственности за неправильное использование этого кода * или любой вид ущерба, который может возникнуть в результате использования этого кода. * * декабрь 2020 г., версия 2 - повышенная стабильность последовательной связи * январь 2021 г. - улучшена мертвая зона вблизи цели, при которой антенна не двигается * апр 2021 г. - повышена стабильность последовательной связи * июнь 2021 г. - мощность, пропорциональная ошибкам для отслеживания движения. Real Soft-Stop * август 2021 года - более быстрое обновление USB, холодное переключение направления Az / El, небольшие оптимизации в коде * ноябрь 2021 года - раскрыли секрет Soft-Start. Это было несложно. Вот и все. на ЖК-дисплей через I2C, адрес по умолчанию 0x27 (A0-A2 без перемычек) LiquidCrystal_I2C lcd (0x27, 16, 2); // адрес, символы, строки. // объявление пользовательского символа для байта стрелки вверх / вниз DownArrow [8] ={B00000, B00100, B00100, B00100, B10101, B01110, B00100, B00000}; byte UpArrow [8] ={B00000, B00100, B01110, B10101, B00100, B00100, B00100, B00000}; / ************************* ********** ЭТО ГДЕ ВЫ ДЕЙСТВИТЕЛЬНО КРУТИТЕ ДВИЖЕНИЕ АНТЕННЫ *************** /// КАЛИБРОВКА потенциометров АНТЕННЫ int AzMin =1; // начало потенциометра int AzMax =1023; // конец потенциометра int ElMin =1; int ElMax =1023; // Допустимая ошибка, при которой антенна не двигается int AzErr =8; int ElErr =4; // Угловая разница в месте начала плавной остановки int Amax =25; // азимут int Emax =15; // высота // минимальная и максимальная мощность двигателей в процентах; int PwAzMin =30; // минимальная мощность, при которой двигатель не глохнет и запускается под нагрузкой int PwAzMax =100; // полная мощность для максимальной скорости int PwElMin =30; int PwElMax =100; int PwAz =0; // расчетная мощность, передаваемая на двигатель (в процентах); int PwEl =0; / ******************************************* ************************************************* ***** /// Переменные кодировщика enum AzPinAssignments {AzEncoderPinA =2, // Az-кодировщик справа AzEncoderPinB =3, // кодировщик слева AzClearButton =4, // кодировщик push ElEncoderPinA =6, // ElEncoderPinB =5 }; // кодировщик слева // процедура обслуживания прерывания vars unsigned int lastReportedPos =1; // управление изменениями static boolean rotating =false; // управление противодействием логическому A_set =false; логическое B_set =false; int aState; int aLastState; // другие переменные int AzPotPin =A0; // выбираем входной контакт для азима. потенциометр int AzRotPin =12; // выбираем вывод выхода для направления вращения int AzPWMPin =11; // выбор вывода для азимутальной команды ШИМ int TruAzim =0; // вычисляем реальное значение азимута int ComAzim =0; // заданное значение азимута int OldTruAzim =0; // сохранить предыдущее значение азимута int OldComAzim =0; char AzDir; // символ для отображения азимутального поворота int AzEncBut =1; // переменная для переключения с помощью кнопки энкодера int ElPotPin =A1; // выбираем входной контакт для подъема. потенциометр int ElRotPin =13; // выбираем вывод для направления вращения по высоте int ElPWMPin =10; // выбираем вывод для поворота по высоте PWM command int TruElev =0; // вычисляем реальное значение высоты int ComElev =0; // заданное значение высоты int OldTruElev =0; // сохранить предыдущее значение высоты int OldComElev =0; char ElDir; // символ отм. rot display // флаги для допусков AZ, EL bool AzStop =false; bool ElStop =false; int ElUp =1; // 1 - Высота Dn, 0 - Elevation STOP, 2 - Высота вверх int StaAzim =0; // Начальный азимутальный угол для мягкого запуска двигателя int PwAzStop =0; // рассчитанный ШИМ (процент) для плавного останова int PwAzStar =0; // рассчитанный ШИМ (процент) для мягкого старта int StaElev =0; // Начальный угол возвышения для мягкого пуска двигателя int PwElStop =0; // рассчитанный ШИМ (процент) для плавного останова int PwElStar =0; // вычисленный ШИМ (процент) для мягкого старта // цикла усреднения const int numReadings =25; int readIndex =0; // индекс текущего чтения int azimuth [numReadings]; // показания аналогового входа int elevation [numReadings]; int totalAz =0; // промежуточный итог int totalEl =0; // переменные для последовательной связи String Azimuth =""; Высота строки =""; String ComputerRead; String ComputerWrite; bool AZser =false; bool ELser =false; bool ANTser =false; / *************** КОНЕЧНОЕ ОПИСАНИЕ ПЕРЕМЕННОЙ ************ / void setup () {Serial.begin (9600); Serial.setTimeout (50); // миллисекунды ждать USB sata. По умолчанию 1000 // Запуск ЖК-дисплея:// lcd.begin (16,2); // выбираем этот, если стрелки отображаются некорректно lcd.init (); lcd.backlight (); // записываем отображаемое имя и версию lcd.setCursor (0, 0); // Устанавливаем курсор в первой строке первого столбца (отсчет начинается с 0!) Lcd.print ("EasyCom AntRotor"); // отображение "..." lcd.setCursor (0, 1); // Устанавливаем курсор на первый столбец второй строки lcd.print ("* Racov * Nov.2021"); // создаем собственный символ для стрелки вверх / вниз lcd.createChar (1, DownArrow); lcd.createChar (2, UpArrow); // объявление вывода pinMode (AzRotPin, OUTPUT); // объявление азим. направление вращения Pin как ВЫХОД. pinMode (AzPWMPin, ВЫХОД); // объявление азимутального вывода команды PWM как OUTPUT pinMode (ElRotPin, OUTPUT); // объявление высоты. направление вращения Pin как ВЫХОД pinMode (ElPWMPin, OUTPUT); pinMode (AzPotPin, ВХОД); pinMode (ElPotPin, ВХОД); pinMode (AzEncoderPinA, ВХОД); pinMode (AzEncoderPinB, ВХОД); pinMode (AzClearButton, ВХОД); pinMode (ElEncoderPinA, ВХОД); pinMode (ElEncoderPinB, INPUT); // Вывод AzEncoder при прерывании 0 (вывод A) attachInterrupt (0, doEncoderA, CHANGE); // Вывод AzEncoder при прерывании 1 (вывод B) attachInterrupt (1, doEncoderB, CHANGE); // Считывает начальное состояние ElEncoderPinA aLastState =digitalRead (ElEncoderPinA); / * инициализация цикла усреднения * / TruAzim =(map (analogRead (AzPotPin), AzMin, AzMax, 0, 359)); // значение азимута 0-359 if (TruAzim <0) {TruAzim =0;} if (TruAzim> 359) {TruAzim =359;} // сохраняем значения в пределах TruElev =(map (analogRead (ElPotPin), ElMin, ElMax , 0, 90)); // значение высоты 0-90 if (TruElev <0) {TruElev =0;} if (TruElev> 90) {TruElev =90;} // сохраняем значения в пределах для (int thisReading =0; thisReading  359) {TruAzim =359;} if (TruElev <0) {TruElev =0;} if (TruElev> 90) {TruElev =90;} // переход к следующей позиции в массиве:readIndex =readIndex + 1; // если мы находимся в конце массива, переходим к началу:if (readIndex> =numReadings) {readIndex =0;} // это для чтения команды из кодировщика ReadAzimEncoder (); ReadElevEncoder (); if (Serial.available ()) {SerComm ();} // считываем данные USB // обновляем отображение положения антенны, только если значение изменилось if ((millis ()% 500) <10) {// не мерцание дисплея, если (OldTruAzim! =TruAzim) {DisplAzim (TruAzim, 4,0); OldTruAzim =TruAzim; } if (OldTruElev! =TruElev) {DisplElev (TruElev, 5,1); OldTruElev =TruElev; }} // обновляем отображение целевой позиции только при изменении значения if (OldComAzim! =ComAzim) {DisplAzim (ComAzim, 12,0); OldComAzim =ComAzim; } если (OldComElev! =ComElev) {DisplElev (ComElev, 13,1); OldComElev =ComElev; } // это для поворота по азимуту if (TruAzim ==ComAzim) {// если равно, прекратить движение AzStop =true; analogWrite (AzPWMPin, 0); // Мощность двигателя по азимуту =0 StaAzim =TruAzim; // это будет начальный азимут для мягкого старта lcd.setCursor (8, 0); lcd.print ("="); } else if ((abs (TruAzim - ComAzim) <=AzErr) &&(AzStop ==false)) {// если в пределах допуска, но он не был равен, повернуть AzimRotate ();} else if (abs (TruAzim - ComAzim)> AzErr) {// если цель вне допуска AzStop =false; // не равно AzimRotate (); // вращение} // это вращение по высоте if (TruElev ==ComElev) {// если равно, остановка движения ElStop =true; analogWrite (ElPWMPin, 0); // Мощность электродвигателя =0 StaElev =TruElev; // это будет начальная отметка для мягкого старта lcd.setCursor (8, 1); lcd.print ("="); ElUp =0; // флаг для превышения STOP} else if ((abs (TruElev - ComElev) <=ElErr) &&(ElStop ==false)) {// если в пределах допуска, но он не был равен, повернуть ElevRotate ();} else if (abs (TruElev - ComElev)> ElErr) {// если цель за пределами допуска ElStop =false; // не равно ElevRotate (); // поворот} // это интерпретация умножения x10 кодировщика Az while (AzEncBut ==10) {// при переключении на x10 analogWrite (AzPWMPin, 0); // ОСТАНОВИТЬ вращение антенны StaAzim =TruAzim; // это будет начальный азимут для мягкого запуска analogWrite (ElPWMPin, 0); lcd.setCursor (8, 0); lcd.print ("*"); ReadAzimEncoder (); if (OldComAzim! =ComAzim) {// обновлять отображение только при изменении чисел DisplAzim (ComAzim, 12, 0); OldComAzim =ComAzim; } задержка (100); }} // конец основного цикла // ____________________________________________________ // ___________ определения процедур __________________void DisplAzim (int x, int y, int z) {char displayString [7] =""; sprintf (displayString, "% 03d", x); // выводит число фиксированной длины (3 целых) lcd.setCursor (y, z); // чтобы не было нулей в начале «__7», используйте «% 3d» lcd.print (displayString); // ************** ДЛЯ ЦЕЛЕЙ КАЛИБРОВКИ ************** // Serial.print ("Az"); // Serial.println ( analogRead (AzPotPin));} void DisplElev (int x, int y, int z) {char displayString [7] =""; sprintf (displayString, "% 02d", x); // выводит число фиксированной длины (2 целых числа) lcd.setCursor (y, z); // без начальных нулей «_7» используйте «% 2d» lcd.print (displayString); // ************** ДЛЯ ЦЕЛЕЙ КАЛИБРОВКИ ********** **** // Serial.print ("El"); // Serial.println (analogRead (ElPotPin));} void ReadElevEncoder () {aState =digitalRead (ElEncoderPinA); // Считывает "текущее" состояние ElEncoderPinA // Если предыдущее и текущее состояние ElEncoderPinA различаются, это означает, что произошел импульс if (aState! =ALastState) {// Если состояние ElEncoderPinB отличается от состояния ElEncoderPinB Состояние ElEncoderPinA, это означает, что энкодер вращается по часовой стрелке if (digitalRead (ElEncoderPinB)! =AState) {ComElev ++;} else {ComElev -;} if (ComElev <0) {ComElev =0;} if (ComElev> 90 ) {ComElev =90;}} aLastState =aState; // Обновляет предыдущее состояние ElEncoderPinA текущим состоянием} void ReadAzimEncoder () {rotating =true; // сбросить средство защиты от ошибок if (lastReportedPos! =ComAzim) {lastReportedPos =ComAzim; } задержка (10); if (digitalRead (AzClearButton) ==LOW) {// если переключатель кодировщика нажат, задержка (250); // переключатель противодействия if (AzEncBut ==1) {AzEncBut =10; ComAzim =int (ComAzim / 10) * 10; // ComAzim в 10 град. шаги} else {AzEncBut =1; }}} // конец ReadAzimEncoder () // Прерывание при изменении состояния. // подождем немного, пока не закончится подпрыгивание // Тестируем переход, действительно ли что-то изменилось? if (digitalRead (AzEncoderPinA)! =A_set) {// еще раз противодействовать A_set =! A_set; // настраиваем счетчик +, если A опережает B if (A_set &&! B_set) ComAzim + =AzEncBut; ComAzim =((ComAzim + 360)% 360); // encoderPos от 0 до 359 град. вращение =ложь; // больше никаких дребезгов до тех пор, пока loop () не сработает снова}} // Прерывание при изменении состояния B, то же самое, что и A выше, избегайте doEncoderB () {if (rotating) delay (1); если (digitalRead (AzEncoderPinB)! =B_set) {B_set =! B_set; // настраиваем счетчик - 1, если B опережает A if (B_set &&! A_set) ComAzim - =AzEncBut; ComAzim =((ComAzim + 360)% 360); // encoderPos от 0 до 359 град. вращение =ложь; }} void AzimRotate () {if (ComAzim> TruAzim) {// это для определения направления вращения // холодное переключение - остановка двигателя перед изменением направления - для защиты механических и электрических частей if (AzDir ==char (127)) { // если ранее вращался в противоположном направлении analogWrite (AzPWMPin, 0); // ОСТАНОВИТЕ двигатель StaAzim =TruAzim; // это будет начальный азимут для задержки мягкого старта (200); // задержка перед переключением digitalWrite (AzRotPin, LOW); // деактивировать штифт вращения - задержка поворота вправо (200); // задержка после переключения} else {// то же направление, без остановки, без задержки digitalWrite (AzRotPin, LOW); // деактивировать штифт вращения - повернуть вправо} AzDir =char (126); // "->"} else {if (AzDir ==char (126)) {// если ранее вращался в противоположном направлении analogWrite (AzPWMPin, 0); // ОСТАНОВИТЕ двигатель StaAzim =TruAzim; // это будет начальный азимут для задержки мягкого старта (200); // задержка перед переключением digitalWrite (AzRotPin, HIGH); // активируем штифт вращения - задержка поворота влево (200); // задержка после переключения} else {// то же направление, без остановки, без задержки digitalWrite (AzRotPin, HIGH); // активируем штифт вращения - вращаем влево} AzDir =char (127); // "<-"} lcd.setCursor (8, 0); lcd.print (Строка (AzDir)); // это активирует азимутальный вывод ШИМ пропорционально угловой ошибке (рассчитывается в процентах%) PwAzStop =PwAzMin + round ((abs (ComAzim-TruAzim)) * (PwAzMax-PwAzMin) / Amax); // формула, которая выводит мощность, пропорциональную разнице углов для Soft-Stop PwAzStar =PwAzMin + round ((abs (StaAzim-TruAzim)) * (PwAzMax-PwAzMin) / Amax); // формула, которая выводит мощность, пропорциональную разнице углов для плавного пуска if (PwAzStar> PwAzStop) {PwAz =PwAzStop; // выбираем наименьшее значение} else {PwAz =PwAzStar;} if (PwAz> PwAzMax) {PwAz =PwAzMax;} analogWrite (AzPWMPin, round (2.55 * PwAz)); // активируем вывод PWM привода Azim} // end AzimRotate () void ElevRotate () {// это для определения направления вращения if (ComElev> TruElev) {if (ElUp ==1) {// если ранее вращался в противоположном направлении направление analogWrite (ElPWMPin, 0); // ОСТАНОВИТЬ двигатель StaElev =TruElev; // это будет начальная отметка для задержки плавного пуска (200); // задержка перед переключением digitalWrite (ElRotPin, LOW); // деактивировать штифт вращения - задержка поворота ВВЕРХ (200); // задержка после переключения} else {// то же направление, без остановки, без задержки digitalWrite (ElRotPin, LOW); // деактивировать штифт вращения - повернуть ВВЕРХ} lcd.setCursor (8, 1); lcd.write (2); // стрелка вверх ElUp =2; // флаг подъема ВВЕРХ} else {if (ElUp ==2) {// если ранее вращался в противоположном направлении analogWrite (ElPWMPin, 0); // ОСТАНОВИТЬ двигатель StaElev =TruElev; // это будет начальная отметка для задержки плавного пуска (200); // задержка перед переключением digitalWrite (ElRotPin, HIGH); // деактивировать штифт вращения - задержка поворота ВВЕРХ (200); // задержка после переключения} else {// то же направление, без остановки, без задержки digitalWrite (ElRotPin, HIGH); // деактивировать штифт вращения - повернуть ВВЕРХ} lcd.setCursor (8, 1); lcd.write (1); // стрелка вниз ElUp =1; // флаг для DN высоты} // это активирует азимутальный вывод ШИМ пропорционально угловой ошибке (рассчитывается в процентах%) PwElStop =PwElMin + round ((abs (ComElev-TruElev)) * (PwElMax-PwElMin) / Emax); // формула, которая выводит мощность, пропорциональную разности углов для Soft-Stop PwElStar =PwElMin + round ((abs (StaElev-TruElev)) * (PwElMax-PwElMin) / Emax); // формула, которая выводит мощность, пропорциональную разнице углов для плавного пуска if (PwElStar> PwElStop) {PwEl =PwElStop; // выбираем наименьшее значение} else {PwEl =PwElStar;} if (PwEl> PwElMax) {PwEl =PwElMax;} analogWrite (ElPWMPin, round (2.55 * PwEl)); // активируем вывод PWM привода Elev} // end ElevRotate () void SerComm () {// инициализируем показания ComputerRead =""; Азимут =""; Высота =""; в то время как (Serial.available ()) {ComputerRead =Serial.readString (); // считываем входящие данные как строку // Serial.println (ComputerRead); // повторить прием для целей тестирования} // ищем команду  for (int i =0; i <=ComputerRead.length (); i ++) {if ((ComputerRead.charAt (i) ==' A ') &&(ComputerRead.charAt (i + 1) ==' Z ')) {// если читается AZ for (int j =i + 2; j <=ComputerRead.length (); j ++) {if (isDigit (ComputerRead.charAt (j))) {// если символ - число Azimuth =Azimuth + ComputerRead.charAt (j); } else {break;}}}} // ищем команду  для (int i =0; i <=(ComputerRead.length () - 2); i ++) {if ((ComputerRead.charAt (i ) =='E') &&(ComputerRead.charAt (i + 1) =='L')) {// если читается EL if ((ComputerRead.charAt (i + 2)) =='-') {ComElev =0; // если отметка отрицательная break; } for (int j =i + 2; j <=ComputerRead.length (); j ++) {if (isDigit (ComputerRead.charAt (j))) {// если это число Elevation =Elevation + ComputerRead.charAt ( j); } else {break;}}}} // если получено  if (Azimuth! ="") {ComAzim =Azimuth.toInt (); ComAzim =ComAzim% 360; // сохранение значений в пределах (для трекеров с поворотом более 360 градусов)} // если получено  if (Elevation! ="") {ComElev =Elevation.toInt (); if (ComElev> 180) {ComElev =0;} if (ComElev> 90) {// если получено более 90град. (для трекеров с углом наклона 180 градусов) ComElev =180-ComElev; // держать ниже 90 град. ComAzim =(ComAzim + 180)% 360; // и вращаем антенну сзади}} // ищем запрос  для положения антенны для (int i =0; i <=(ComputerRead.length () - 4); i ++) {if ((ComputerRead .charAt (i) =='A') &&(ComputerRead.charAt (i + 1) =='Z') &&(ComputerRead.charAt (i + 3) =='E') &&(ComputerRead.charAt (i +4) =='L')) {// отправляем обратно положение антенны <+ xxx.x xx.x> ComputerWrite ="+" + String (TruAzim) + ". 0" + String (TruElev) + ". 0 "; Serial.println (ComputerWrite); }}} // завершаем SerComm () 
ant-rot-AC-aug2021 Arduino
Убедитесь, что вы используете электрическую схему для двигателей переменного тока.
Предлагает сухие контакты (ВКЛ / ВЫКЛ). Его можно легко подключить к коммерческим ротаторам.
 / * Контроллер ротатора антенны AZ / EL для Arduino - двигатели переменного тока * ========================================================* Использует протокол EasyComm для компьютера - Программное обеспечение слежения * Ручное управление с помощью двух поворотных энкодеров AZ - EL * * совместим с вращателями распределительной коробки * или двигателями переменного тока * сухие контакты для левого-правого, верхнего-нижнего * * Viorel Racoviteannu / * https://www.youtube.com/channel/UCiRLZX0bV9rS04BGAyUf-fA * https://racov.ro * [email protected] * * Я не несу ответственности за неправильное использование этого кода * или любой ущерб, который может возникнуть в результате использования этого кода. * * декабрь 2020 v2 - улучшенная стабильность последовательной связи * январь 2021 - фиксированные допуски AZ, EL для активации двигателя * апрель 2021 - улучшенная стабильность последовательной связи * август 2021 - более быстрое обновление USB, холодное переключение направления Az / El, небольшие оптимизации в коде * / #include  // Библиотека для связи I2C # include  // https://www.arduinolibraries.info/libraries/liquid-crystal-i2-c (Библиотека для ЖК-дисплея) // Подключение:вывод SDA подключен к A4, а вывод SCL - к A5 .// Подключение к ЖК-дисплею через I2C, адрес по умолчанию 0x27 (A0-A2 без перемычек) LiquidCrystal_I2C lcd (0x27, 16, 2); // адрес, символы, строки. // объявление пользовательского символа для байта стрелки вверх / вниз DownArrow [8] ={B00000, B00100, B00100, B00100, B10101, B01110, B00100, B00000}; byte UpArrow [8] ={B00000, B00100, B01110, B10101, B00100, B00100, B00100, B00000}; // КАЛИБРОВКА потенциометров АНТЕННЫ int AzMin =1; // начало потенциометра int AzMax =1023; // конец потенциометра int ElMin =1; int ElMax =1023; // Допустимая ошибка, при которой антенна не двигается int AzErr =8; int ElErr =4; // Переменные кодировщика Azim enum AzPinAssignments {AzEncoderPinA =2, // кодировщик справа AzEncoderPinB =3, // кодировщик слева AzClearButton =4}; // кодировщик push unsigned int lastReportedPos =1; // управление изменениями static boolean rotating =false; // управление устранением ошибок // процедура обслуживания прерывания vars boolean A_set =false; логическое B_set =false; // Переменные кодировщика Elev enum ElPinAssignments {ElEncoderPinA =6, // кодировщик справа ElEncoderPinB =5, // кодировщик слева ElClearButton =7}; // кодировщик push int aState; int aLastState; // другие переменные int AzPotPin =A0; // выбираем входной контакт для азима. потенциометр int AzRotPinR =13; // выбираем вывод для направления вращения int AzRotPinL =12; int TruAzim =0; // calculated real azimuth value int ComAzim =0; // commanded azimuth value int OldTruAzim =0; // to store previous azimuth value int OldComAzim =0; char AzDir; // symbol for azim rot display int AzEncBut =1; // variable to toggle with encoder push button int ElPotPin =A1; // select the input pin for the elev. potentiometer int ElRotPinD =11; // select the out pin for elevation rotation direction int ElRotPinU =10; int TruElev =0; // calculated real elevation value int ComElev =0; // commanded elevation value int OldTruElev =0; // to store previous elevation value int OldComElev =0; char ElDir; // symbol for elev. rot display int ElEncBut =1; // variable to toggle with encoder push button // flags for AZ, EL tolerances bool AzStop =false; bool ElStop =false; int ElUp =0; // 1 =Elevation Dn, 0 =Elevation STOP, 2 =Elevation Up //averaging loop const int numReadings =25; int readIndex =0; // the index of the current reading int azimuth[numReadings]; // the readings from the analog input int elevation[numReadings]; int totalAz =0; // the running total int totalEl =0;// variables for serial comm String Azimuth =""; String Elevation =""; String ComputerRead; String ComputerWrite; bool AZser =false; bool ELser =false; bool ANTser =false;/*************** END VARIABLE DECLARATION ************/ void setup() { Serial.begin(9600); Serial.setTimeout(50); // miliseconds to wait for USB sata. Default 1000// Initiate the LCD:// lcd.begin(16,2); //select this one if the arrows are not displayed correctly lcd.init(); lcd.backlight();// write on display name and version lcd.setCursor(0, 0); // Set the cursor on the first column first row.(counting starts at 0!) lcd.print("EasyCom AntRotor"); lcd.setCursor (0, 1); // Set the cursor on the first column the second row lcd.print("*Racov* Aug.2021 ");//creating custom symbol for up/dwn arrow lcd.createChar(1, DownArrow); lcd.createChar(2, UpArrow); // pin declaration pinMode(AzRotPinR, OUTPUT); //declaring azim. rotation direction Pin as OUTPUT pinMode(AzRotPinL, OUTPUT); pinMode(ElRotPinD, OUTPUT); //declaring elev. rotation direction Pin as OUTPUT pinMode(ElRotPinU, OUTPUT); pinMode(AzPotPin, INPUT); pinMode(ElPotPin, INPUT); pinMode(AzEncoderPinA, INPUT); pinMode(AzEncoderPinB, INPUT); pinMode(AzClearButton, INPUT); pinMode(ElEncoderPinA, INPUT); pinMode(ElEncoderPinB, INPUT); pinMode(ElClearButton, INPUT);// AzEncoder pin on interrupt 0 (pin A) attachInterrupt(0, doEncoderA, CHANGE);// AzEncoder pin on interrupt 1 (pin B) attachInterrupt(1, doEncoderB, CHANGE);// Reads the initial state of the ElEncoderPinA aLastState =digitalRead(ElEncoderPinA);/* initialization of the averaging loop */ TruAzim =(map(analogRead(AzPotPin), AzMin, AzMax, 0, 359)); // azimuth value 0-359 if (TruAzim<0) {TruAzim=0;} if (TruAzim>359) {TruAzim=359;} // keep values between limits TruElev =(map(analogRead(ElPotPin), ElMin, ElMax, 0, 90)); // elev value 0-90 if (TruElev<0) {TruElev=0;} if (TruElev>90) {TruElev=90;} // keep values between limits for (int thisReading =0; thisReading 359) {TruAzim=359;} if (TruElev<0) {TruElev=0;} if (TruElev>90) {TruElev=90;} // advance to the next position in the array:readIndex =readIndex + 1; // if we're at the end of the array, wrap around to the beginning:if (readIndex>=numReadings) {readIndex =0;} // this is to read the command from encoder ReadAzimEncoder(); ReadElevEncoder(); if (Serial.available()) {SerComm();} // read USB data// update antenna position display only if value change if ((millis()%500)<10){ //not to flicker the display if (OldTruAzim!=TruAzim) { DisplAzim(TruAzim,4,0); OldTruAzim =TruAzim; } if (OldTruElev!=TruElev) { DisplElev(TruElev,5,1); OldTruElev =TruElev; } }// update target position display only if value change if (OldComAzim !=ComAzim) { DisplAzim(ComAzim,12,0); OldComAzim =ComAzim; } if (OldComElev !=ComElev) { DisplElev(ComElev,13,1); OldComElev =ComElev; }// this is to rotate in azimuth if (TruAzim ==ComAzim) { // if equal, stop moving AzStop =true; digitalWrite(AzRotPinL, LOW); // deactivate rotation pin digitalWrite(AzRotPinR, LOW); lcd.setCursor(8, 0); lcd.print("="); } else if ((abs(TruAzim - ComAzim)<=AzErr)&&(AzStop ==false)) { // if in tolerance, but it wasn't an equal, rotate AzimRotate();} else if (abs(TruAzim - ComAzim)>AzErr){ // if target is off tolerance AzStop =false; // it's not equal AzimRotate(); // rotate }// this is to rotate in elevation if (TruElev ==ComElev) { // if equal, stop moving ElStop =true; digitalWrite(ElRotPinD, LOW); // deactivate elevator pin digitalWrite(ElRotPinU, LOW); lcd.setCursor (8, 1); lcd.print("="); ElUp =0; // flag for elevation STOP } else if ((abs(TruElev - ComElev)<=ElErr)&&(ElStop ==false)) { // if in tolerance, but it wasn't an equal, rotate ElevRotate();} else if (abs(TruElev - ComElev)>ElErr){ // if target is off tolerance ElStop =false; // it's not equal ElevRotate(); // rotate }// this is to interpret x10 AZ ENC multiplication while (AzEncBut ==10) { // while toggled to x10 digitalWrite(AzRotPinL, LOW); // deactivate rotation pin digitalWrite(AzRotPinR, LOW); digitalWrite(ElRotPinD, LOW); // deactivate elevator pin digitalWrite(ElRotPinU, LOW); lcd.setCursor(8, 0); lcd.print("*"); ReadAzimEncoder(); if (OldComAzim !=ComAzim){ // update display only if numbers change DisplAzim(ComAzim, 12, 0); OldComAzim =ComAzim; } задержка (100); }}// end main LOOP//____________________________________________________// ___________procedures definitions__________________void DisplAzim(int x, int y, int z) { char displayString[7] =""; sprintf(displayString, "%03d", x); //outputs a fixed lenght number (3 integer) lcd.setCursor(y, z); // for no leading zeros "__7" use "%3d" lcd.print(displayString); // ************** FOR CALIBRATION PURPOSES **************// Serial.print ("Az ");// Serial.println (analogRead(AzPotPin));}void DisplElev(int x, int y, int z){ char displayString[7] =""; sprintf(displayString, "%02d", x); //outputs a fixed lenght number (2 integer) lcd.setCursor(y, z); // for no leading zeros "_7" use "%2d" lcd.print(displayString);// ************** FOR CALIBRATION PURPOSES **************// Serial.print ("El ");// Serial.println (analogRead(ElPotPin));}void ReadElevEncoder() { aState =digitalRead(ElEncoderPinA); // Reads the "current" state of the ElEncoderPinA // If the previous and the current state of the ElEncoderPinA are different, that means a Pulse has occured if (aState !=aLastState){ // If the ElEncoderPinB state is different to the ElEncoderPinA state, that means the encoder is rotating clockwise if (digitalRead(ElEncoderPinB) !=aState) { ComElev ++;} else { ComElev --;} if (ComElev <0) {ComElev =0;} if (ComElev>90) {ComElev =90;} } aLastState =aState; // Updates the previous state of the ElEncoderPinA with the current state}void ReadAzimEncoder() { rotating =true; // reset the debouncer if (lastReportedPos !=ComAzim) { lastReportedPos =ComAzim; } задержка (10); if (digitalRead(AzClearButton) ==LOW ) { // if encoder switch depressed delay (250); // debounce switch if (AzEncBut ==1){ AzEncBut =10; ComAzim =int(ComAzim/10)*10; // ComAzim in 10deg. steps } else { AzEncBut =1; } }} //end ReadAzimEncoder()// Interrupt on A changing statevoid doEncoderA() { // debounce if ( rotating ) delay (1); // wait a little until the bouncing is done // Test transition, did things really change? if ( digitalRead(AzEncoderPinA) !=A_set ) { // debounce once more A_set =!A_set; // adjust counter + if A leads B if ( A_set &&!B_set ) ComAzim +=AzEncBut; ComAzim =((ComAzim + 360) % 360); // encoderPos between 0 and 359 deg. rotating =false; // no more debouncing until loop() hits again }}// Interrupt on B changing state, same as A abovevoid doEncoderB() { if ( rotating ) delay (1); if ( digitalRead(AzEncoderPinB) !=B_set ) { B_set =!B_set; // adjust counter - 1 if B leads A if ( B_set &&!A_set ) ComAzim -=AzEncBut; ComAzim =((ComAzim + 360) % 360); // encoderPos between 0 and 359 deg. rotating =false; } }void AzimRotate() { if ((ComAzim-TruAzim)> (TruAzim-ComAzim)) { // this to determine direction of rotation// cold switching - stop motor before changing direction - to protect mechanic and electric parts digitalWrite(AzRotPinL, LOW); // deactivate rotation pin Left if (AzDir ==char(127)) {delay(500);} // if previously rotating in the oposite direction, wait 0.5 seconds digitalWrite(AzRotPinR, HIGH); // activate rotation pin Right AzDir =char(126); // "->" } else { digitalWrite(AzRotPinR, LOW); if (AzDir ==char(126)) {delay(500);} digitalWrite(AzRotPinL, HIGH); AzDir =char(127); // "<-" } lcd.setCursor(8, 0); lcd.print(String(AzDir));}void ElevRotate() {// this to determine direction of rotation if ((ComElev-TruElev)> (TruElev-ComElev)) { digitalWrite(ElRotPinD, LOW); if (ElUp ==1) {delay(500);} digitalWrite(ElRotPinU, HIGH); lcd.setCursor (8, 1); lcd.write(2); // arrow up ElUp =2; } else { digitalWrite(ElRotPinU, LOW); if (ElUp ==2) {delay(500);} digitalWrite(ElRotPinD, HIGH); lcd.setCursor (8, 1); lcd.write(1); // arrow down ElUp =1; }}void SerComm() { // initialize readings ComputerRead =""; Azimuth =""; Elevation =""; while(Serial.available()) { ComputerRead=Serial.readString(); // read the incoming data as string Serial.println(ComputerRead); // echo the reception for testing purposes } // looking for command  for (int i =0; i <=ComputerRead.length(); i++) { if ((ComputerRead.charAt(i) =='A')&&(ComputerRead.charAt(i+1) =='Z')){ // if read AZ for (int j =i+2; j <=ComputerRead.length(); j++) { if (isDigit(ComputerRead.charAt(j))) { // if the character is number Azimuth =Azimuth + ComputerRead.charAt(j); } else {break;} } } } // looking for command  for (int i =0; i <=(ComputerRead.length()-2); i++) { if ((ComputerRead.charAt(i) =='E')&&(ComputerRead.charAt(i+1) =='L')){ // if read EL if ((ComputerRead.charAt(i+2)) =='-') { ComElev =0; // if elevation negative break; } for (int j =i+2; j <=ComputerRead.length(); j++) { if (isDigit(ComputerRead.charAt(j))) { // if the character is number Elevation =Elevation + ComputerRead.charAt(j); } else {break;} } } } // if  received if (Azimuth !=""){ ComAzim =Azimuth.toInt(); ComAzim =ComAzim%360; // keeping values between limits }// if  received if (Elevation !=""){ ComElev =Elevation.toInt(); if (ComElev>180) { ComElev =0;} if (ComElev>90) { //if received more than 90deg. (for trackers with 180deg. elevation) ComElev =180-ComElev; //keep below 90deg. ComAzim =(ComAzim+180)%360; //and rotate the antenna on the back } }// looking for  interogation for antenna position for (int i =0; i <=(ComputerRead.length()-4); i++) { if ((ComputerRead.charAt(i) =='A')&&(ComputerRead.charAt(i+1) =='Z')&&(ComputerRead.charAt(i+3) =='E')&&(ComputerRead.charAt(i+4) =='L')){ // send back the antenna position <+xxx.x xx.x> ComputerWrite ="+"+String(TruAzim)+".0 "+String(TruElev)+".0"; Serial.println(ComputerWrite); } }}// end SerComm()
Potentiometer calibration procedureArduino
AZ / EL Potentiometers limit calibration PROCEDURE for displaying the correct antenna angles and rotation limits ( 0-359ᴼ / 0-90ᴼ)
This is plain text, not a code :)
AZ / EL Potentiometers limit calibration PROCEDURE ( 0-359ᴼ / 0-90ᴼ)This might seem complicated, but it only has to be done once.1. Open the code in Arduino and - Look for void DisplAzim(int x, int y, int z) {...// Serial.print ("Az ");// Serial.println (analogRead(AzPotPin)); - Uncoment these lines. Should look like this:Serial.print ("Az "); Serial.println (analogRead(AzPotPin)); - Look for void DisplElev(int x, int y, int z){...// Serial.print ("El ");// Serial.println (analogRead(ElPotPin));Uncoment these lines, too. Should look like this:Serial.print ("El "); Serial.println (analogRead(ElPotPin));2. Upload the code and open the Serial Monitor. There you will see a lot of numbers;3. With the help of the encoders, move the antenna to minimum values, 0ᴼ in azimuth and 0ᴼ in elevation.- Write down the values for Azimuth and Elevation. (in my case it was AzMin=90, ElMin=10)- These are the input values read by Arduino, not the real angles;4. Move the antenna again to maximum values, 359ᴼ in azimuth and 90ᴼ in elevation.- Again, write down the values for Azimuth and Elevation. (in my case it was AzMax=1000, ElMax=992);5. Look in the code, at the beginning, for the section// ANTENNA potentiometers CALIBRATION int AzMin =1; int AzMax =1023; int ElMin =1; int ElMax =1023;- Here input the values you wrote down for each situation;6. Now it is no longer necessary to send this on serial, so you have to comment back these lines, like this:// Serial.print ("Az "); // Serial.println (analogRead(AzPotPin));... // Serial.print ("El "); // Serial.println (analogRead(ElPotPin));7. Upload again the code.That's all.Now, in the serial monitor, there should be no more numbers, and the true antenna position is read correctly.
Motor calibration procedureArduino
This procedure sets the parameters for the Antenna Speed-Up / Slow-Down Zone.
This is plain text, not a code :)
Motor Calibration Procedure For Soft-Start / Soft-Stop feature.This procedure sets the parameters for the Antenna Speed-Up / Slow-Down and the Dead-Zone.You basically set how fast and how slow you want the antenna to start and to stop. You also set much the target can move, before the antenna will adjust again.It’s not strictly necessary, only if you don’t like the default settings.Make sure you first apply the Potentiometer Calibration Procedure !!! That one is strictly necessary.Look at the power diagram for a better understanding.***For Azimuth movement***-As the antenna starts to move towards the target, is picking up speed, reaching full power after  degrees difference. -As the antenna closes in to the target, below  degrees difference, it starts to slow down.  should be higher for heavier antennas.-The power starts to decrease from  to  until the angle difference becomes zero.  (in percents %) should be 100 for full speed. If you ever think your antenna rotates too fast, you can set a smaller .  (in percents %) is the minimum power for which your motor doesn’t stall and can start under load. The power output never falls below this value.-Once the antenna reaches the target position (zero degrees error), it stops and doesn’t move again until the target travels more than  degrees. This is a dead zone, to prevent continuously shaking the antenna for the smallest target movement, or potentiometer position jitter. The smaller the , the more precise tracking, the more frequent shacking of the motors.***For Elevation movement***Exactly as for the Azimuth.Look at the beginning of the code for this section. Here you can input your desired values./**************THIS IS WHERE YOU REALY TWEAK THE ANTENNA MOVEMENT************/...// Allowed error for which antennna won't move. int AzErr =8; int ElErr =4;// Angle difference where soft stop begins int Amax =25; //azimuth int Emax =15; //elevation// min and max power for motors, percents; int PwAzMin =30; //minimum power for which the motor doesn't stall and starts under load int PwAzMax =100; //full power for the fastest speed int PwElMin =30; int PwElMax =100;/****************************************************************************/

Схема

Make sure you use this diagram with the code for DC motors.
Connection of all the modules, encoders, LCD, relays, MosFet etc, Make sure you use this diagram with the code for AC motors.
Offers dry contacts (ON/OFF). It can be easily interfaced with commercial rotators.

Производственный процесс

  1. Затемнение света с ШИМ с помощью кнопки
  2. Игра с гироскопом Arduino с MPU-6050
  3. Датчик DHT11 со светодиодами и пьезо-динамиком
  4. Unopad - MIDI-контроллер Arduino с Ableton
  5. Железный человек
  6. Простой датчик препятствий с Arduino
  7. Найди меня
  8. Управление увлажнителем Arduino
  9. Светодиодный куб 4x4x4 с Arduino Uno и 1sheeld
  10. Джойстик Arduino