//https://www.qsl.net/on7eq/projects/arduino_rotor_controller.htm
// 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
// V14 : Random parking to avoid wear of pinions
// LCD 16x2 7=RS, 8=E, 9=D4, 10=D5, 11=D6, 12=D7 (New)
/* ***** 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 <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);
#include <SoftwareSerial.h>
/***********************************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 no azimuth change detected (system stall)
int StallTimer = 5000 ;
// For PstRotator : Random parking band definition - e.g. '20' means + and - 10° around park set point in PstRotator
int ParkBand = 20 ; // Set to zero to disable this function. Must be < than 2x AzErr !
// ***********************************************************************************************
// 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() {
pinMode(AzRotPin_CCW, OUTPUT);
pinMode(AzRotPin_CW, OUTPUT);
Serial.begin(9600);
Serial.println("Hello Arduino\n");
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(16,2);
//
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("V1 10-24");
/*
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(" 4L7ZS");
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(" FTDX10");
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;
ComAzim = 220;
OldTruAzim = TruAzim;
OldComAzim = ComAzim;
DisplTruAzim();
DisplComAzim();
AzimChangeTime = millis ();
LastBuzzer = millis();
randomSeed(analogRead(AzPotPin)); // generate random for parking
if (ParkBand > (2 * AzErr)) ParkBand = 2 * AzErr; // ParkBand can't be > than 2x AzErr !
}
////////////////////////////////////////////////////
////////////////////// 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 <AZxx> received
if (Azimuth != ""){
ComAzim = Azimuth.toInt();
// if PARK command issued from PSTrotator : First STOP, immediately followed by MOVE
if (millis() - StopCmdTime < 500) {
ComAzim = ComAzim - ParkBand/2 ; // park position within Park Band
ComAzim = ComAzim + random(0,ParkBand+1);
}
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 <AZ> 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);
}
}
}