Industrial manufacturing
Industrial Internet of Things | Industrial materials | Equipment Maintenance and Repair | Industrial programming |
home  MfgRobots >> Industrial manufacturing >  >> Manufacturing Technology >> Manufacturing process

Professional Antenna Rotator Controller – Seamless Integration with Tracking Software

Components and supplies

Professional Antenna Rotator Controller – Seamless Integration with Tracking Software
Arduino UNO
Arduino Uno board
×1
Professional Antenna Rotator Controller – Seamless Integration with Tracking Software
Rotary potentiometer (generic)
max. 1Kohm (500 Ohms work better)
×2
Professional Antenna Rotator Controller – Seamless Integration with Tracking Software
Rotary Encoder with Push-Button
×2
Professional Antenna Rotator Controller – Seamless Integration with Tracking Software
Breadboard (generic)
×1
Relay Module (Generic)
2 modules x 2 relay NO-Com-NC
×2
Professional Antenna Rotator Controller – Seamless Integration with Tracking Software
Power MOSFET N-Channel
power mosfet module (min 12V/3A)
×2

Apps and online services

Professional Antenna Rotator Controller – Seamless Integration with Tracking Software
Arduino IDE

About this project

Latest update November 2021

This project started as an entertainment and became a serious piece of equipment.

The Controller accepts manual positioning of the antenna, by means of two rotary encoders, Azimuth and Elevation. It can automatically track satellites, when connected by USB to a PC running satellite tracking software.

It's compatible with all tracking software using EasyComm2 protocol / 9600 bauds. PstRotator, WXtrack, HRD, MacDoppler... Even WXtoIMG can control the rotator.

It works directly with Orbitron, with a DDE plugin from http://tripsintech.com/orbitron-dde-azimuth-elevation-to-serial/

The controller outputs a response on serial, for the tracking software to display the real antenna position on the screen. So far, only PstRotator did that.

The code doesn't use any library (except for the LCD) and runs exactly as is, with pins according to the electric diagram below. If you press the button of the Azimuth encoder, all antenna movement stops immediately, and the azimuth command can be set in 10deg. steps.

You will find here two versions: One for DC motors, and one for AC motors (just relays). The later one can be interfaced with existing commercial antenna rotators.

The DC motors version has the advantage of using PWM for a softer/smoother antenna movement. It outputs a power response proportional with angle error (Target<->Antenna). Therefore, when the antenna starts moving, it accelerates progressively, and, when approaching the desired position, it slows down until full stop. This is known as Soft-Start / Soft-Stop. There's an adjustable Dead Zone, where the antenna doesn't move for the slightest target offset.

Professional Antenna Rotator Controller – Seamless Integration with Tracking Software

I have a beta version with Soft-Start / Soft-Stop for AC motors, taking advantage of this AC-Dimmer module, but right now it only works for azimuth. If you want to give it a try, let me know by email.

If you have 180deg. elevation system, you're good, give me an email. There's also a version with 0.1deg. precision, but I wouldn't recommend it unless you have a goddamn rock solid potentiometer reading and a paranoiac controller construction design. You'll find more versions on my web page.

After finishing the construction, you must apply the calibration procedures.

  • The potentiometer calibration is mandatory and ensures correct reading of 0-359deg. / 0-90deg., no matter what kind of potentiometer you're using.
  • The motor calibration is only for tuning the Soft-Start-Stop feature. This is necessary if you don't like the default settings.

More detailed explanations in the videos. Because the code have been improved over time, and the videos can't be updated anymore, check my web-page for the latest information and personal experience living with this controller. https://racov.ro/index.php/2020/12/09/arduino-based-antenna-rotator-part3-software-tracking-update/

Give me an email if you want to know more, because this platform doesn't inform me of new comments, dunno why. I'll try to solve small problems the best I can. YO3RAK@gmail.com

Thank you very much to all who sent me feedback, helping to make this project more reliable. Any feedback is highly appreciated.

Code

  • ant-rot-DC-nov2021
  • ant-rot-AC-aug2021
  • Potentiometer calibration procedure
  • Motor calibration procedure
ant-rot-DC-nov2021Arduino
This code is for DC motors, with soft start-stop PWM output
/*  AZ/EL Antenna Rotator controller for Arduino - DC motors
 *  ========================================================
 *  Uses EasyComm protocol for computer - Tracking Software
 *  Manual command by means of two rotary encoders AZ - EL
 *  
 *  Viorel Racoviteannu
 *  https://www.youtube.com/channel/UCiRLZX0bV9rS04BGAyUf-fA
 *  https://racov.ro
 *  YO3RAK@gmail.com
 *  
 * I cannot take any responsibility for missuse of this code
 * or any kind of damage it may occur from using this code.
 * 
 * dec 2020 v2 - improved serial comm stability
 * jan 2021 - improved near target dead-zone, for which antenna won't move
 * apr 2021 - improved serial comm stability
 * jun 2021 - error proportional power for tracking movement. Real Soft-Stop
 * aug 2021 - faster USB update, cold switching Az/El direction, small optimizations in the code
 * nov 2021 - cracked the secret of Soft-Start. It wasn't hard. There you have it
 */
 
#include <Wire.h> // Library for I2C communication
#include <LiquidCrystal_I2C.h> // Library for LCD
// Wiring: SDA pin is connected to A4 and SCL pin to A5.
// Connect to LCD via I2C, default address 0x27 (A0-A2 not jumpered)
LiquidCrystal_I2C lcd(0x27, 16, 2); // address, chars, rows.

// declaring custom symbol for up/down arrow
 byte DownArrow[8] = {
  B00000,
  B00100,
  B00100,
  B00100,
  B10101,
  B01110,
  B00100,
  B00000
};
 byte UpArrow[8] = {
  B00000,
  B00100,
  B01110,
  B10101,
  B00100,
  B00100,
  B00100,
  B00000
};

/***********************************THIS IS WHERE YOU REALY TWEAK THE ANTENNA MOVEMENT***************/
// ANTENNA potentiometers CALIBRATION
  int AzMin = 1;         //begining of the potentiometer
  int AzMax = 1023;      //end of the potentiometer 
  int ElMin = 1;
  int ElMax = 1023;

// 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;
  
  int PwAz = 0;        //calculated power to be transmitted to motor (percents);
  int PwEl = 0;
/***************************************************************************************************/

// Encoder variables
  enum AzPinAssignments {
  AzEncoderPinA = 2,                   // Az encoder right
  AzEncoderPinB = 3,                   // encoder left
  AzClearButton = 4,                   // encoder push
  ElEncoderPinA = 6,                   // El encoder right
  ElEncoderPinB = 5};                  // encoder left
  // interrupt service routine vars
  unsigned int lastReportedPos = 1;    // change management
  static boolean rotating = false;     // debounce management
  boolean A_set = false;
  boolean B_set = false;
  int aState;
  int aLastState;
  
// other variables
  int AzPotPin = A0;   // select the input pin for the azim. potentiometer
  int AzRotPin = 12;   // select the out pin for rotation direction
  int AzPWMPin = 11;   // select the out pin for azimuth PWM command
  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 ElRotPin = 13;   // select the out pin for elevation rotation direction
  int ElPWMPin = 10;   // select the out pin for elevation rotation PWM command
  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
  
// flags for AZ, EL tolerances
  bool AzStop = false;
  bool ElStop = false;
  int ElUp = 1;        // 1 - Elevation Dn, 0 - Elevation STOP, 2 - Elevation Up
  int StaAzim = 0;     // Start Azimuth angle for motor Soft-Start
  int PwAzStop = 0;    // calculated PWM (percent) for soft-stop
  int PwAzStar = 0;    // calculated PWM (percent) for soft-start
  int StaElev = 0;     // Start Elevation angle for motor Soft-Start
  int PwElStop = 0;    // calculated PWM (percent) for soft-stop
  int PwElStar = 0;    // calculated PWM (percent) for soft-start
  
//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"); // display "..."
  lcd.setCursor(0, 1);           // Set the cursor on the first column the second row
  lcd.print("*Racov* Nov.2021");

//creating custom symbol for up/dwn arrow
  lcd.createChar(1, DownArrow);
  lcd.createChar(2, UpArrow);
  
// pin declaration
  pinMode(AzRotPin, OUTPUT);       //declaring  azim. rotation direction Pin as OUTPUT
  pinMode(AzPWMPin, OUTPUT);       //declaring  azimuth PWM command Pin as OUTPUT
  pinMode(ElRotPin, OUTPUT);       //declaring  elev. rotation direction Pin as OUTPUT
  pinMode(ElPWMPin, OUTPUT);
  pinMode(AzPotPin, INPUT);
  pinMode(ElPotPin, INPUT);
  pinMode(AzEncoderPinA, INPUT);
  pinMode(AzEncoderPinB, INPUT);
  pinMode(AzClearButton, INPUT);
  pinMode(ElEncoderPinA, INPUT);
  pinMode(ElEncoderPinB, 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 < numReadings; thisReading++) {
    azimuth[thisReading] = TruAzim;
    elevation[thisReading] = TruElev;
  }
  totalAz = TruAzim * numReadings;
  totalEl = TruElev * numReadings;
  
// this is to set azim-command the same value as real, not to jerk the antenna at start-up
  ComAzim = TruAzim;
  ComElev = TruElev;
  OldTruAzim = TruAzim;
  OldComAzim = ComAzim;
  OldTruElev = TruElev;
  OldComElev = TruElev;
  
  delay(1500);                    // keep for 1.5 seconds
// display Azim. and Elev. values
  lcd.setCursor(0, 0);
  lcd.print("Azm.---" + String(char(223)) + "=Cd.---" + String(char(223)));  // char(223) is degree symbol
  lcd.setCursor(0, 1); 
  lcd.print("Elv. --" + String(char(223)) + "=Cd. --" + String(char(223)));
  DisplAzim(TruAzim, 4,0);
  DisplAzim(ComAzim,12,0);
  DisplElev(TruElev, 5,1);
  DisplElev(ComElev,13,1);
}
// end SETUP

void loop() {
/************** FYI, this loop repeats 500 times per second !!! **************/
// AZIMUTH/ELEVATION AVERAGING LOOP
  // subtract the oldest value
  totalAz = totalAz - azimuth[readIndex];
  totalEl = totalEl - elevation[readIndex];
  // read from the sensor:
  azimuth[readIndex] = (map(analogRead(AzPotPin), AzMin, AzMax, 0, 359));
  elevation[readIndex] = (map(analogRead(ElPotPin), ElMin, ElMax, 0, 90));
  // add the reading to the total:
  totalAz = totalAz + azimuth[readIndex];
  totalEl = totalEl + elevation[readIndex];
  // do the average
  TruAzim = totalAz / numReadings;
  TruElev = totalEl / numReadings;
  // keep values between limits
  if (TruAzim<0) {TruAzim=0;}
  if (TruAzim>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;
    analogWrite(AzPWMPin, 0);                 // Az motor power = 0
    StaAzim = TruAzim;                        // this will be the start azimuth for soft-start
    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;
    analogWrite(ElPWMPin, 0);                 // El motor power = 0
    StaElev = TruElev;                        // this will be the start elevation for soft-start
    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 Az encoder x10 multiplication
  while (AzEncBut == 10) {                    // while toggled to x10
    analogWrite(AzPWMPin, 0);                 // STOP antenna rotation
    StaAzim = TruAzim;                        // this will be the start azimuth for soft-start
    analogWrite(ElPWMPin, 0);
    lcd.setCursor(8, 0);
    lcd.print("*");
    ReadAzimEncoder();
    if (OldComAzim != ComAzim){               // update display only if numbers change
      DisplAzim(ComAzim, 12, 0);
      OldComAzim = ComAzim;
    }
    delay (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;
  }
  delay(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 state
void 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 above
void 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) {                          // this to determine direction of rotation
// cold switching - stop motor before changing direction - to protect mechanic and electric parts
        if (AzDir == char(127)) {                     // if previously rotating in the oposite direction
          analogWrite(AzPWMPin, 0);                   // STOP the motor
          StaAzim = TruAzim;                          // this will be the start azimuth for soft-start
          delay(200);                                 // pre-switch delay
          digitalWrite(AzRotPin, LOW);                // deactivate rotation pin - rotate right
          delay(200);                                 // post-switch delay
        }
        else {                                        // same directin, no Stop, no delay
          digitalWrite(AzRotPin, LOW);                // deactivate rotation pin - rotate right
        }
          AzDir = char(126);                          // "->"
    }
      else {
        if (AzDir == char(126)) {                     // if previously rotating in the oposite direction
          analogWrite(AzPWMPin, 0);                   // STOP the motor
          StaAzim = TruAzim;                          // this will be the start azimuth for soft-start
          delay(200);                                 // pre-switch delay
          digitalWrite(AzRotPin, HIGH);               // activate rotation pin - rotate left
          delay(200);                                 // post-switch delay
        }
        else {                                        // same directin, no Stop, no delay
          digitalWrite(AzRotPin, HIGH);               // activate rotation pin - rotate left
        }
        AzDir = char(127);                            // "<-"
      }
    lcd.setCursor(8, 0);
    lcd.print(String(AzDir));
 // this activates azim PWM pin proportional with angle error (calculated in percents %)
    PwAzStop = PwAzMin + round((abs(ComAzim-TruAzim))*(PwAzMax-PwAzMin)/Amax);   //formula which outputs a power proportional with angle difference for Soft-Stop
    PwAzStar = PwAzMin + round((abs(StaAzim-TruAzim))*(PwAzMax-PwAzMin)/Amax);   //formula which outputs a power proportional with angle difference for Soft-Start
    if (PwAzStar > PwAzStop){
         PwAz = PwAzStop;                             //choose whichever value is smallest
      }
      else {PwAz = PwAzStar;}
    if (PwAz > PwAzMax) {PwAz = PwAzMax;}
    analogWrite(AzPWMPin, round(2.55*PwAz));          // activate Azim drive PWM pin 
}
// end AzimRotate()

void ElevRotate() {
// this to determine direction of rotation
    if (ComElev > TruElev) {
      if (ElUp == 1) {                                // if previously rotating in the oposite direction
        analogWrite(ElPWMPin, 0);                     // STOP the motor
        StaElev = TruElev;                            // this will be the start elevation for soft-start
        delay(200);                                   // pre-switch delay
        digitalWrite(ElRotPin, LOW);                  // deactivate rotation pin - rotate UP
        delay(200);                                   // post-switch delay
      }
      else {                                          // same directin, no Stop, no delay
        digitalWrite(ElRotPin, LOW);                  // deactivate rotation pin - rotate UP
      }
      lcd.setCursor(8, 1);
      lcd.write(2);                                   // arrow up
      ElUp = 2;                                       // flag for elevation UP
    }
     else {
      if (ElUp == 2) {                                // if previously rotating in the oposite direction
        analogWrite(ElPWMPin, 0);                     // STOP the motor
        StaElev = TruElev;                            // this will be the start elevation for soft-start
        delay(200);                                   // pre-switch delay
        digitalWrite(ElRotPin, HIGH);                 // deactivate rotation pin - rotate UP
        delay(200);                                   // post-switch delay
      }
      else {                                          // same directin, no Stop, no delay
        digitalWrite(ElRotPin, HIGH);                 // deactivate rotation pin - rotate UP
      }
        lcd.setCursor(8, 1);
        lcd.write(1);                                 // arrow down
        ElUp = 1;                                     // flag for elevation DN
    }
 // this activates azim PWM pin proportional with angle error (calculated in percents %)
    PwElStop = PwElMin + round((abs(ComElev-TruElev))*(PwElMax-PwElMin)/Emax);   //formula which outputs a power proportional with angle difference for Soft-Stop
    PwElStar = PwElMin + round((abs(StaElev-TruElev))*(PwElMax-PwElMin)/Emax);   //formula which outputs a power proportional with angle difference for Soft-Start
    if (PwElStar > PwElStop){
         PwEl = PwElStop;                             //choose whichever value is smallest
      }
      else {PwEl = PwElStar;}
    if (PwEl > PwElMax) {PwEl = PwElMax;}
    analogWrite(ElPWMPin, round(2.55*PwEl));          // activate Elev drive PWM pin
}
// end ElevRotate()

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 <AZxxx.x>
    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 <ELxxx.x>
    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 <AZxx> received
    if (Azimuth != ""){
      ComAzim = Azimuth.toInt();
      ComAzim = ComAzim%360;          // keeping values between limits(for trackers with more than 360 deg. rotation)
      }

// if <ELxx> 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 <AZ EL> 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()
ant-rot-AC-aug2021Arduino
Make sure you use the Electric Diagram for AC motors
Offers dry contacts (ON/OFF). It can be easily interfaced with commercial rotators.
/*  AZ/EL Antenna Rotator controller for Arduino - AC motors
 *  ========================================================
 *  Uses EasyComm protocol for computer - Tracking Software
 *  Manual command by means of two rotary encoders AZ - EL
 *  
 *  compatible with switch-box rotators 
 *  or AC motors
 *  dry contatcts for Left-Right, Up-Down
 *  
 *  Viorel Racoviteannu /
 *  https://www.youtube.com/channel/UCiRLZX0bV9rS04BGAyUf-fA
 *  https://racov.ro
 *  YO3RAK@gmail.com
 *  
 * I cannot take any responsibility for missuse of this code
 * or any kind of damage it may occur from using this code.
 * 
 * dec 2020 v2 - improved serial comm stability
 * jan 2021 - fixed AZ, EL tolerances for motor activation
 * apr 2021 - improved serial comm stability
 * aug 2021 - faster USB update, cold switching Az/El direction, small optimizations in the code
 */
 
#include <Wire.h> // Library for I2C communication
#include <LiquidCrystal_I2C.h> // https://www.arduinolibraries.info/libraries/liquid-crystal-i2-c (Library for LCD)
// Wiring: SDA pin is connected to A4 and SCL pin to A5.
// Connect to LCD via I2C, default address 0x27 (A0-A2 not jumpered)
LiquidCrystal_I2C lcd(0x27, 16, 2); // address, chars, rows.

// declaring custom symbol for up/down arrow
 byte DownArrow[8] = {
  B00000,
  B00100,
  B00100,
  B00100,
  B10101,
  B01110,
  B00100,
  B00000
};
 byte UpArrow[8] = {
  B00000,
  B00100,
  B01110,
  B10101,
  B00100,
  B00100,
  B00100,
  B00000
};

// ANTENNA potentiometers CALIBRATION
  int AzMin = 1;        //begining of the potentiometer
  int AzMax = 1023;     //end of the potentiometer 
  int ElMin = 1;
  int ElMax = 1023;

// Allowed error for which antenna won't move
  int AzErr = 8;
  int ElErr = 4;

// Azim encoder variables
  enum AzPinAssignments {
  AzEncoderPinA = 2,                  // encoder right
  AzEncoderPinB = 3,                  // encoder left
  AzClearButton = 4};                 // encoder push
  unsigned int lastReportedPos = 1;   // change management
  static boolean rotating = false;    // debounce management
  // interrupt service routine vars
  boolean A_set = false;
  boolean B_set = false;
  
//Elev encoder variables
  enum ElPinAssignments{
  ElEncoderPinA = 6,                  // encoder right
  ElEncoderPinB = 5,                  // encoder left
  ElClearButton = 7};                 // encoder push
  int aState;
  int aLastState; 
  
// other variables
  int AzPotPin = A0;    // select the input pin for the azim. potentiometer
  int AzRotPinR = 13;   // select the out pin for rotation direction
  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 < numReadings; thisReading++) {
    azimuth[thisReading] = TruAzim;
    elevation[thisReading] = TruElev;
  }
  totalAz = TruAzim * numReadings;
  totalEl = TruElev * numReadings;
// this is to set azim-command the same value as real, not to jerk the antenna at start-up
  ComAzim = TruAzim;
  ComElev = TruElev;
  OldTruAzim = TruAzim;
  OldComAzim = ComAzim;
  OldTruElev = TruElev;
  OldComElev = TruElev;
  
   delay(1500);                   // keep for 1.5 seconds
// display Azim. and Elev. values                          
  lcd.setCursor(0, 0);
  lcd.print("Azm.---" + String(char(223)) + "=Cd.---" + String(char(223)));  // char(223) is degree symbol
  lcd.setCursor(0, 1); 
  lcd.print("Elv. --" + String(char(223)) + "=Cd. --" + String(char(223)));
  DisplAzim(TruAzim, 4,0);
  DisplAzim(ComAzim,12,0);
  DisplElev(TruElev, 5,1);
  DisplElev(ComElev,13,1);
}
// end SETUP
  
void loop() {
/************** FYI, this loop repeats 500 times per second !!! **************/
// AZIMUTH/ELEVATION AVERAGING LOOP
  // subtract the oldest value
  totalAz = totalAz - azimuth[readIndex];
  totalEl = totalEl - elevation[readIndex];
  // read from the sensor:
  azimuth[readIndex] = (map(analogRead(AzPotPin), AzMin, AzMax, 0, 359));
  elevation[readIndex] = (map(analogRead(ElPotPin), ElMin, ElMax, 0, 90));
  // add the reading to the total:
  totalAz = totalAz + azimuth[readIndex];
  totalEl = totalEl + elevation[readIndex];
  // do the average
  TruAzim = totalAz / numReadings;
  TruElev = totalEl / numReadings;
  // keep values between limits
  if (TruAzim<0) {TruAzim=0;}
  if (TruAzim>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;
    }
    delay(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;
  }
  delay(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 state
void 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 above
void 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 <AZxxx.x>
    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 <ELxxx.x>
    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 <AZxx> received
    if (Azimuth != ""){
      ComAzim = Azimuth.toInt();
      ComAzim = ComAzim%360;     // keeping values between limits
    }

// if <ELxx> 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 <AZ EL> 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 <Amax> degrees difference. 
-As the antenna closes in to the target, below <Amax> degrees difference, it starts to slow down. <Amax> should be higher for heavier antennas.
-The power starts to decrease from <PwAzMax> to <PwAzMin> until the angle difference becomes zero.
  <PwAzMax> (in percents %) should be 100 for full speed. If you ever think your antenna rotates too fast, you can set a smaller <PwAzMax>.
  <PwAzMin> (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 <AzErr> degrees.
  This is a dead zone, to prevent continuously shaking the antenna for the smallest target movement, or potentiometer position jitter.
  The smaller the <AzErr>, 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;
/****************************************************************************/

Schematics

Make sure you use this diagram with the code for DC motors.
Connection of all the modules, encoders, LCD, relays, MosFet etc,Professional Antenna Rotator Controller – Seamless Integration with Tracking SoftwareMake sure you use this diagram with the code for AC motors.
Offers dry contacts (ON/OFF). It can be easily interfaced with commercial rotators.Professional Antenna Rotator Controller – Seamless Integration with Tracking Software

Manufacturing process

  1. Control LED Brightness with PWM via Push‑Button on Arduino Mega
  2. Build a Real-Time Gyroscope Game with Arduino Nano & MPU-6050 Sensor
  3. DHT11 Temperature & Humidity Sensor Project with LED Indicators and Piezo Speaker
  4. Unopad: Seamless Arduino MIDI Controller for Ableton Live
  5. Build a Simple Obstacle Sensor with Arduino – Easy IR LED & Photodiode Tutorial
  6. Find Me: Smart Item Locator with Arduino and Bluetooth
  7. Build a 4x4x4 LED Cube with Arduino Uno & 1Sheeld – Interactive LED Project
  8. Build a Custom Arduino Joystick Steering Wheel for Gaming
  9. Master Modbus on Arduino: Step‑by‑Step Guide
  10. Real-Time Soil Moisture Monitoring with LCD Display – Arduino DIY Kit