/* Generic sketch version with PLEIADES-4 boards or the last version of Skypikit shield boards, using ARDUINO UNO Rev. 3 and for an EQUATORIAL mount. You can start with this version and modify the settings according to your telescope specs (motors, reductions...) by making tests with the SKYPIKIT MOTOR TESTER TUNER application. The sketch has two main purposes: 1- Communicate with a computer by USB to receive the computer commands and return the status of the controllers. 2- Read the controller mini-switches and the handbox buttons to be able to control the tracking speed and telescope displacements in standalone mode without the need of a computer. Version 2021-10-27 By Jean Vallieres */ #include // for I2C communication #include // for the strings // ############################################################################################################################ // CONSTANTS and GLOBAL VARIABLES // ############################################################################################################################ String inputStringUSB = ""; // a string to hold incoming data from the computer boolean stringCompleteUSB = false; // whether the input string (from the computer) is complete int ttUSB = 1; // =1 to print characters to USB serial port int len, i; char i2c_address; // real I2C address of the SKYPIKIT controller int RA_i2c = 65; // right ascension I2C address int DE_i2c = 66; // declination I2C address int FC_i2c = 68; // focuser I2C address // initial values for standalone operation (without using a computer or a tablet) ..................................... // values are for initial tests; must be changed according to telescope specs; // USER MAY CALCULATE THOSE VALUES WITH THE SKYPIKIT MOTOR TESTER TUNER APPLICATION AND MODIFY THOSE VALUES AFTER MAKING TESTS ON THE SKY .... // Try to keep sidereal speed between 40 and 120 micro-steps/second // Try to keep max goto speed below 40000 micro-steps/sec for stepper motors. float RAsiderealSpeed = 106.9587; // in micro-steps/sec according to the mount properties ( RA gears, reductors, encoder, microsteps...) float RAmaxSlewSpeed = 25600; // maximum right ascension slew speed for simple handpad potentiometer (1°/s) float RAacceleration = 25600; // RA acceleration : 0 to max speed in 1 sec float RAfinalApproach = -1000; // final appoach in RA is -1000 steps (towards west, same direction as tracking) float RAoffsetSolarTrackSpeed = RAsiderealSpeed * (+0.00273); // offset tracking speed for solar tracking float RAoffsetLunarTrackSpeed = RAsiderealSpeed * (+0.03660); // offset tracking speed for lunar tracking float DEsiderealSpeed = 106.9587; // in micro-steps/sec according to the mount properties ( DE gears, reductors, encoder, microsteps...) float DEmaxSlewSpeed = 25600; // maximum declination slew speed for simple handpad potentiometer (1°/s) float DEacceleration = 25600; // DE acceleration : 0 to max speed in 1 sec float DEfinalApproach = 1000; // final approach in Decl. is +1000 steps (towards north) float FCsiderealSpeed = 20.0000; // in micro-steps/sec according to the focuser properties ( example values are for a MoonLite focuser) float FCmaxSlewSpeed = 550; // maximum focuser slew speed for simple handpad potentiometer (= 972 µstep/mm for a MoonLite focuser) float FCacceleration = 720; // FC acceleration float FCfinalApproach = 0; // no final approach int invertRA = 0; // invert direction for RA: 0 = no invert, 8 = invert int invertDE = 0; // invert direction for DE: 0 = no invert, 8 = invert int invertFC = 0; // invert direction for FC: 0 = no invert, 8 = invert int trackingMode = 0; // 0 = not tracking bool trackModeChanged = false; // if trackingMode has been changed with the mini switches String trackCommand = ""; // the selected track command String RAsiderealCommand = String(round(RAsiderealSpeed * 1000.000)); // 1000 times the sidereal tracking speed String DEsiderealCommand = String(round(DEsiderealSpeed * 1000.000)); // 1000 times the sidereal tracking speed String FCsiderealCommand = String(round(FCsiderealSpeed * 1000.000)); // 1000 times the sidereal tracking speed String RAoffsetSolarCommand = String(round(RAoffsetSolarTrackSpeed * 1000)); // 1000 times the offset solar speed String RAoffsetLunarCommand = String(round(RAoffsetLunarTrackSpeed * 1000)); // 1000 times the offset lunar speed String RAfinalAppCommand = String(round(RAfinalApproach)); // final appoach in RA (negative = same direction as tracking) String DEfinalAppCommand = String(round(DEfinalApproach)); // final approach is 0 or your choice in Decl. String FCfinalAppCommand = String(round(FCfinalApproach)); // final approach is 0 String RAaccelCommand = String(round(RAacceleration)); String DEaccelCommand = String(round(DEacceleration)); String FCaccelCommand = String(round(FCacceleration)); String RAslewCommand = "&S"; // the slew command when a RA button is pushed (default to Stop) String DEslewCommand = "&S"; // the slew command when a DE button is pushed (default to Stop) String FCslewCommand = "&S"; // the slew command when a focuser button is pushed (default to Stop) int analogValue = 0; // analog value read from the simple handbox potentiometer on analog pin 0 int RAselect = 0; int RAselectPrev = 0; int DEselect = 0; int DEselectPrev = 0; int FCselect = 0; int FCselectPrev = 0; int sw1, sw2, sw3; // virtual tracking switches int sumTrackSw=0, sumTrackSwPrev=0; // sum of switches values to detect if changed // ############################################################################################################################ // SETUP // ############################################################################################################################ void setup() { // USB and I2C initializations *********************************************************************************** Serial.begin(57600); // start serial communication at 57600 bps for the computer connected on USB port while (!Serial) { ; } // wait for serial port to connect. Needed for Leonardo only inputStringUSB.reserve(64); // reserve 64 bytes for the inputString: Wire.begin(); // join i2c bus (address is optional for master) Wire.setClock(100000); // set Arduino Uno I2C to normal speed: 100 kHz ************************************************************ // digital pins settings **************************************************************************************************** // input pins must use internal pullup resistors because there are no pullups on the board, all inputs active at LOW pinMode(12, INPUT_PULLUP); // Tracking ON when LOW pinMode(8, INPUT_PULLUP); // LUNAR track speed if LOW pinMode(7, INPUT_PULLUP); // SOLAR track speed if LOW; pinMode(4, INPUT_PULLUP); // RA+ slew RA+ if LOW; pinMode(11, INPUT_PULLUP); // RA- slew RA- if LOW; pinMode(5, INPUT_PULLUP); // DE+ slew DE+ if LOW; pinMode(10, INPUT_PULLUP); // DE- slew DE- if LOW; pinMode(6, INPUT_PULLUP); // Focus+ move OUT if LOW; pinMode(9, INPUT_PULLUP); // Focus- move IN if LOW; delay(2000); // wait for the SKYPIKITs to stabilize after power-on or reset // INITIAL SETUPS FOR STANDALONE OPERATION WITH THE SIMPLE HANDBOX WITHOUT COMPUTER ***************************************** int RA_polarityBits = B00000100 + invertRA; // 000, free at stop: NO, invert dir: YES, enable votlage: 5V, all limit switches: normally open or not used transmitToDeviceAll("&y" + String(RA_polarityBits), RA_i2c, 60); int DE_polarityBits = B00000100 + invertDE; // 000, free at stop: NO, invert dir: YES, enable votlage: 5V, all limit switches: normally open or not used transmitToDeviceAll("&y" + String(DE_polarityBits), DE_i2c, 60); // Uncomment the following 2 lines if there is a focuser control /* int FC_polarityBits = B00000100 + invertFC; // 000, free at stop: NO, invert dir: YES, enable votlage: 5V, all limit switches: normally open or not used transmitToDeviceAll("&y" + String(FC_polarityBits), FC_i2c, 60); */ // UNCOMMENT THOSE LINES FOR EACH MOTOR THAT IS SERVO DC WITH ENCODER FOR Proportional-Integral CONTROL // AND MODIFY THOSE VALUES USING THE SKYPIKIT MOTOR TESTER TUNER APPLICATION (see documentation) /* transmitToDeviceAll("&p7", RA_i2c, 60); // RA proportional gain = 7 transmitToDeviceAll("&i40", RA_i2c, 60); // RA integral gain = 40 transmitToDeviceAll("&e10000", RA_i2c, 60); // RA error limit = 10000 transmitToDeviceAll("&j10000", RA_i2c, 60); // RA integral limit = 10000 transmitToDeviceAll("&k10000", RA_i2c, 60); // RA integral max speed = 10000 transmitToDeviceAll("&p7", DE_i2c, 60); // DE proportional gain = 7 transmitToDeviceAll("&i40", DE_i2c, 60); // DE integral gain = 40 transmitToDeviceAll("&e10000", DE_i2c, 60); // DE error limit = 10000 transmitToDeviceAll("&j10000", DE_i2c, 60); // DE integral limit = 10000 transmitToDeviceAll("&k10000", DE_i2c, 60); // DE integral max speed = 10000 */ // Uncomment the focuser lines if there is a focuser transmitToDeviceAll("&a" + RAaccelCommand, RA_i2c, 60); // RA acceleration : 0 to max speed in 1 sec) transmitToDeviceAll("&a" + DEaccelCommand, DE_i2c, 60); // DE acceleration : 0 to max speed in 1 sec) //transmitToDeviceAll("&a" + FCaccelCommand, FC_i2c, 60); // FC acceleration : 0 to max speed in 1 sec) transmitToDeviceAll("&f" + RAfinalAppCommand, RA_i2c, 60); // RA final approach in steps transmitToDeviceAll("&f" + DEfinalAppCommand, DE_i2c, 60); // DE final approach in steps //transmitToDeviceAll("&f" + FCfinalAppCommand, FC_i2c, 60); // FC final approach in steps transmitToDeviceAll("&r" + RAsiderealCommand, RA_i2c, 60); // set RA sidereal speed transmitToDeviceAll("&r" + DEsiderealCommand, DE_i2c, 60); // set DE sidereal speed //transmitToDeviceAll("&r" + FCsiderealCommand, FC_i2c, 60); // set FC sidereal speed transmitToDeviceAll("&t0", RA_i2c, 60); // RA offset tracking speed = 0 transmitToDeviceAll("&t0", DE_i2c, 60); // DE offset tracking speed = 0 //transmitToDeviceAll("&t0", FC_i2c, 60); // FC offset tracking speed = 0 transmitToDeviceAll("&g" + RAsiderealCommand, RA_i2c, 60); // RA guiding speed = 100% RA sidereal speed transmitToDeviceAll("&g" + DEsiderealCommand, DE_i2c, 60); // DE guiding speed = 100% DE sidereal speed //transmitToDeviceAll("&g" + FCsiderealCommand, FC_i2c, 60); // FC guiding speed = 100% FC sidereal speed but never used transmitToDeviceAll("&C1", RA_i2c, 60); // connect RA motor in sidereal reference system transmitToDeviceAll("&C0", DE_i2c, 60); // connect DE motor in fixed reference system //transmitToDeviceAll("&C0", FC_i2c, 60); // connect focuser motor in fixed reference system } // END OF SETUP // ############################################################################################################################ // MAIN LOOP // ############################################################################################################################ void loop() { // ************************************************************************************************************************** // READING THE TRACKING MINI-SWITCHES ON THE ALCYONE-4 BOARD FOR STANDALONE OPERATION // OFF-ON SW1 : untrack-track // ON-OFF-ON SW2 : solar-sidereal-lunar // ************************************************************************************************************************** if (digitalRead(12)==LOW) { sw1 = 1;} else {sw1 = 0;} // Track switch (SW1) if (digitalRead(8)==LOW) { sw2 = 2;} else {sw2 = 0;} // LUNAR track speed if LOW (SW2) if (digitalRead(7)==LOW) { sw3 = 4;} else {sw3 = 0;} // SOLAR track speed if LOW; (SW2) sumTrackSw = sw1 + sw2 + sw3; if (sumTrackSw != sumTrackSwPrev) // if tracking switches changed { switch (sumTrackSw) { case 0 : trackCommand = "&N"; break; // untrack case 1 : trackCommand = "&t0"; break; // track sidereal case 2 : trackCommand = "&N"; break; // untrack case 3 : trackCommand = "&t" + RAoffsetLunarCommand; break; // track lunar case 4 : trackCommand = "&N"; break; // untrack case 5 : trackCommand = "&t" + RAoffsetSolarCommand; break; // track solar default: trackCommand = "&N"; break; // untrack } transmitToDeviceAll(trackCommand, RA_i2c, 60); // track or untrack in right ascension if (trackCommand == "&N") { transmitToDeviceAll("&N", DE_i2c, 60); } // untrack in declination else { transmitToDeviceAll("&T", RA_i2c, 60); // begin tracking in R.A. transmitToDeviceAll("&t0", DE_i2c, 60); transmitToDeviceAll("&T", DE_i2c, 60); // track at speed zero in declination (to enable guiding) } } sumTrackSwPrev = sumTrackSw; // for the next iteration // ************************************************************************************************************************** // READING THE SIMPLE HANDBOX MOVE BUTTONS (add focuser buttons and focuser section if there is a focuser) // ************************************************************************************************************************** // DETECT AND EXECUTE CHANGES ON SIMPLE HANDBOX RIGHT ASCENSION BUTTONS ***************************************************** if (digitalRead(4)==LOW) { RAselect = 1; } // RA+ : east RA button pressed else if (digitalRead(11)==LOW) { RAselect = 2; } // RA- : west RA button pressed else { RAselect = 0; } // no RA button pressed if (RAselect != RAselectPrev) { switch (RAselect) { case 0 : RAslewCommand = "&S"; break; // stop case 1 : RAslewCommand = "&M+" + handpadSpeedValue(RAmaxSlewSpeed); break; // slew east (increasing RA) case 2 : RAslewCommand = "&M-" + handpadSpeedValue(RAmaxSlewSpeed); break; // slew west default : RAslewCommand = "&S"; break; // stop } transmitToDeviceAll(RAslewCommand, RA_i2c, 60); // transmits the command to the RA device } RAselectPrev = RAselect; // for the next iteration // DETECT AND EXECUTE CHANGES ON SIMPLE HANDBOX DECLINATION BUTTONS ********************************************************* if (digitalRead(5)==LOW) { DEselect = 1; } // DE+ : north DE button pressed else if (digitalRead(10)==LOW) { DEselect = 2; } // DE- : south DE button pressed else { DEselect = 0; } // no DE button pressed if (DEselect != DEselectPrev) { switch (DEselect) { case 0 : DEslewCommand = "&S"; break; // stop case 1 : DEslewCommand = "&M+" + handpadSpeedValue(DEmaxSlewSpeed); break; // slew north case 2 : DEslewCommand = "&M-" + handpadSpeedValue(DEmaxSlewSpeed); break; // slew south default : DEslewCommand = "&S"; break; // stop } transmitToDeviceAll(DEslewCommand, DE_i2c, 60); // transmits the command to the DE device } DEselectPrev = DEselect; // for the next iteration // DETECT AND EXECUTE CHANGES ON SIMPLE HANDBOX FOCUSER BUTTONS (uncomment if there is a focuser control) ******************** /* if (digitalRead(6)==LOW) { FCselect = 1; } // FC+ : focuser OUT button pressed else if (digitalRead(9)==LOW) { FCselect = 2; } // FC- : focuser IN button pressed else { FCselect = 0; } // no focuser button pressed if (FCselect != FCselectPrev) { switch (FCselect) { case 0 : FCslewCommand = "&S"; break; // stop case 1 : FCslewCommand = "&M+" + handpadSpeedValue(FCmaxSlewSpeed); break; // slew up case 2 : FCslewCommand = "&M-" + handpadSpeedValue(FCmaxSlewSpeed); break; // slew down default : FCslewCommand = "&S"; break; // stop } transmitToDeviceAll(FCslewCommand, FC_i2c, 60); // transmits the command to the AZ device } FCselectPrev = FCselect; // for the next iteration */ // ************************************************************************************************************************** // READING COMMANDS FROM COMPUTER AND SENDING CONTROLLER STATUS TO COMPUTER BY USB // ************************************************************************************************************************** while ((Serial.available())&&(!stringCompleteUSB)) { char inChar = (char)Serial.read(); // get the new byte inputStringUSB += inChar; // add it to the inputStringUSB if ((inChar == ':')||(inChar == '#')) { stringCompleteUSB = true; } // first and last char for SKYPIKIT protocol commands } if (stringCompleteUSB) // send the command when stringCompleteUSB { i2c_address = inputStringUSB[0]; // first char of string gives real SKYPIKIT I2C address (value 64 to 79) if (inputStringUSB[0] == '@') // this is a test command (@&Q0) at address 64+0 { Serial.print('O'); Serial.print('K'); Serial.print('#'); // print "OK" } else if ((inputStringUSB[1] == '&')&&(inputStringUSB[2] == 'Q')) // this is a request command (&Q0 or &Q1) { transmitToDeviceSkipFirst(inputStringUSB, i2c_address, 60); // send the command to the SKYPIKIT at i2c_address, then wait 60ms Wire.requestFrom(i2c_address, 36); // request the status string from the SKYPIKIT I2C slave device, maximum of 36 bytes while (Wire.available()) // as long as there are characters sent from the SKYPIKIT { char c = Wire.read(); // receive one character from the SKYPIKIT if (ttUSB == 1) { if (c == '\0') { Serial.print('#'); } // for compatibilty with Skypikit ASCOM driver end of line char else { Serial.print(c); } // else send this character to the computer on USB port } if (c == '\0') {ttUSB = 0;} // detect the last NULL character and do not send the next characters } } else // this is not a request command { transmitToDeviceSkipFirst(inputStringUSB, i2c_address, 0); // send the command to the SKYPIKIT at i2c_address } inputStringUSB = ""; // clear the string: stringCompleteUSB = false; ttUSB = 1; } } // END OF MAIN LOOP // ############################################################################################################################ // FUNCTIONS // ############################################################################################################################ // **************************************************************************************************************************** // Transmit the command string st to the SKYPIKIT device at address i2c_addr, end with a delay in ms // Skip the first Character (normally the address char) // **************************************************************************************************************************** void transmitToDeviceSkipFirst(String st, int i2c_addr, int afterdelay) { Wire.beginTransmission(i2c_addr); // default i2c_addr = 65 for RA, 66 for DE, 67 for focuser len = st.length(); for (i = 1; i < len; i++) { Wire.write(st[i]); } // skip the first char. of st and write the command string ( ex. "&G-4000") Wire.write(0); // write a NULL char at the end of the string Wire.endTransmission(); // stop transmitting delay(afterdelay); // delay in ms } // **************************************************************************************************************************** // Transmit all the command string st to the Skypikit device at address i2c_addr, end with a delay in ms // **************************************************************************************************************************** void transmitToDeviceAll(String st, int i2c_addr, int afterdelay) { Wire.beginTransmission(i2c_addr); // default i2c_addr = 65 for RA, 66 for DE, 67 for focuser len = st.length(); for (i = 0; i < len; i++) { Wire.write(st[i]); } // write the command string ( ex. "&G-4000", len = 7) Wire.write(0); // write a NUL char at the end of the string Wire.endTransmission(); // stop transmitting delay(afterdelay); // delay in ms } // **************************************************************************************************************************** // Return a string giving the digital speed value according to the device maximum slew speed // and to the handbox potentiometer value on analog pin 0 . // Since there is a 4.7K resistor in series with the 10K pot., the maximum pot. digital value is (10/14.7) * 1024 = 697. // **************************************************************************************************************************** String handpadSpeedValue(float maxSlewSpeed) { float potValue = analogRead(0); // simple handpad potentiometer value on pin A0 : 0 to 697 (10 bits) float slewSpeedValue; // slew speed value to send to the Skypikit slewSpeedValue = maxSlewSpeed * (potValue/697)*(potValue/697); return String(round(slewSpeedValue)); }