Pengontrol Rotator Antena Profesional – Integrasi Mulus dengan Perangkat Lunak Pelacakan
Komponen dan Perlengkapan
Arduino UNO
Arduino Uno board
×
1
Potensiometer rotary (Umum)
max. 1Kohm (500 Ohms lebih baik)
×
2
Rotary Encoder dengan Tombol Tekan
×
2
Papan Rohot (Umum)
×
1
Modul Relay (Umum)
2 modul x 2 relay NO-Com-NC
×
2
Power MOSFET N-Channel
power mosfet module (min 12V/3A)
×
2
Aplikasi dan Layanan Online
Arduino IDE
Tentang Proyek Ini
Pembaruan terbaru November2021
Proyek ini dimulai sebagai hiburan dan menjadi serangkaian peralatan serius.
Pengontrol menerima posisi antena secara manual, melalui dua enkoder rotary, Azimuth dan Elevasi. Ia dapat melacak satelit secara otomatis, saat terhubung melalui USB ke PC yang menjalankan perangkat lunak pelacakan satelit.
Kompatibel dengan semua perangkat lunak pelacakan yang menggunakan protokol EasyComm2 / 9600 bauds. PstRotator, WXtrack, HRD, MacDoppler... Bahkan WXtoIMG dapat mengontrol rotator.
Ia bekerja langsung dengan Orbitron, dengan plugin DDE dari https://tripsintech.com/orbitron-dde-azimuth-elevation-to-serial/
Pengontrol mengeluarkan respons serial, untuk perangkat lunak pelacakan untuk menampilkan posisi antena yang sebenarnya di layar. Sampai saat ini, hanya PstRotator yang melakukannya.
Kode tidak menggunakan library apa pun (kecuali untuk LCD) dan berjalan persis seperti itu, dengan pin sesuai dengan diagram rangkaian di bawah ini. Jika Anda menekan tombol enkoder Azimuth, semua gerakan antena berhenti segera, dan perintah azimuth dapat diatur dalam langkah 10 derajat.
Anda akan menemukan dua versi di sini: Satu untuk motor DC, dan satu untuk motor AC (hanya relay). Yang terakhir dapat diintegrasikan dengan rotator antena komersial yang ada.
Versi motor DC memiliki keuntungan menggunakan PWM untuk gerakan antena yang lebih lembut/halus. Ia mengeluarkan respons daya yang proporsional dengan kesalahan sudut (Target <-> Antena). Oleh karena itu, saat antena mulai bergerak, ia mempercepat secara progresif, dan saat mendekati posisi yang diinginkan, ia melambat hingga berhenti sepenuhnya. Ini dikenal sebagai Soft-Start / Soft-Stop. Ada Dead Zone yang dapat disesuaikan, di mana antena tidak bergerak untuk offset target yang paling kecil.
Saya memiliki versi beta dengan Soft-Start / Soft-Stop untuk motor AC, memanfaatkan modul AC-Dimmer ini, tetapi saat ini hanya berfungsi untuk azimuth. Jika Anda ingin mencobanya, beri tahu saya melalui email.
Jika Anda memiliki sistem elevasi 180 derajat, Anda siap, beri tahu saya melalui email. Ada juga versi dengan presisi 0,1 derajat, tetapi saya tidak merekomendasikannya kecuali Anda memiliki pembacaan potensiometer yang sangat solid dan desain konstruksi pengontrol yang paranoidis. Anda akan menemukan lebih banyak versi di halaman web saya.
Setelah menyelesaikan konstruksi, Anda harus menerapkan prosedur kalibrasi.
Kalibrasi potensiometer adalah wajib dan memastikan pembacaan yang benar 0-359 derajat / 0-90 derajat, terlepas dari jenis potensiometer yang Anda gunakan.
Kalibrasi motor hanya untuk menyesuaikan fitur Soft-Start-Stop. Ini diperlukan jika Anda tidak suka pengaturan default.
Penjelasan lebih rinci dalam video. Karena kode telah ditingkatkan seiring waktu, dan video tidak dapat diperbarui lagi, periksa halaman web saya untuk informasi terbaru dan pengalaman pribadi hidup dengan pengontrol ini. https://racov.ro/index.php/2020/12/09/arduino-based-antenna-rotator-part3-software-tracking-update/
Berikan saya email jika Anda ingin tahu lebih banyak, karena platform ini tidak memberitahu saya tentang komentar baru, tidak tahu kenapa. Saya akan mencoba menyelesaikan masalah kecil sebaik yang bisa saya. YO3RAK@gmail.com
Terima kasih banyak kepada semua yang telah memberikan masukan, membantu membuat proyek ini lebih andal. Masukan apa pun sangat dihargai.
Kode
ant-rot-DC-nov2021
ant-rot-AC-aug2021
Prosedur kalibrasi potensiometer
Prosedur kalibrasi motor
ant-rot-DC-nov2021Arduino
Kode ini untuk motor DC, dengan output PWM soft start-stop
/* 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; /****************************************************************************/
Skematik
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.