/* 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 * january 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 * */ /// VERSION HISTORY : // V4 : implement interpolation between 8 ref positions // V5 : limit motor running time to 1min15 = 75000 ms = OK // V6 : stall detect , AZ not changing // V7 : manual / software control mode + OK ! // V8 : LCD parallel // STOP command software // V9 : finetuning // V10 : CW / CCW cast difference // V11 : ERR display // V12 : 27-02-2022: 500ms delay if motor direction is reversed while running // V13 : Buzzer for error alert /* // FOR I2C LCD #include // Library for I2C communication #include // 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, 8, 2); // address, chars, rows. */ // FOR PARALLEL LCD #include LiquidCrystal lcd(7, 8, 9, 10, 11, 12); /* ***** for EA DIPS082 DISPLAY 2x 8 ****** * * * LCD RS pin 4 to digital pin 7 * LCD R/W pin 5 - put to GND * LCD Enable pin 6 to digital pin 8 * LCD D4 pin 11 to digital pin 9 * LCD D5 pin 12 to digital pin 10 * LCD D6 pin 13 to digital pin 11 * LCD D7 pin 14 to digital pin 12 * * LCD pin 1 = GND * LCD pin 2 = + 5v 80 mA * LCD pin 3 = contrast adjust), or adjust between 0 ... 5v by trimmer * * */ #include /***********************************THIS IS WHERE YOU REALY TWEAK THE ANTENNA MOVEMENT***************/ // ANTENNA potentiometers CALIBRATION (counts) #define Az000 213 //begining of the potentiometer (000°) #define Az045 321 #define Az090 430 #define Az135 525 #define Az180 614 #define Az225 697 #define Az270 770 #define Az315 840 #define Az360 902 //end of the potentiometer // ************ ROTOR PARAMETERS ************************************************************ // Allowed error for which antennna won't move. Minimum 1 degree int AzErr = 15; // Angle difference where soft stop begins (for casting motor) int Amax_CW = 2; // clockwise / right int Amax_CCW = 0; // counter clockwise / right // max running time of rotor motor 1min15s long unsigned MotorRunTimeLimit = 75000 ; // max time motor can run if nu azimuth change detected (system stall) int StallTimer = 5000 ; // *********************************************************************************************** // other variables #define AzPotPin A0 // select the input pin for the azim. potentiometer #define AzRotPin_CCW 3 // select the out pin for rotation direction #define AzRotPin_CW 4 // select the out pin for rotation direction #define RS232pin 13 // RS-232 activity blinker #define BuzzerPin (6) // pin for buzzer bool SoftControl = false; // if controlled by software commands = true int TruAzim = 0; // calculated real azimuth value int ComAzim = 0; // commanded azimuth value int RepAzim = 0; // reported Azimuth to software int RawAzim = 0; // for interpolation int OldTruAzim = 0; // to store previous azimuth value int OldComAzim = 0; char AzDir; // symbol for azim rot display // flags for AZ tolerances bool AzStop = false; // not used !!!! bool rotate = false; // 'rotate command' active bool rotate_CW = true; // flag rotate CW //averaging loop const int numReadings = 5; // averages the reading int readIndex = 0; // the index of the current reading int azimuth[numReadings]; // the readings from the analog input int totalAz = 0; // the running total long unsigned LastDispUpdate; // display update frequency long unsigned LastBuzzer; // Buzzer sounding long unsigned AzimChangeTime; // change of true azim detected long unsigned MotorStartTime; // start of motor long unsigned MotorStopTime; // stop of motor bool MotorErr = false; bool OFF = false; // when rotor command box is OFF = true // variables for serial comm String Azimuth = ""; String Elevation = ""; String ComputerRead; String ComputerWrite; long unsigned LastSerExch; long unsigned StopCmdTime; bool StopPrinted = false; // build LCD specific characters 'degree' byte degree [8] = { B00100, B01010, B00100, B00000, B00000, B00000, B00000, B00000, }; // build LCD specific characters 'right' byte right [8] = { 0b01000, 0b01100, 0b01110, 0b11111, 0b11111, 0b01110, 0b01100, 0b01000 }; // build LCD specific characters 'left' byte left [8] = { 0b00010, 0b00110, 0b01110, 0b11111, 0b11111, 0b01110, 0b00110, 0b00010 }; //////////////////////////////////////////////////// //////////////////// S E T U P /////////////////// //////////////////////////////////////////////////// void setup() { Serial.begin(9600); Serial.setTimeout(50); // sets the maximum milliseconds to wait for serial data. It defaults to 1000 milliseconds // too long for PST rotator updates ... // FOR PARALLEL LCD // // Initiate the LCD: 8 char x 2 rows lcd.begin(8,2); // /* Initiate the I2C LCD: lcd.init(); lcd.backlight(); */ lcd.createChar(1, degree); lcd.createChar(2, right); lcd.createChar(3, left); // pin declaration digitalWrite(AzRotPin_CCW, LOW); // deactivate rotation pin left digitalWrite(AzRotPin_CW, LOW); // deactivate rotation pin right digitalWrite(RS232pin, LOW); // RS232pin pinMode(AzRotPin_CCW, OUTPUT); //declaring azim. rotation direction Pin as OUTPUT pinMode(AzRotPin_CW, OUTPUT); //declaring azim. rotation direction Pin as OUTPUT pinMode(RS232pin, OUTPUT); pinMode(AzPotPin, INPUT); pinMode(BuzzerPin, OUTPUT); // write on display name and version lcd.clear(); lcd.setCursor(0, 0); // Set the cursor on the first column first row.(counting starts at 0!) lcd.print("RotorCTL"); lcd.setCursor(0, 1); // Set the cursor on the first column the second row lcd.print("V3 05-22"); /* tone(BuzzerPin,2100); delay (250); tone(BuzzerPin,2300); delay (250); */ tone(BuzzerPin,2400,250); // Frequency, duration //delay (250); //noTone (BuzzerPin); delay(1500); // keep for 1.5 seconds lcd.clear(); lcd.setCursor(0, 0); // Set the cursor on the first column first row.(counting starts at 0!) lcd.print(" by "); lcd.setCursor(0, 1); // Set the cursor on the first column the second row lcd.print(" ON7EQ"); delay(1500); // keep for 1.5 seconds lcd.clear(); lcd.setCursor(0, 0); // Set the cursor on the first column first row.(counting starts at 0!) lcd.print(" YAESU"); lcd.setCursor(0, 1); // Set the cursor on the first column the second row lcd.print(" 9k6"); delay(1500); // keep for 1.5 seconds lcd.clear(); // display Azim. value lcd.setCursor(0, 0); lcd.print("Az --- "); lcd.setCursor(2, 0); lcd.write(1); lcd.setCursor(0, 1); lcd.print("Ctl --- "); // this is to set azim-command the same value as real, not to jerk the antenna at start-up RawAzim = analogRead(AzPotPin); if ( (RawAzim <= Az045)) TruAzim = (map(RawAzim, Az000, Az045, 0, 45)); if ((RawAzim > Az045) and (RawAzim <= Az090)) TruAzim = (map(RawAzim, Az045, Az090, 45, 90)); if ((RawAzim > Az090) and (RawAzim <= Az135)) TruAzim = (map(RawAzim, Az090, Az135, 90, 135)); if ((RawAzim > Az135) and (RawAzim <= Az180)) TruAzim = (map(RawAzim, Az135, Az180, 135, 180)); if ((RawAzim > Az180) and (RawAzim <= Az225)) TruAzim = (map(RawAzim, Az180, Az225, 180, 225)); if ((RawAzim > Az225) and (RawAzim <= Az270)) TruAzim = (map(RawAzim, Az225, Az270, 225, 270)); if ((RawAzim > Az270) and (RawAzim <= Az315)) TruAzim = (map(RawAzim, Az270, Az315, 270, 315)); if ((RawAzim > Az315) ) TruAzim = (map(RawAzim, Az315, Az360, 315, 359)); //TruAzim = (map(analogRead(AzPotPin), AzMin, AzMax, 0, 359)); // azimuth value 0-359 , for linear pot if (TruAzim<0) {TruAzim=0;} if (TruAzim>359) {TruAzim=359;} // keep values between limits // initialize all the readings for (int thisReading = 0; thisReading < numReadings; thisReading++) { azimuth[thisReading] = 0; } ComAzim = TruAzim; OldTruAzim = TruAzim; OldComAzim = ComAzim; DisplTruAzim(); DisplComAzim(); AzimChangeTime = millis (); LastBuzzer = millis(); } //////////////////////////////////////////////////// ////////////////////// L O O P ///////////////////// //////////////////////////////////////////////////// void loop() { // Sound buzzer for motor error if ((millis() - LastBuzzer > 2000) and (MotorErr == true)) { tone(BuzzerPin,2400,500); // Frequency, duration //delay (500); //noTone (BuzzerPin); LastBuzzer = millis(); } // AZIMUTH AVERAGING LOOP totalAz = totalAz - azimuth[readIndex]; // read from the sensor: RawAzim = analogRead(AzPotPin); if (RawAzim < 150 ) { // detect if CONTROLLER is powered OFF = true; } else OFF = false; if ( (RawAzim <= Az045)) azimuth[readIndex] = (map(RawAzim, Az000, Az045, 0, 45)); if ((RawAzim > Az045) and (RawAzim <= Az090)) azimuth[readIndex] = (map(RawAzim, Az045, Az090, 45, 90)); if ((RawAzim > Az090) and (RawAzim <= Az135)) azimuth[readIndex] = (map(RawAzim, Az090, Az135, 90, 135)); if ((RawAzim > Az135) and (RawAzim <= Az180)) azimuth[readIndex] = (map(RawAzim, Az135, Az180, 135, 180)); if ((RawAzim > Az180) and (RawAzim <= Az225)) azimuth[readIndex] = (map(RawAzim, Az180, Az225, 180, 225)); if ((RawAzim > Az225) and (RawAzim <= Az270)) azimuth[readIndex] = (map(RawAzim, Az225, Az270, 225, 270)); if ((RawAzim > Az270) and (RawAzim <= Az315)) azimuth[readIndex] = (map(RawAzim, Az270, Az315, 270, 315)); if ((RawAzim > Az315) ) azimuth[readIndex] = (map(RawAzim, Az315, Az360, 315, 359)); //azimuth[readIndex] = (map(analogRead(AzPotPin), Az000, Az360, 0, 359)); /// For linear pot // 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, wrap around to the beginning: if (readIndex >= numReadings) {readIndex = 0;} // calculate the average: TruAzim = totalAz / numReadings; if (TruAzim<0) {TruAzim=0;} if (TruAzim>359) {TruAzim=359;} // keep values between limits // update antenna true position display if ((millis()- LastDispUpdate) > 100){ //not to flicker the display LastDispUpdate = millis(); /* if (RawAzim < 150 ) { lcd.setCursor(4, 0); lcd.print ("OFF"); OFF = true; } */ if (OFF == true) { lcd.setCursor(4, 0); lcd.print ("OFF"); } else if (abs(OldTruAzim - TruAzim)>1 ) { // eliminate last digit jitter in display and PC software AzimChangeTime = millis(); //reset azimuth change timer DisplTruAzim(); RepAzim = TruAzim; } } if ( (millis() - AzimChangeTime > StallTimer) and (rotate == true)) { // Check for motor stall MotorErr = true; //No AZ change during > stalltimer while rotate command active --> ERROR ! digitalWrite(AzRotPin_CCW, LOW); // deactivate rotation pin left digitalWrite(AzRotPin_CW, LOW); // deactivate rotation pin right lcd.setCursor(3, 0); lcd.print(" "); lcd.setCursor(7, 0); lcd.print(" "); lcd.setCursor(4, 1); lcd.print("ERR"); } // every 0,1 seconds looking for serial communication if ((millis()- LastSerExch) > 100){ digitalWrite(RS232pin, LOW); LastSerExch = millis(); if (Serial.available() > 0) { digitalWrite(RS232pin, HIGH); // blink LED SerComm(); } } // update command target position display if (ComAzim != OldComAzim) { SoftControl = true; // we have received a command from software AzimChangeTime = millis () ; // reset AZ change timer, keep before rotating antenna DisplComAzim(); } // this is to rotate in azimuth if (TruAzim == ComAzim) { // if equal, stop moving digitalWrite(AzRotPin_CCW, LOW); // deactivate rotation pin left digitalWrite(AzRotPin_CW, LOW); // deactivate rotation pin right rotate = false; // AzMotor stopped SoftControl = false; // back to manual mode lcd.setCursor(3, 0); lcd.print(" "); lcd.setCursor(7, 0); lcd.print(" "); lcd.setCursor(4, 1); if (MotorErr == false) { lcd.print(" - "); } } /* else if ((abs(ComAzim-TruAzim) <= Amax)) { // uitloop motor, STOP ! digitalWrite(AzRotPin_CCW, LOW); // deactivate rotation pin left digitalWrite(AzRotPin_CW, LOW); // deactivate rotation pin right rotate = false; // AzMotor stopped SoftControl = false ; // back to manual mode lcd.setCursor(3, 0); lcd.print(" "); lcd.setCursor(7, 0); lcd.print(" "); } */ else if ((abs(ComAzim-TruAzim) <= Amax_CW) and (rotate_CW == true)) { // uitloop motor, STOP ! digitalWrite(AzRotPin_CCW, LOW); // deactivate rotation pin left digitalWrite(AzRotPin_CW, LOW); // deactivate rotation pin right rotate = false; // AzMotor stopped SoftControl = false ; // back to manual mode lcd.setCursor(3, 0); lcd.print(" "); lcd.setCursor(7, 0); lcd.print(" "); lcd.setCursor(4, 1); if (MotorErr == false) { lcd.print(" - "); } } else if ((abs(ComAzim-TruAzim) <= Amax_CCW) and (rotate_CW == false)) { // uitloop motor, STOP ! digitalWrite(AzRotPin_CCW, LOW); // deactivate rotation pin left digitalWrite(AzRotPin_CW, LOW); // deactivate rotation pin right rotate = false; // AzMotor stopped SoftControl = false ; // back to manual mode lcd.setCursor(3, 0); lcd.print(" "); lcd.setCursor(7, 0); lcd.print(" "); lcd.setCursor(4, 1); if (MotorErr == false) { lcd.print(" - "); } } else if ((abs(TruAzim - ComAzim)<=AzErr)&&(rotate == true) && (SoftControl == true)) { // if in tolerance, but it wasn't an equal, rotate if (MotorErr == false) { AzimRotate(); } else { // MotorErr = true ! digitalWrite(AzRotPin_CCW, LOW); // we have motor error digitalWrite(AzRotPin_CW, LOW); // } } else if ((abs(TruAzim - ComAzim)>AzErr) && (SoftControl == true)){ // if target is off tolerance if (MotorErr == false) { AzimRotate(); // rotate } else { // MotorErr = true ! digitalWrite(AzRotPin_CCW, LOW); // we have motor error digitalWrite(AzRotPin_CW, LOW); // } } //// Clear STOP in display if ((millis()- StopCmdTime > 3000) and (StopPrinted == true)) { lcd.setCursor(4, 1); lcd.print(" - "); StopPrinted = false; } // delay(20); //pause the program for x ms } ///////////////////////////////////////////////////// ////////////// procedures definitions ////////////// ///////////////////////////////////////////////////// //////// Display actual rotor azimuth /////////// 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);} OldTruAzim = TruAzim; // ************** FOR CALIBRATION PURPOSES ************** /* lcd.setCursor(0, 1); lcd.print (analogRead(AzPotPin)); lcd.print (" ") ; */ } /////// Display command azimuth ////////////// void DisplComAzim(){ lcd.setCursor(4, 1); 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))); // degrees OldComAzim = ComAzim; // } ////////// Rotate antenna ////////////////////// void AzimRotate() { if (rotate == false ) MotorStartTime = millis(); if ((ComAzim-TruAzim) > (TruAzim-ComAzim)) { // this to determine direction of rotation // ROTATE RIGHT - CW if ((rotate == false ) and (MotorErr == false)) MotorStartTime = millis(); digitalWrite(AzRotPin_CCW, LOW); // deactivate rotation pin left if ((rotate_CW == false) and (rotate == true)) delay (500); // allow time to treverse motor, if rotation inversed if (millis() - MotorStartTime < MotorRunTimeLimit) { if (MotorErr == false ) digitalWrite(AzRotPin_CW, HIGH); // rotate right rotate = true; rotate_CW = true; AzimChangeTime = millis () ; // reset AZ change timer at start of rotation AzDir = char(126); } // "->" else { digitalWrite(AzRotPin_CW, LOW); // stop if running too long lcd.setCursor(3, 0); lcd.print(" "); lcd.setCursor(7, 0); lcd.print(" "); lcd.setCursor(4, 1); lcd.print("ERR"); MotorErr = true; } } else { // ROTATE LEFT - CCW if ((rotate == false ) and (MotorErr == false)) MotorStartTime = millis(); digitalWrite(AzRotPin_CW, LOW); // deactivate rotation pin right if ((rotate_CW == true) and (rotate == true)) delay (500); // allow time to treverse motor, if rotation inversed if (millis() - MotorStartTime < MotorRunTimeLimit) { if (MotorErr == false ) digitalWrite(AzRotPin_CCW, HIGH); // rotate left rotate = true; rotate_CW = false; AzimChangeTime = millis () ; // reset AZ change timer at start of rotation AzDir = char(127); } // "<-" else { digitalWrite(AzRotPin_CCW, LOW); // stop if running too long lcd.setCursor(3, 0); lcd.print(" "); lcd.setCursor(7, 0); lcd.print(" "); lcd.setCursor(4, 1); lcd.print("ERR"); MotorErr = true; } } // Print direction arrows if (AzDir == char(126)) { //CW or turning right lcd.setCursor(3, 0); lcd.print(" "); lcd.setCursor(7, 0); //lcd.print(String(AzDir)); lcd.write(2); } if (AzDir == char(127)) { //CCW or turning left lcd.setCursor(7, 0); lcd.print(" "); lcd.setCursor(3, 0); //lcd.print(String(AzDir)); lcd.write(3); } } ///////// Handle serial communication ///////////// void SerComm() { // initialize readings ComputerRead = ""; Azimuth = ""; while(Serial.available()) { ComputerRead= Serial.readString(); // read the incoming data as string (in this case, default timeout = 1 sec unless declared) // Serial.println(ComputerRead); // echo the reception for testing purposes } // looking for command : YAESU style : Mxxx = Move to azimuth XXX for (int i = 0; i <= ComputerRead.length(); i++) { if ((ComputerRead.charAt(i) == 'M') and (MotorErr == 0)){ // if read AZIMUTH command for (int j = i+1; j <= ComputerRead.length(); j++) { if (isDigit(ComputerRead.charAt(j))) { // if the character is number Azimuth = Azimuth + ComputerRead.charAt(j); } else {break;} } } if (ComputerRead.charAt(i) == 'S'){ // if read STOP command SoftControl = false; // halt software control rotate = false; // interrupts rotate command digitalWrite(AzRotPin_CCW, LOW); // deactivate rotation pin left digitalWrite(AzRotPin_CW, LOW); // deactivate rotation pin right MotorErr = false; // manual reset of motor error condition lcd.setCursor(3, 0); lcd.print(" "); lcd.setCursor(7, 0); lcd.print(" "); lcd.setCursor(4, 1); lcd.print("STP"); StopPrinted = true; StopCmdTime = millis(); tone(BuzzerPin,2400,150); // Frequency, duration } } // if received if (Azimuth != ""){ ComAzim = Azimuth.toInt(); ComAzim = (ComAzim+360)%360; // keeping values between limits if (OFF == false) { tone(BuzzerPin,2400,30); // Frequency, duration } else { tone(BuzzerPin,2400,30); // Frequency, duration delay (80); tone(BuzzerPin,2400,30); // Frequency, duration delay (80); tone(BuzzerPin,2400,30); // Frequency, duration } } // looking for interogation for antenna position / YAESU protocol = 'C' reply is +0xxx xxx = azimuth for (int i = 0; i <= (ComputerRead.length()); i++) { if ((ComputerRead.charAt(i) == 'C') and (millis() > 6000) ){ /// give some time to average position, so PstRotataor indicates set AZ as actual AZ // send back the antenna position <+0xxx> ComputerWrite = "+0"+String(RepAzim); Serial.println(ComputerWrite); } } }