Project tutorial
Arduino UNO Antenna Rotator

Arduino UNO Antenna Rotator © GPL3+

A simple interface for a homemade (or commercial) antenna rotator. Manual command or USB via computer tracking software.

  • 3,737 views
  • 3 comments
  • 15 respects

Components and supplies

Apps and online services

About this project

This is a project that started as an entertainment and is about to end as a serious piece of equipment for my ham radio hobby.

For computer communication, I am using Orbitron with a DDE plugin from http://tripsintech.com/orbitron-dde-azimuth-elevation-to-serial/

*the incoming string looks like (9600 bps) :

AZ25.5 EL45.7 SNOSCAR1_AO-9

Where AZ - target azimuth = 25.5 deg, EL - target elevation = 45.7 deg. SN - satellite name = OSCAR1_AO-9... the string can be longer, but it doesn't matter.

*The module outputs a response on serial like:

ANTAZ114 ANTEL34

Where ANTAZ - antenna azimuth = 114 deg, ANTEL34 - antenna elevation = 34 deg.

The code doesn't use any library (except for the LCD) and runs exactly as is, with pins according to the schematics.

More detailed explanations in the videos.

Code

Arduino UNO antenna rotator - July 2020Arduino
An Arduino controller for home-made or commercial antenna rotators. Displays on LCD the target and antenna azimuth and elevation, reads target position and outputs antenna position on USB, manual command of the antenna by two rotary encoders. Precision of tracking 1 degree.
//usb PC transmission to arduino
#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 = LiquidCrystal_I2C(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
};

// Azim encoder variables
  enum AzPinAssignments {
  AzEncoderPinA = 2,   // encoder right
  AzEncoderPinB = 3,   // encoder left
  AzClearButton = 4};    // encoder push
  int AzEncoderPos = 0;  // a counter for the dial
  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 ElEncoderPos = 0;
  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 AzPWM = 0;       // value to store calculated azimuth PWM duty cycle
  int AzPot = 0;       // variable to store the value coming from the potentiometer
  int TruAzim = 0;     // calculated real azimuth value
  int ComAzim = 0;     // commanded azimuth value
  int AzEncBut = 1;    // variable to toggle with encoder push button
  int OldTruAzim = 0;  // to store previous azimuth value
  int OldComAzim = 0;
  int OldTruElev = 0;  // to store previous elevation value
  int OldComElev = 0;
  char AzDir;                     // symbol for azim rot display
  boolean newData = false;        // for azim read command

  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 ElPWM = 0;       // value to store calculated elevation rotation PWM duty cycle
  int ElPot = 0;       // variable to store the value coming from the elevation pot.
  int TruElev = 0;     // calculated real elevation value
  int ComElev = 0;     // commanded elevation value
  int ElEncBut = 1;    // variable to toggle with elevation encoder push button
  char ElDir;          // symbol for elev. rot display
//averaging loop
  const int numReadings = 25;
  int azimuth[numReadings];      // the readings from the analog input
  int elevation[numReadings];
  int readIndex = 0;              // the index of the current reading
  int totalAz = 0;                  // the running total
  int totalEl = 0;
  int averageAz = 0;                // the average
  int averageEl = 0;
// variables for serial comm
  String Azimuth = "";
  String Elevation = "";
  String ComputerRead;
  String ComputerWrite;
  
void setup() {
  lcd.begin(16,2);
  lcd.createChar(1, DownArrow);           //creating custom symbol for up/dwn arrow
  lcd.createChar(2, UpArrow);
  pinMode(AzPotPin, INPUT);
  pinMode(ElPotPin, INPUT);
  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);
// azim. elev. encoder init
  pinMode(AzEncoderPinA, INPUT);
  pinMode(AzEncoderPinB, INPUT);
  pinMode(AzClearButton, INPUT);
  pinMode(ElEncoderPinA, INPUT);
  pinMode(ElEncoderPinB, INPUT);
  pinMode(ElClearButton, INPUT);
// turn on pullup resistors
  digitalWrite(AzEncoderPinA, HIGH);
  digitalWrite(AzEncoderPinB, HIGH);
  digitalWrite(AzClearButton, HIGH);
//  digitalWrite(ElEncoderPinA, HIGH);
//  digitalWrite(ElEncoderPinB, HIGH);
  digitalWrite(ElClearButton, HIGH);
// 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);

  Serial.begin(9600);

// Initiate the LCD:
//  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("ANTENNA ROTATOR"); // display "..."
  lcd.setCursor(0, 1);          // Set the cursor on the first column the second row
  lcd.print("*Racov* Jul.2020");
  delay(2000);                  // keep for 2 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)));
// initialize all the readings to 0:
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    azimuth[thisReading] = 0;
    elevation[thisReading] = 0;
  }
// this is to set azim-command the same value as real, not to jerk the antenna at start-up
  AzPot = analogRead(AzPotPin);    // read the value from the sensor 0-1023
  TruAzim = int(AzPot/2.842);      // azimuth value 0-359
  if (TruAzim<0) {TruAzim=0;}
  if (TruAzim>359) {TruAzim=359;}  // keep values between limits
  ElPot = analogRead(ElPotPin);
  TruElev = int(ElPot/11.3);       // elev value 0-90
  if (TruElev<0) {TruElev=0;}
  if (TruElev>90) {TruElev=90;}    // keep values between limits
  ComAzim = TruAzim;
  ComElev = TruElev;
  AzEncoderPos = TruAzim;
  ElEncoderPos = TruElev;
  OldTruAzim = TruAzim;
  OldComAzim = ComAzim;
  OldTruElev = TruElev;
  OldComElev = TruElev;
  DisplTruAzim();
  DisplComAzim();
  DisplTruElev();
  DisplComElev();
  }
  
void loop() {
// AVERAGING LOOP - subtract the last reading:
  totalAz = totalAz - azimuth[readIndex];
  // read from the sensor:
  azimuth[readIndex] = analogRead(AzPotPin);
  // add the reading to the total:
  totalAz = totalAz + azimuth[readIndex];
  // advance to the next position in the array:
  readIndex = readIndex + 1;
  // if we're at the end of the array...
  if (readIndex >= numReadings) {
    // ...wrap around to the beginning:
    readIndex = 0;
  }
  // calculate the average:
  averageAz = totalAz / numReadings;
//  Serial.println(averageAz);
  TruAzim = (averageAz-86)*1.127;
  TruAzim = int(TruAzim/2.842);      // azimuth value 0-359
  if (TruAzim<0) {TruAzim=0;}
  if (TruAzim>359) {TruAzim=359;}  // keep values between limits
  if (TruAzim != OldTruAzim) {     // display azim value if chnged
    DisplTruAzim();
    }
  //ELEVATION AVERAGING LOOP
  totalEl = totalEl - elevation[readIndex];
  elevation[readIndex] = analogRead(ElPotPin);;
  totalEl = totalEl + elevation[readIndex];
  readIndex = readIndex + 1;
  if (readIndex >= numReadings) {
    readIndex = 0;
  }
  averageEl = totalEl / numReadings;
  TruElev = ((averageEl-2)*1.025);
  TruElev = int(TruElev/11.3);       // elev value 0-90
  if (TruElev<0) {TruElev=0;}
  if (TruElev>90) {TruElev=90;}    // keep values between limits
  if (TruElev != OldTruElev) {     // display elev value if chnged
    DisplTruElev();
    }

// this is to read the command from pc
  ReadSerial();
  
// this is to read the command from encoder
  ReadAzimEncoder();
  ReadElevEncoder();
  
  if (ComAzim != OldComAzim) {
    DisplComAzim();
    }
  if (ComElev != OldComElev) {
    DisplComElev();
    }
  
// this is to rotate azimuth/elevation
  if (abs(TruAzim - ComAzim)>2) {  AzimRotate();}  //rotate only if more than 2 degree difference
  if (abs(TruElev - ComElev)>1) {  ElevRotate();}  //rotate only if more than 1 degree difference

// this is to interpter x10 multiplication, even if Tru position = Com position
  while (AzEncBut == 10) {       // while toggled to x10
    analogWrite(AzPWMPin, 0);    // deactivate pin azim PWM command
    analogWrite(ElPWMPin, 0);    // deactivate pin elev PWM command
    AzDir = char(42);            // display direction "*"
    lcd.setCursor(8, 0);
    lcd.print(String(AzDir));
    ReadAzimEncoder();
    if (OldComAzim != ComAzim){   // update display only if numbers change
      lcd.setCursor(12, 0);
    if (ComAzim<10) {
      lcd.print("00");
      lcd.print(ComAzim);}
    else if (ComAzim<100) {
      lcd.print("0");
      lcd.print(ComAzim);}
    else {lcd.print(ComAzim);}
    OldComAzim = ComAzim;
    }
    delay(10);
    }
/*  while (ElEncBut == 10) {       // while toggled to x10
    analogWrite(ElPWMPin, 0);    // deactivate pin elev PWM command
    ElDir = char(42);
    lcd.setCursor(8, 1);
    lcd.print(String(ElDir));    // display direction "*"
    ReadElevEncoder();
    if (OldComElev != ComElev){   // update display only if numbers change
    lcd.setCursor(13, 1);
    if (ComElev<10) {
      lcd.print("0");
      lcd.print(ComElev);}
    else {lcd.print(ComElev);}
    OldComElev = ComElev;
    }
    delay(10);
    }
*/
//  delay(10);                      //pause the program for x ms
}

//____________________________________________________
// ___________procedures definitions__________________

void DisplTruAzim() {
  lcd.setCursor(4, 0);
  if (TruAzim<10) {
      lcd.print("00");
      lcd.print(TruAzim);}
    else if (TruAzim<100) {
      lcd.print("0");
      lcd.print(TruAzim);}
    else {lcd.print(TruAzim);}
  lcd.print(String(char(223)));
  if (ComAzim == TruAzim) {         // command and real azim are equal, direction "="             
    lcd.setCursor(8, 0);
    lcd.print("=");
    analogWrite(AzPWMPin, 0);       // deactivate pin azim PWM command
  }
  OldTruAzim = TruAzim;
}
void DisplComAzim(){
  lcd.setCursor(12, 0);
  if (ComAzim<10) {
      lcd.print("00");
      lcd.print(ComAzim);}
    else if (ComAzim<100) {
      lcd.print("0");
      lcd.print(ComAzim);}
    else {lcd.print(ComAzim);}
  lcd.print(String(char(223)));
  if (ComAzim == TruAzim) {         // command and real azim are equal, direction "="             
    lcd.setCursor(8, 0);
    lcd.print("=");
    analogWrite(AzPWMPin, 0);       // deactivate pin azim PWM command
  }
  OldComAzim = ComAzim;
}
void DisplTruElev(){
  lcd.setCursor(5, 1);
  if (TruElev<10) {
      lcd.print("0");
      lcd.print(TruElev);}
    else {lcd.print(TruElev);}
  lcd.print(String(char(223)));
  if (ComElev == TruElev) {         // command and real elev are equal, direction "="             
    lcd.setCursor(8, 1);
    lcd.print("=");
    analogWrite(ElPWMPin, 0);       // deactivate pin elev PWM command
  }
  OldTruElev = TruElev;
}
void DisplComElev(){
  lcd.setCursor(13, 1);
  if (ComElev<10) {
      lcd.print("0");
      lcd.print(ComElev);}
    else {lcd.print(ComElev);}
  lcd.print(String(char(223)));
  if (ComElev == TruElev) {         // command and real elev are equal, direction "="             
    lcd.setCursor(8, 1);
    lcd.print("=");
    analogWrite(ElPWMPin, 0);       // deactivate pin elev PWM command
  }
  OldComElev = ComElev;
}

void ReadElevEncoder() {
/*  if (digitalRead(ElClearButton) == LOW )  {      // if encoder switch depressed
    delay (300);                                  //debounce switch-wait xx ms
    if (ElEncBut == 1){
      ElEncBut = 5;
      ElEncoderPos = int(AzEncoderPos/10)*10;
      ElDir = char(42);
    }
      else {
        ElEncBut = 1;
        ElDir = char(61);
    }
    lcd.setCursor(8, 1);
    lcd.print(String(ElDir));                     // display direction
  }
*/
  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) { 
        ElEncoderPos ++;
     } else {
       ElEncoderPos --;
     }
     if (ElEncoderPos <0) {ElEncoderPos = 0;}
     if (ElEncoderPos >90) {ElEncoderPos = 90;}
   }
   ComElev = ElEncoderPos;
   aLastState = aState; // Updates the previous state of the ElEncoderPinA with the current state
}

void ReadAzimEncoder() {
  rotating = true;  // reset the debouncer
  if (lastReportedPos != AzEncoderPos) {
    ComAzim = AzEncoderPos;
    lastReportedPos = AzEncoderPos;
  }
  if (digitalRead(AzClearButton) == LOW )  {      // if encoder switch depressed
    delay (250);                                  // debounce switch 
    if (AzEncBut == 1){
      AzEncBut = 10;
      AzEncoderPos = int(AzEncoderPos/10)*10;
      AzDir = char(42);
    }
      else {
        AzEncBut = 1;
        AzDir = char(61);
    }
    lcd.setCursor(8, 0);
    lcd.print(String(AzDir));
  }
}

// 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 )
      AzEncoderPos += AzEncBut;
      AzEncoderPos = ((AzEncoderPos + 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 )
      AzEncoderPos -= AzEncBut;
      AzEncoderPos = ((AzEncoderPos + 360) % 360);     // encoderPos between 0 and 359 deg.
    rotating = false;
  }
 }

void AzimRotate() {
  AzPWM = abs(ComAzim-TruAzim);             // the difference between command and true azim
  if (AzPWM <3 ) {                          //rotate only if less than 3 degrees difference
    analogWrite(AzPWMPin, 0);       // deactivate pin azim PWM command
    AzDir = char(61);               // because command and real azim are equal, direction "="
//  delay (20); 
  }
  else {                                       // if error more than 2 degrees
    // this to determine direction of rotation
    if ((ComAzim-TruAzim) > (TruAzim-ComAzim)) {
        digitalWrite(AzRotPin, LOW);                  // deactivate rotation pin - rotate right
        AzDir = char(126);}                           // "->"
      else {
        digitalWrite(AzRotPin, HIGH);                 // activate rotation pin - rotate left
        AzDir = char(127);}                           // "<-"
 // this activates azim PWM pin with soft stop
    if (AzPWM < 6) {
        analogWrite(AzPWMPin, (255/3));}      // if less then 6 deg., 30% power         
      else if (AzPWM < 10) {
        analogWrite(AzPWMPin, (255/1.5));}    // if less then 10 deg., 60% power        
      else {
        analogWrite(AzPWMPin, 255);}          // more than 10 deg. diff., full power     
  }
  lcd.setCursor(8, 0);
  lcd.print(String(AzDir));
}

void ElevRotate() {
  if (abs(TruElev - ComElev) > 1) {    //rotate only if more than 1 degree difference
// this to determine direction of rotation
    if ((ComElev-TruElev) > (TruElev-ComElev)) {
        digitalWrite(ElRotPin, LOW);                  // deactivate rotation pin - rotate right
        lcd.setCursor(8, 1);
        lcd.write(2);}                                // arrow up
     else {
        digitalWrite(ElRotPin, HIGH);                 // activate rotation pin - rotate left
        lcd.setCursor(8, 1);
        lcd.write(1);}                                // arrow down
 // this activates elev PWM pin with soft stop
    ElPWM = abs(ComElev-TruElev);           // the difference between command and true elev
    if (ElPWM < 8) {
        analogWrite(ElPWMPin, 225/1.5);}    // if less then 8 deg., 60% power          
      else {
        analogWrite(ElPWMPin, 255);}        // more than 8 deg. diff., full power       
//  delay (20);
  }
  if (abs(TruElev - ComElev) <= 1) {
    analogWrite(ElPWMPin, 0);       // deactivate pin elev PWM command
    lcd.setCursor(8, 1);
    lcd.print("=");                 // because command and real azim are equal, direction "="
  }
}

void ReadSerial() {
  while(Serial.available()) {
    ComputerRead= Serial.readString();  // read the incoming data as string
//    Serial.println(ComputerRead);   //if i want to see what is coming via the serial com
    if ((ComputerRead.charAt(0) == 'A')&&(ComputerRead.charAt(1) == 'Z')){ // if read AZ
      Azimuth = "";                                 // initialize Azimuth
      for (int i = 2; i <= ComputerRead.length(); i++) {
        if (ComputerRead.charAt(i) == ' ') {        // if pause detected, see if read EL
          if ((ComputerRead.charAt(i+1) == 'E')&&(ComputerRead.charAt(i+2) == 'L')){
            Elevation = "";                         // initialize Elevation
            for (int j = i+3 ; j <= ComputerRead.length() ; j++) {
              if (ComputerRead.charAt(j) == ' ') { break;}
              Elevation = Elevation + ComputerRead.charAt(j);
            }
            break;                                  // exit
          }
          break;
        }                            
        Azimuth = Azimuth + ComputerRead.charAt(i);
      }
    }
    ComAzim = Azimuth.toInt();
    ComElev = Elevation.toInt();
// keeping comand between limits
    ComAzim = (ComAzim+360)%360;
    if (ComElev<0) { ComElev = 0;}
    if (ComElev>90) { ComElev = 90;}
// set the encoder values, not to jerk the antenna
    AzEncoderPos = ComAzim;
    ElEncoderPos = ComElev;
    ComputerWrite = "ANTAZ"+String(TruAzim)+" ANTEL"+String(TruElev);
    Serial.println(ComputerWrite);
  }
}

Schematics

Arduino UNO antenna rotator
Circuit diagram and pinout
Drawing1 go0czg5f4v

Comments

Similar projects you might like

Arduino Uno Autonomous Car

Project tutorial by hannahmcneelyy

  • 20,932 views
  • 2 comments
  • 28 respects

Sending Data from PC to Another Using Arduino UNO

Project tutorial by Mohamed Maher

  • 5,204 views
  • 2 comments
  • 13 respects

Control Arduino Uno Using ESP8266 WiFi Module and Blynk App

Project tutorial by Adithya TG

  • 68,833 views
  • 18 comments
  • 61 respects

Multifunctional Watch With Arduino Uno

Project showcase by shaqibmusa94

  • 5,564 views
  • 2 comments
  • 12 respects

Ohm meter with Arduino Uno

Project tutorial by xristos_xatz

  • 3,237 views
  • 0 comments
  • 11 respects

Tito - Arduino UNO 3D-printed robot

Project tutorial by Camilo Parra Palacio

  • 21,727 views
  • 21 comments
  • 56 respects
Add projectSign up / Login