diff --git a/Software/EQG2HBXWiFi/EQG2HBX.h b/Software/EQG2HBXWiFi/EQG2HBX.h new file mode 100644 index 0000000..ea16073 --- /dev/null +++ b/Software/EQG2HBXWiFi/EQG2HBX.h @@ -0,0 +1,225 @@ +/* + * Copyright 2017, 2018 John Archbold +*/ + +#include +/******************************************************** + EQG2HBX program definitions + =========================== + *********************************************************/ + +#ifndef EQG2HBX +#define EQG2HBX + +String EQ2HBX_Version = "EQG2HBX V1.05"; + +#ifdef ESP32 +#include "WiFi.h" +#include "AsyncUDP.h" + +#include +bool ota_started; +#endif + +// Time related libararies +#include + +#ifndef ESP32 +#include //https://github.com/PaulStoffregen/Time +#include //https://github.com/PaulStoffregen/TimeAlarms +#endif + +// Pin definitions for LED indicators +// ================================== +#ifdef mESP32 +#define SCOPELED 22 +#define EQGLED 21 +#endif + +// Jumpers to run test +// ============================== +#ifdef ESP32 +#define TESTHBX 13 // GPI35 +#endif + + +/************************************************************** + * Common variables + **************************************************************/ +Preferences preferences; +unsigned long DelayTimer; // Delay timer +unsigned long StatusTime; // H2X interval time + +unsigned long StatusTimer; // H2X status delay timer +unsigned long StateTimer; // H2X state delay timer +bool StateSelect; +bool StatusSelect; +int StatusCount; + +Ticker AlarmDebugPrint; +Ticker AlarmCheckETX; + +long P1; +long P2; +float F1; +float F2; + +time_t epoch; // UTC seconds +time_t currentTime; // Local value + +/************************************************************** + * EQG protocol communications buffers and pointers + * EQG protocol variables + **************************************************************/ + +#define EQGLEN 256 // Communications buffers +#define EQGMASK EQGLEN-1 // Index wraps to 0 + +unsigned long EQGP1; +unsigned long EQGP2; +float EQGF1; + +unsigned char EQGRxBuffer[EQGLEN]; // Hold data from EQG +unsigned char EQGTxBuffer[EQGLEN]; // Hold responses to EQG +unsigned char EQGRxiPtr = 0; // Pointer for input from EQG +unsigned char EQGRxoPtr = 0; // Pointer for output from EQG Rx buffer +unsigned char EQGTxiPtr = 0; // Pointer for input to EQG buffer +unsigned char EQGTxoPtr = 0; // Pointer for output to EQG + +unsigned char EQGCmnd = 0; // EQG Command +unsigned char EQGErrorValue; // EQG Returned error value +unsigned char EQGDone = 0; // EQG Complete message +unsigned char EQGRADEC = 0; // EQG Motor Select (ASCII) +unsigned char EQGMOTOR = 0; // EQG Motor Select (binary) +unsigned char EQGRAAutoguide = 0; // EQG Autoguide rate +unsigned char EQGDECAutoguide = 0; // EQG Autoguide rate +unsigned char EQGRxState = 1; // EQG State +unsigned char EQGRxChar; // EQG Rx Character +unsigned char EQGRxCount; // EQG # parameters + +#define dbgLEN 256 // Communications buffers +#define dbgMASK dbgLEN-1 // Index wraps to 0 +unsigned char dbgRxBuffer[dbgLEN]; // Hold data from EQG + char dbgCommand[dbgLEN]; // Hold data from EQG +unsigned char dbgRxiPtr = 0; // Pointer for input from EQG +unsigned char dbgRxoPtr = 0; // Pointer for output from EQG Rx buffer +unsigned char dbgFlag = 0; // Received a command +unsigned char dbgIndex = 0; // Index into command + float f; +unsigned long v; + +/************************************************************** + * HBX communications buffers and pointers + * HBX variables + **************************************************************/ + +unsigned long H2XStart = 0; // Used to count uS ticks +unsigned long H2XTimer = 0; // Used to count uS ticks +unsigned char EQGMotorStatus; // Current State of motor + +typedef struct { + unsigned char MotorType; // Current type of motor + unsigned char MotorFlag; // Flag to print motor positions + + unsigned long ETXMotorState; // ETX Motor State Nachine + unsigned long ETXMotorStatus; // Current ETX Motor Status + unsigned long EQGMotorStatus; // Current EQG Motor Status + unsigned long MotorControl; // Current HBX Motor Control bits + + unsigned char HBXBitCount; // #bits left to process + unsigned char Command; // Current command + unsigned char HBXData; // Data byte from HBX Bus + unsigned char HBXP1; // HBX status/data - MSB + unsigned char HBXP2; // HBX status/data - LSB + unsigned char HBXP3; // HBX status/data - PWM % related + unsigned char HBXP4; // HBX status/data - single flag bit related to battery alarm ( 0 = OK ) + unsigned char HBXCount; // HBX valid data count + unsigned char HBXLEDI; // LED current value from Motor + unsigned long DirnSpeed; // Speed, Direction for Motor to move + char HBXGuide; // Guide speed + char HBXSnapPort; // Snap port + char LEDValue; // Polar LED brightness + char ETXSpeedCommand; // Current ETX Speed command + long Speed; // Move speed + long TargetSpeed; // Target Move speed + char SpeedState; // Slowdown/speedup state + long Position; // Current position + long Target; // Current target delta + long Increment; // Change in position for motor speed calcs + long SlowDown; // Point to change to lower speed + long Offset; // Current adjustment + +// MeadeRatio = ((Vanes * 4) * GbxRatio * XferRatio * WormTeeth) / 1,296,000 + float MeadeRatio; // Meade Ratio + float GbxRatio; // GearBox Ratio + unsigned long Vanes; // Number of photocoupler vanes + float XferRatio; // Gearbox Transfer Ratio (usually 1) + unsigned long WormTeeth; // Number of Worm teeth + +// a-Value = (Vanes * 4) * GbxRatio * XferRatio * WormTeeth +// b-Value = (6460.09 * MeadeRatio * a-Value * 15.041069) / 1,296,000 + unsigned long aVALUE; // For rate calculations + unsigned long bVALUE; // For rate calculations + unsigned long OneDegree; // For slew comparisons + +// SIDEREALRATE = 6460.09 * MeadeRatio +// SOLARRATE = (SOLARSECS/SIDEREALSECS) * SIDEREALRATE +// LUNARRATE = (SOLARSECS/SIDEREALSECS) * SIDEREALRATE +// DEGREERATE1 = 240 * SIDEREALRATE + + unsigned long SIDEREALRATE; // Constants + unsigned long SOLARRATE; + unsigned long LUNARRATE; + unsigned long DEGREERATE1; + +// PEC = a-VALUE / WormTeeth; + unsigned long PEC; // PEC period (period of worm tooth) + + unsigned char PrintStatus0; // Force print of no status change + unsigned long TimeDelta; // Used in HBX Monitor +} axis_type; + +axis_type axis[4]; // Az, Alt + +// Support other scopes with Meade interface +typedef struct { + unsigned long Vanes; // Number of photocoupler vanes + float GbxRatio; // GearBox Ratio + float XferRatio; // Gearbox Transfer Ratio (usually 1) + unsigned long WormTeeth; // Number of Worm teeth + char Telescope[16]; // name of scope +} axis_values; + +unsigned char telescope = 0; // Default telescope (ETX60) +unsigned char protocol = 0; // Default protocol (UDP) +unsigned char station = 0; // Default station (AP) +char scope[16] = "ETX60"; + +axis_values ratio[16][2] = // 16 scopes, Az, Alt + { + {{36, 91.1458333, 1, 94, "ETX60"}, {36, 157.5, 1, 58, "ETX60"}}, // ETX60/70/80 + {{36, 91.1458333, 1, 94, "ETX70"}, {36, 157.5, 1, 58, "ETX70"}}, // ETX60/70/80 + {{36, 91.1458333, 1, 94, "ETX80"}, {36, 157.5, 1, 58, "ETX80"}}, // ETX60/70/80 + {{108, 50, 1, 144, "LXD55"}, {108, 50, 1, 144, "LXD55"}}, // LXD55/75, LX70-GTS + {{108, 50, 1, 144, "LXD75"}, {108, 50, 1, 144, "LXD75"}}, // LXD55/75, LX70-GTS + {{108, 50, 1, 144, "LXD70"}, {108, 50, 1, 144, "LXD70"}}, // LXD55/75, LX70-GTS + {{108, 53.5859375, 1, 154, "LX90"}, {108, 53.5859375, 1, 154, "LX90"}}, // LX90, LT, LX80AltAz + {{108, 53.5859375, 1, 154, "LT"}, {108, 53.5859375, 1, 154, "LT"}}, // LX90, LT, LX80AltAz + {{108, 53.5859375, 1, 154, "LX80"}, {108, 53.5859375, 1, 154, "LX80"}}, // LX90, LT, LX80AltAz + {{256, 50, 1, 350, "LX200"}, {256, 50, 1, 350, "LX200"}}, // LX200 + {{500, 36, 1, 225, "LX850"}, {500, 36, 1, 225, "LX850"}}, // LX850 + {{256, 50, 1, 180, "LX400"}, {256, 50, 1, 180, "LX400"}}, // LX400/500 + {{36, 205.3330000, 1, 144, "DSEXT"}, {36, 205.3330000, 1, 144, "DSEXT"}}, // DS external + {{36, 410.6660000, 1, 100, "DHEXT"}, {36, 157.5, 1, 58, "DHEXT"}}, // DH external/114EQs/4504s + + {{36, 205.3330000, 1, 60, "ETXnn"}, {36, 205.3330000, 1, 60, "ETXnn"}}, // ETX-xxx, DS-xxx + {{36, 91.1458333, 1, 83, "ETX??"}, {36, 144.7362076, 1, 66, "ETX??"}} // ?? + }; + +unsigned long PreviousTime; // Used in HBX Monitor, Testing + +// Testing only +unsigned char TestCount; +unsigned long TestLoopTime; + +#endif diff --git a/Software/EQG2HBXWiFi/EQG2HBXWiFi.ino b/Software/EQG2HBXWiFi/EQG2HBXWiFi.ino new file mode 100644 index 0000000..245d03b --- /dev/null +++ b/Software/EQG2HBXWiFi/EQG2HBXWiFi.ino @@ -0,0 +1,322 @@ +#include + +/* + Name: EQG2HBXE32.ino + Created: 2018-09-01 10:07:17 AM + Author: JOHNWIN10PRO\John +*/ + +/******************************************************** + Initialize HBX, translate EQG to HBX + ==================================== + *********************************************************/ +#define mESP32 +#define mTEST + +#include "HBXFileSystem.h" +#include "HBXWiFiServer.h" +#include "EQGProtocol.h" +#include "ETXProtocol.h" +#include "HBXComms.h" +#include "EQG2HBX.h" // All the declared variables + +// Function Prototypes +// +void UpdateETX(void); +void CheckETXState(void); +void TimerDelaymS(unsigned long ); +void TimerDelayuS(unsigned int); + + +// Functions +// +/************************************************************************************************** + * Read / Update ETX - Timer Driven + **************************************************************************************************/ +void UpdateETX(void) { + +} + +/********************************************** + Multiple 1mS delay +***********************************************/ + +void TimerDelaymS(unsigned long d) { + delay(d); +} + +/********************************************** + Multiple 1uS delay +***********************************************/ + +void TimerDelayuS(unsigned int d) { + delayMicroseconds(d); +} + +void CheckETXStatus(unsigned char Motor) { + /************************************************************************************************** + * Get ETXStatus + **************************************************************************************************/ + HBXGetStatus(Motor); +} + +void CheckETXState(unsigned char Motor) { + /************************************************************************************************** + * Check ETXState + **************************************************************************************************/ + ETXState(Motor); // Check the motor state +} + +void HandleOTA() +{ + StartOTAIfRequired(); + ArduinoOTA.handle(); +} + +void StartOTAIfRequired() +{ + if (ota_started) + return; + // Port defaults to 8266 + // ArduinoOTA.setPort(8266); + // Hostname defaults to esp8266-[ChipID] + //if (ArduinoOTA.getHostname() && ArduinoOTA.getHostname().length()) + + // No authentication by default + ArduinoOTA.setPassword((const char *)"EQG2HBX"); + ArduinoOTA.onStart([]() { + Serial.println("OTA Start"); + }); + ArduinoOTA.onEnd([]() { + Serial.println("\nOTA End"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r\n", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); + ota_started = true; + delay(500); +} + +// ======================================================================================================= +void setup() +{ + +#ifdef mESP32 + dbgSerial.begin(115200); // debug + EQGSerial.begin(9600, SERIAL_8N1, 18, 19); // EQG via serial or WiFi + delay(10); + StartOTAIfRequired(); +#endif + + dbgSerial.println(EQ2HBX_Version); + DelayTimer = micros(); // Initialize timers, counts + StatusTimer = DelayTimer; + StatusTime = DelayTimer; + EQGErrorValue = 0; + +#ifdef mESP32 + HBXWiFiSetup(); +#endif + + pinMode(SCOPELED, OUTPUT); + pinMode(EQGLED, OUTPUT); + digitalWrite(SCOPELED, LOW); + digitalWrite(EQGLED, LOW); + pinMode(TESTHBX, INPUT_PULLUP); // Initialize Mode jumpers + digitalWrite(TESTHBX, HIGH); // Use internal PUR, write 1 to O/P + + axis[AzMotor].PrintStatus0 = 0; // Disable printing "status polls" with no change + axis[AltMotor].PrintStatus0 = 0; // Disable printing "status polls" with no change + + // Initialize EQG communications + + // ************************** + // Check for HBX Testing Mode + // ========================== + dbgSerial.print("digitalRead(TESTHBX) : "); + dbgSerial.println(digitalRead(TESTHBX)); + while (digitalRead(TESTHBX) == 0) { // Check if test jumper installed + dbgSerial.print("digitalRead(TESTHBX) : "); + dbgSerial.println(digitalRead(TESTHBX)); + HBXTestLoop(); // Execute test code until jumper removed + }; + + dbgSerial.println("HBX Initialization"); + + // Read Motor Type to determine telescope type + // ------------------------------------------- + preferences.begin("EQG2HBX", false); // Access EQG2HBX namespace + telescope = 0; // Default ETX60 + if (!(preferences.getUChar("TELESCOPE", 0) == 0)) { // If it exists check telescope table for a match + telescope = (preferences.getUChar("TELESCOPE", 0)); + } + dbgSerial.print("Telescope: "); + dbgSerial.print(telescope); + dbgSerial.print(", "); + dbgSerial.println(ratio[telescope][0].Telescope); + if (!(preferences.getUChar("PROTOCOL", 0) == 0)) { // If it exists get protocol type (UDP, NOW) + protocol = (preferences.getUChar("PROTOCOL", 0)); + } + dbgSerial.print("Protocol: "); + dbgSerial.print(protocol); + dbgSerial.print(", "); + if (!(preferences.getUChar("STATION", 0) == 0)) { // If it exists get station type (AP, STA) + protocol = (preferences.getUChar("STATION", 0)); + } + dbgSerial.print("Station: "); + dbgSerial.print(station); + dbgSerial.print(", "); + preferences.end(); + + AzInitialise(telescope); + AltInitialise(telescope); + PrintRatioValues(telescope); + PrintHbxValues(AzMotor); + PrintHbxValues(AltMotor); + + // Initialize HBX communications as outputs + // It will use H2X communications + HCL1Talk(); // Set for Talking on RAClk + HCL2Talk(); // Set for Talking on DECClk + HDAListen(); + + HBXReset(); + + // Reset the motors (RA and DEC) + // and wait until both respond to a command + dbgSerial.println("Waiting for both motors to start up .."); + WaitForMotors(); + + // Get Motor Type from Az MC ( assume both same type of motor) + + + do { + axis[AzMotor].MotorType = 0x00; + if (HBXSendCommand(GetMotorType, AzMotor)) + axis[AzMotor].MotorType = HBXGetByte(AzMotor); + } while (!axis[AzMotor].MotorType); + axis[AltMotor].MotorType = axis[AzMotor].MotorType; + dbgSerial.println(""); dbgSerial.print("Motor Type: "); dbgSerial.print(axis[AltMotor].MotorType); + + // Handle position sensors LED current + // ----------------------------------- + dbgSerial.println(""); dbgSerial.print("Check Calibrate LEDs"); + preferences.begin("EQG2HBX", false); // Access EQG2HBX namespace + if (preferences.getUChar("AzLEDI", 0) == 0) { // If it does not exist, calibrate the LEDs + CalibrateLEDs(); + } +// Read stored LED currents + axis[AzMotor].HBXLEDI = preferences.getUChar("AzLEDI", 0); + axis[AltMotor].HBXLEDI = preferences.getUChar("AltLEDI", 0); + preferences.end(); + +// Set the MC LED values + dbgSerial.println(""); dbgSerial.print("Set MC LED values"); + if (HBXSendCommand(SetLEDI, AzMotor)) + HBXSendByte(axis[AzMotor].HBXLEDI, AzMotor); + axis[AzMotor].HBXP1 = axis[AzMotor].HBXLEDI; + HBXPrintStatus(AzMotor); + + if (HBXSendCommand(SetLEDI, AltMotor)) + HBXSendByte(axis[AltMotor].HBXLEDI, AltMotor); + axis[AltMotor].HBXP1 = axis[AltMotor].HBXLEDI; + HBXPrintStatus(AltMotor); + + // Set the Offset Clear Command + // Send HBXP1, HBXP2 + dbgSerial.println(""); dbgSerial.print("Reset any ETX offset bytes"); + axis[AzMotor].HBXP1 = 0; + axis[AzMotor].HBXP2 = 0; + axis[AltMotor].HBXP1 = 0; + axis[AltMotor].HBXP2 = 0; + if (HBXSendCommand(SetOffset, AzMotor)) + HBXSend2Bytes(AzMotor); + TimerDelaymS(CMNDTIME); + if (HBXSendCommand(SetOffset, AltMotor)) + HBXSend2Bytes(AltMotor); + TimerDelaymS(CMNDTIME); + + // Stop the motors (RA and DEC) + dbgSerial.println(""); dbgSerial.print("Stop motors"); + do { + P1 = 0; + if (HBXSendCommand(Stop, AzMotor)) P1 += 1; + if (HBXSendCommand(Stop, AltMotor)) P1 += 1; + } while (P1 < 2); + + // Read status + dbgSerial.println(""); dbgSerial.println("Read Status"); + HBXGet2Status(); // Check and read both motor states + + dbgSerial.println("Setup Complete. Listening for commands .."); + + // Print debug info every 10 s + // --------------------------- + + // HBXTestLoop(); + +// AlarmDebugPrint.attach(10, debugEQG); // Print debug info every 10 s +// AlarmCheckETX.attach_ms(6, CheckETXState); // Check motor status (position), every 6mS +} + + +// ======================================================================================================= +void loop() +{ + /************************************************************************************************** + * Process EQG comms - Rx, Tx Comms are interrupt driven + **************************************************************************************************/ + HandleOTA(); + dbgRx(); // Check for comms from debug port for telescope values + + // Check ETX motor status and state + if ((micros() - StateTimer) > (STATEDELAY * 1000)) { // ~6.55mS + if (StateSelect) StateSelect = false; + else StateSelect = true; + StateTimer = micros(); + if (StateSelect) CheckETXState(AzMotor); + else CheckETXState(AltMotor); + } + if ((micros() - StatusTimer) > (STATUSDELAY * 1000)) { // ~50mS + if (StatusSelect) StatusSelect = false; + else StatusSelect = true; + StatusTimer = micros(); + if (StatusSelect) CheckETXStatus(AzMotor); + else CheckETXStatus(AltMotor); + } + + // Check any incoming characters from the EQMOD serial interface + if (HBXCheckRx()) + EQGState(); // Check command state if data received + if (EQGDone) { // EQG Rx complete, see what it is + if (EQGErrorValue == 0) { + EQGAction(); // and do it + } + else { + EQGError(EQGErrorValue); // EQGAction() may set an error + } + } + + // Check for any characters that are ready to go to the WiFi interface + while (EQGTxoPtr != EQGTxiPtr) { + + // Send any characters that are ready to go to the WiFi interface + digitalWrite(EQGLED, HIGH); + HBXCheckTx(); + EQGTxoPtr &= EQGMASK; + digitalWrite(EQGLED, LOW); + } +// TimerDelaymS(1); +// yield(); +} + +// End loop() diff --git a/Software/EQG2HBXWiFi/EQGProtocol.h b/Software/EQG2HBXWiFi/EQGProtocol.h new file mode 100644 index 0000000..6ce0de8 --- /dev/null +++ b/Software/EQG2HBXWiFi/EQGProtocol.h @@ -0,0 +1,218 @@ +/* + * Copyright 2017, 2018 John Archbold +*/ + +/******************************************************** + EQG Protocol function definitions + ================================= + *********************************************************/ + +#include + +#ifndef EQGProtocol +#define EQGProtocol + +#define CR 0x0d +#define LF 0x0a + +float SIDEREALSECS = 86164.098903691; // Some astronomical constants +float SOLARSECS = 86400; +float LUNARSECS = 89309; + + +#define EQG_CMNDSTART 0x01 +#define EQG_WAITFORCR 0x77 +#define EQG_INTERPRET 0x78 + +/* +// Get Motor Controller Version +// :e1[0D] +// =llhhMM[0D] +// =========== +e 0 6 Get Motor Controller Version // =llhhMM[0D] MM = mount type, + // x00 = "EQ6Pro" + // x01 = "HEQ5" + // x02 = "EQ5" + // x03 = "EQ3" + // x04 = "EQ8" + // x05 = "AZEQ6" + // x06 = "AZEQ5" + // hh.ll = board version hh=x00..x07 = equatorial + // =x08..xFF = altaz +*/ +// :qm + +// A 8 +// 4 +// 2 in PPEC +// 1 in PPECTraining + +// B 8 is AZEQ +// 4 has HomeIndexer +// 2 has PPEC +// 1 has Encoder + +// C 8 has Wifi +// 4 hasHalfCurrentTracking // ref :Wx06.... +// 2 has CommonSlewStart // Supports ":J3" +// 1 has PolarLed +/* +// A +AxisFeatures[Axis1].inPPECTraining = rafeatures & 0x00000010; +AxisFeatures[Axis1].inPPEC = rafeatures & 0x00000020; +// B +AxisFeatures[Axis1].hasEncoder = rafeatures & 0x00000001; +AxisFeatures[Axis1].hasPPEC = rafeatures & 0x00000002; +AxisFeatures[Axis1].hasHomeIndexer = rafeatures & 0x00000004; +AxisFeatures[Axis1].isAZEQ = rafeatures & 0x00000008; +// C +AxisFeatures[Axis1].hasPolarLed = rafeatures & 0x00001000; +AxisFeatures[Axis1].hasCommonSlewStart = rafeatures & 0x00002000; // supports :J3 +AxisFeatures[Axis1].hasHalfCurrentTracking = rafeatures & 0x00004000; +AxisFeatures[Axis1].hasWifi = rafeatures & 0x00008000; +*/ +typedef struct SkyWatcherFeatures +{ + bool inPPECTraining = false; + bool inPPEC = false; + bool hasEncoder = false; + bool hasPPEC = false; + + bool hasHomeIndexer = false; + bool isAZEQ = false; + bool hasPolarLed = false; + bool hasCommonSlewStart = false; // supports :J3 + + bool hasHalfCurrentTracking = false; + bool hasWifi = false; +} SkyWatcherFeatures; + +enum SkywatcherGetFeatureCmd +{ + GET_INDEXER_CMD = 0x00, + GET_FEATURES_CMD = 0x01 +}; + +enum SkywatcherSetFeatureCmd +{ + START_PPEC_TRAINING_CMD = 0x00, + STOP_PPEC_TRAINING_CMD = 0x01, + TURN_PPEC_ON_CMD = 0x02, + TURN_PPEC_OFF_CMD = 0X03, + ENCODER_ON_CMD = 0x04, + ENCODER_OFF_CMD = 0x05, + DISABLE_FULL_CURRENT_LOW_SPEED_CMD = 0x0006, + ENABLE_FULL_CURRENT_LOW_SPEED_CMD = 0x0106, + RESET_HOME_INDEXER_CMD = 0x08 +}; + +// Get Motor Controller Assets +// :qm010000[0D] +// =ABCDEF[0D] +// ============ +// A 8 not defined +// 4 not defined +// 2 PPEC ON +// 1 PPEC training in progress, +// B 8 supports AZ/EQ +// 4 has Home Sensors +// 2 supports PPEC +// 1 supports dual encoders +// C 8 has WIFI +// 4 supports half current tracking // ref :Wx06.... +// 2 axes slews must start independently // ie cant use :J3 +// 1 has polar LED +// D +// E +// F +// EQ6 returns !0 +// ABCDEF +// AZEQ5 =0B6000 at boot +// AZEQ6 =0B3000 +// EQ8 =076000 +// EFCDAB +#define AEQ6 0x003000 // !:J3, Polar LED +#define AEQ5 0x003008 // !:J3, Polar LED, az/eq +#define AEQ3 0x003000 // !:J3, Polar LED +#define AAZEQ5 0x00B008 // !:J3, Polar LED, WiFi, AZ/EQ + + +// Motor firmware versions +#define VEQ6 0x000204 // Pretend EQ6/5 +#define VHEQ5 0x010204 // Pretend HEQ5 +#define VEQ5 0x020204 // Pretend EQ5 +#define VEQ3 0x030301 // Pretend EQ3 +#define VEQ8 0x040301 // Pretend EQ3 +#define VAZEQ6 0x050211 // Pretend AZEQ6 +#define VAZEQ5 0x060301 // Pretend AZEQ5 + +#define EQGVERSION VEQ6 +#define EQGASSETS AEQ6 + + +// :I := ( :b * 1296000 / :a ) / Speed ( where Speed is in arcsec/sec ) +// If :I is greater than about 10, then the slew will need to use :G = LoSpeed mode +// If :I is less than 10, then the slew will need :G = HiRate, and :I := I * :g +// a-AxxValue (Ticks/rev) := AxxVanes * 4 * AxxGbxRatio * ( Axx Transfer ) * AxxWormTeeth +// b-AxxValue := 6460.09 * AxxRatio * a-AxxValue * 15.041069 / 1,296,000 + +// Speed = g*(b*129600/a)/I +// ============================== +// IVALUE = (axis[EQGMOTOR].bVALUE * 1296000) / axis[EQGMOTOR].STEPSPER360) + +#define EQG_gVALUE 0x000010 + +#define EQGMAXIMUMSPEED 12 // 0x0C + +// EQG 'G' Command - SET move parameters +#define DIRECTION 0x00000001 // Increasing(0) Decreasing(1) +#define HEMISPHERE 0x00000002 // North(0) South(1) + +// EQG 'f' Command - GET Motor status bit definitions +// Get axis tracking/slewing "status" // =ABC[0D] + // A xxx0 0 means GOTO, 1 means SLEW *** these are diff to :G usage + // 0 means "actually doing" the goto. On stopping, it reverts to Slew Mode + // xx0x 0 means +ve, 1 means -ve + // x0xx 0 means LoRate, 1 means HiSpeed *** + // B xxx0 0 means stopped, 1 means moving, + // xx0x 0 means OK, 1 means blocked ( For DC motors only ) + // C xxx0 1 means axis is Initialised/Energised + // xx0x 1 means level switch ON ( AltAz mounts and DEC only ) + +// MotorState bit definitions +// A: nibble 1 +#define MOVESLEW 0x0001 // Step(0) Slew(1) +#define MOVEDECR 0x0002 // Increasing(0) Decreasing(1) +#define MOVEHIGH 0x0004 // Low(0) High(1) +// B: nibble2 +#define MOVEAXIS 0x0010 // Stopped(0) Moving(1) +#define MOVEFACE 0x0020 // Front(0) Rear(1) +// C: nibble3 +#define MOVEACTIVE 0x0100 // Inactive(0) Active(1) + +void EQGState(void); +void EQGError(unsigned char); +void EQGAction(void); + +void TimerDelaymS(unsigned long); + +bool EQGRx(void); +void EQGTx(unsigned char); +void EQGTxHex(unsigned char); +void EQGTxHex2(unsigned char); +void EQGTxHex3(unsigned int); +void EQGTxHex6(unsigned long); + +// debug +void putbyte(unsigned char); +void putbyte(unsigned char); +void puthexn(unsigned char); +void puthexb(unsigned char); +void puthexw(unsigned int); +void puthexl(unsigned long); +void putdecn(unsigned char); +void putdecb(unsigned char); +void putdecw(unsigned int); +void putdecl(unsigned long); +#endif + diff --git a/Software/EQG2HBXWiFi/EQGProtocol.ino b/Software/EQG2HBXWiFi/EQGProtocol.ino new file mode 100644 index 0000000..93f3543 --- /dev/null +++ b/Software/EQG2HBXWiFi/EQGProtocol.ino @@ -0,0 +1,968 @@ +/* + * Copyright 2017, 2018 John Archbold +*/ +#include +#include +/* +// EQG Protocol description +// Courtesy Andrew Johansen - Yahoo Roboscope Group + +// Transmit to EQG +================== + :CbDDDDDD[0D] + C = command ( a..z, A..Z ) + b = controller ( 1 = Az, 2 = Alt, 3 = both ) + + DDDDDD = data ( little endian ) uses 24bit little endian ( unless its a bitmapped command like :G ) + +//------------------------------------------------------------------------------------------------- + +// Receive from EQG +=================== + "=DDDDDD[0D]" // Data + "!E[0D]" // Error + + = means success + DDDDDD = data ( little endian ) uses 24bit little endian ( unless its a bitmapped command like :f, :q ) + + ! means error + E is reason code + Error E = 0 Invalid Command // the command doesn't apply to the model + 1 Invalid Paramcount // a valid command was passed with invalid parameter count + 2 Motor not Stopped // a valid command failed to run ( e.g. sending :G whilst motor is running ) + 3 NonHex Param // the parameter contains a non uppercase Hex Char // Note! It doesn't mean an invalid parameter + 4 Not energised // motor is not energised + 5 Driver Asleep // controller is in sleep mode + 6 + 7 + 8 Invalid PPEC model + +//------------------------------------------------------------------------------------------------- + +Values for Bytes sent +:C DDDDDD bytes + +Command, Bytes Sent, Nibbles Received, Description, // Example + + sent rcvd +a 0 6 Get microsteps per 360deg // =llhhLL[0D] +b 0 6 Get timer interrupt freq // =llhhLL[0D] +c 0 6 Get current ":U" value // =llhhLL[0D] +d 0 6 Get Current Encoder count // =llhhLL[0D] default #x800000 = home ( this is synched somehow with :j Data ) +e 0 6 Get Motor Controller Version // =llhhMM[0D] MM = mount type, + // x00 = "EQ6Pro" + // x01 = "HEQ5" + // x02 = "EQ5" + // x03 = "EQ3" + // x04 = "EQ8" + // x05 = "AZEQ6" + // x06 = "AZEQ5" + // hh.ll = board version hh=x00..x07 = equatorial + // =x08..xFF = altaz +f 0 3 Get axis tracking/slewing "status" // =ABC[0D] + // A xxx0 0 means GOTO, 1 means SLEW *** these are diff to :G usage + // 0 means "actually doing" the goto. On stopping, it reverts to Slew Mode + // xx0x 0 means +ve, 1 means -ve + // x0xx 0 means LoSpeed, 1 means HiSpeed *** + // B xxx0 0 means stopped, 1 means moving, + // xx0x 0 means OK, 1 means blocked ( For DC motors only ) + // C xxx0 1 means axis is Initialised/Energised + // xx0x 1 means level switch ON ( AltAz mounts and DEC only ) +g 0 2 Get HiSpeed multiplier // =llhhLL[0D] EQ6Pro, AZEQ5, EQ8 = 16 AZEQ6 = 32 +h 0 6 Get Current "goto" target // =llhhLL[0D] last value as set by :S or ( :j +/- :H ) +i 0 6 Get Current "slew" speed // =llhhLL[0D] must use with :f Hi/Lo and :g multiplier for correct value + // Note! this value gets changed as a goto is done, ie a goto trumps any prev :I data + // AZEQ5 changes as we do a goto, EQ6 only returns one value. +j 0 6 Get Current Axis position. // =llhhLL[0D] Cardinal encoder count with #x800000 = 0 as a centre datum + // DEC #x800000 = 0, pointing at West Horizon in Sth Hemi + // DEC #xA26C80 = -90, pointing at Polar South in Sth Hemi + // RA #x800000 = 0, CW shaft down + +k 1 6 :kx0[0D]=llhhLL[0D] gets running microstep count since last start/reset + :kx1[0D]=llhhLL[0D] gets running microstep count since last start/reset then resets count + // :k works on EQ6Pro, but not AZEQ5, AZEQ6, EQ8 +l *** +m 0 6 Appears to be ramp up for a goto // =llhhLL[0D] ( ie :j +/- :M ) ) + // :J processing for EQ6Pro converts data to :h +/- :c if above low distance limit + // to :h if below low distance limit +n 0 2 Read EEProm Addr // =DD[0D] used with :C for peek ??? #### +o *** +p *** +q 6 6 :qx000000[0D]=000000[0D] if axis is CW from home ( ie -ve ) just after home sensor trip has been reset + =FFFFFF[0D] CCW from home ( ie +ve ) just after home sensor trip has been reset ) + =llhhLL[0D] if sensor has tripped since reset ( use :W to clear data first ) + // AZEQ5 returns =000080 for Az and Alt + :qx010000[0D]=ABCDEF[0D] ie the bitmapped nibbles for current status + // A 8 not defined + // 4 not defined + // 2 PPEC ON + // 1 PPEC training in progress, + // B 8 supports AZ/EQ + // 4 has Home Sensors + // 2 supports PPEC + // 1 supports dual encoders + // C 8 has WIFI + // 4 supports half current tracking // ref :Wx06.... + // 2 axes slews must start independently // ie cant use :J3 + // 1 has polar LED + // D + // E + // F + // EQ6 returns !0 + // AZEQ5 =0B6000 at boot + // AZEQ6 =0B3000 + // EQ8 =076000 +r 0 2 Read Register Addr // =DD[0D] or =DDDD or =DDDDDD used with :A for peek ??? + // result appears to vary based on length of last valid data return ref test data done lower + // AZEQ5 returns =[0D] ie no data if used directly after :A + // must use :A then :g then :r ( ie the :g fake sets the return length to 2 ) +s 0 6 Get microsteps per worm rev // =llhhLL[0D] Used for wormwheel teeth calcs and PPEC +t *** +u *** +v *** +w *** +x *** +y *** +z 0 0 Set Debug Flag // EQ6Pro returns !0[0D], AZEQ5/AZEQ6/EQ8 returns =[0D] + +A 2 0 :AxHH[0D] Set Register Addr // used with :R and :r for Register poke/peek +B *** +C 4 0 :CxLLHH[0D] Set EEProm Addr // used with :N and :n for EEProm poke/peek +D 0 6 :Dx[0D] Get 1x Track Rate // =llhhLL[0D] This is the :I rate used to give sidereal tracking +E 6 0 :ExllhhLL[0D] Reset Axis datum to // used to synch encoder posn against a known HA/DEC ) +F 0 0 :Fx[0D] Initialise Target Axis ( energises motor ) +G 2 0 :GxAB[0D] Prepare slew parameters using bitmapped nybbles xAB + // ( Note: ":f" is used to read the "current" actual status ) + // A = '0' high speed GOTO slewing, doesnt make "bitmapped" sense, but it is as coded by SkyWatcher????? ????? + // '1' low speed slewing mode, all other bytes use bitmapping ( incl :f ), this doesnt + // '2' low speed GOTO mode, + // '3' high speed slewing mode + // xxx0 0 means AutoGoto, 1 means manual slew or stopped + // xx0x 0 means HiRate if Goto else LoRate if Slew + // speed mode for AutoGoto is ignored for EQ6Pro + // B = '0' +CW and Nth Hemi + // '1' -CCW and Nth Hemi + // '2' +CW and Sth Hemi + // '3' -CCW and Sth Hemi + // xxx0 0 means +ve, 1 = -ve "motor" direction, ie code takes care of whats N/S/E/W etc + // +ve speed in RA is Axle moves CW when viewed from pole + // +ve speed in DEC is Axle moves CCW when viewed from above + // xx0x 0 means Nth Hemi else Sth Hemi ( ST4 guiding related ) ????? + // Note! when using :S type gotos, the direction bit here "appears" to be ignored + +H 6 0 :HxllhhLL[0D] Set goto target ( as a delta to current ) increment. Direction set by :G, +I 6 0 :IxllhhLL[0D] Set Manual slewing rate ( read in conjunction with Hi/Lo rate as set by :G ) +J 0 0 :Jx[0D] Start moving +K 0 0 :Kx[0D] Stop moving normal ( ramp if reqd ) +L 0 0 :Lx[0D] Stop moving Immediately +M 6 0 :MxllhhLL[0D] Set break point increment // Doesnt appear to do anything ????? But possibly Ramp UP related + // EQASCOM uses if H > 80000 then H - 80000 else H / 2 + // Indi uses HiSpeed if H > 3200 then 3200 else H/10 based on skywatcher code ( that also sets I ) + // LoSpeed if H > 200 then 200 else H/10 + // no values of :M appear to affect my EQ6 behaviour +N 2 0 :NxHH[0D] Set EEProm Value to xHH // used with :C for poke?? NOT TESTED +O 1 0 :OxA[0D] Toggle "Trigger/Snap Port" A = '1' = On, '0' = Off // EQ6 fires both at same time via Hbx, ie :O11 :O21 + // AZEQ5 can fire independently, EQ8 uses :O2x[0D] to fire its only port. +P 1 0 :PxA[0D] set ST4 guiderate A = 0..4 = 1.0, 0.75, 0.50, 0.25, 0.125 +Q *** Set Bootloader Mode // Always uses :Qx55AA[0D] and has no response. 55AA looks like a flash unlock code ???? +R 2 0 :RxHH[0D] Set Register Value to xHH // used with :A for poke?? NOT TESTED +S 6 0 :SxllhhLL[0D] Set absolute goto target // EQ8 also uses :M with this, but :M is absolute in EQ8 ????? + // :S appears to ignore direction data set via :G +T 6 0 :TxllhhLL[0D] Set LSB of speed // hhLL must be 0000. ie equivalent to :I = ll0000[0D] but works in HiSpeed mode ????? + // Set Long Goto Step Period ( as per Synta ) +U 6 0 :UxllhhLL[0D] Set rampdown range // sets the microsteps from target where the rampdown process begins +V 2 0 :VxHH[0D] Set Polar LED brightness // HH := x00 to xFF +W 6 0 :Wx000000[0D] Start PPEC train + :Wx010000[0D] Stop PPEC train + :Wx020000[0D] Turn PPEC ON + :Wx030000[0D] Turn PPEC OFF ( also sent when synch encoder used in EQ6 in 3.36. Not in 3.37 ??? + :Wx040000[0D] Encoder ON + :Wx050000[0D] Encoder OFF + :Wx060000[0D] Disable Full Current Low speed + :Wx060100[0D] Enable Full Current Low speed + :Wx07vvvv[0D] Set Stride for Slewing // need to test + :Wx080000[0D] Reset Home Sensor datum +X *** +Y *** +Z *** +//================================================================================================= +When setting "GOTO" data, it appears to require a correct sequence +ie :G then :H then :M or + :G then :S then :M for gotos. +Mount must be stopped before sending :G here, or it chucks a fault. +:M appears to have no function anymore???? + +:U appears to be standalone, and can be set at any time and is totally "sticky" + Only appears to work so far with the EQ6Pro + +When getting data we also need to get current status +:j gets current offset +:f is used first to check for current mode ie slew/goto, Hi/Lo, Fwd/Bwd so we know/can check signs for :h and :m +:h gets current target ( should be ( :j + :H ) for Fwds, ( :j - :H ) for Bwds ) // ie same as :S +:m gets ??? target ( should be ( :j + :M ) for Fwds, ( :j - :M ) for Bwds ) +:d gets the current quadrature encoder count ( if encoders are fitted ). Result is always true + ie even if encoders are OFF, :d returns the true count. +// *** WARNING *** :f always responds correctly to the latest :G +// however, :h and :m do not. The :M and :H/:S must be sent AFTER :G +// if you want to correctly reverse engineer settings from :h, :m + +When setting "Slew" data, it also requires a set procedure +Again :G sets direction and speed "range", and must be sent when stopped. +:I is used to set the speed. +The value used is basically the no of timer interrupts per microstep +:I := ( :b * 1296000 / :a ) / Speed ( where Speed is in arcsec/sec ) +Speed = g*(b*1296000/9024000)/I +If :I is greater than about 10, then the slew will need to use :G = LoSpeed mode +If :I is less than 10, then the slew will need :G = HiRate, and :I := I * :g +In LoSpeed mode, once moving, simply resending a new :I will cause the speed to change. +In HiSpeed mode, you must issue a stop, resend :G, reset :I then restart. +:b = :I * Speed/g * :a / 1296000 +*/ + +/******************************************************** + EQG Protocol related functions + ============================== + *********************************************************/ + +// Process received EQG characters +// =============================== +void EQGState(void) { + while ((EQGRxiPtr != EQGRxoPtr) && (EQGDone == 0)) { + if (dbgFlag == 1) { +// if (EQGRxBuffer[EQGRxoPtr] == 'j') +// dbgFlag = 0; + } + if (EQGRxBuffer[EQGRxoPtr] == ':') { + dbgSerial.println(""); + dbgSerial.print("--- "); + dbgFlag++; + } + if (dbgFlag) { + dbgSerial.write(EQGRxBuffer[EQGRxoPtr]); + } + + EQGRxChar = EQGRxBuffer[EQGRxoPtr++]; // Get a character + if ((EQGRxState < EQG_WAITFORCR) && (EQGRxChar < ' ')) { + EQGRxState = EQG_INTERPRET; // Terminate on non-alpha + } + +// Convert hex parameters to binary nibble +// --------------------------------------- + if ((EQGRxState > 0x03) && (EQGRxState < EQG_WAITFORCR)) { + EQGRxChar = toupper(EQGRxChar); + if ((EQGRxChar <= 'F') && (EQGRxChar >= '0')) { + EQGRxChar -= '0'; + if (EQGRxChar > 9) EQGRxChar -= 0x07; + } + else EQGRxState = EQG_INTERPRET; // terminate on non-hex + } + +// Rx State machine +// ---------------- + + switch (EQGRxState) { + + case EQG_WAITFORCR: // Waiting for CR + if (EQGRxChar == CR) { + EQGRxState = EQG_CMNDSTART; + EQGDone++; + } + break; + + case EQG_CMNDSTART: // Waiting for ':' + if (EQGRxChar == ':') { + EQGRxCount = 1; // Count for # parameters + EQGRxState++; + } + break; + + case 0x02: // Waiting for command + EQGCmnd = EQGRxChar; + EQGRxCount++; + EQGRxState++; + break; + + case 0x03: // Which motor? + EQGRADEC = EQGRxChar; + EQGMOTOR = EQGRADEC - '0'; + if ((EQGRADEC > '0') && (EQGRADEC < '3')) { + EQGRxCount++; + switch (EQGCmnd) { // Commands that have additional bytes + case 'q': // Get mount assets + case 'A': // Not used - Set Register Addr + case 'B': // Unknown + case 'C': // Not done - Set EEPROM Addr + case 'E': // Set Current Position + case 'G': // Set Move direction and speed multiplier + case 'H': // Set GoTo Target Increment + case 'I': // Set Speed + case 'M': // Set BreakPoint Increment + case 'N': // Not done - Set EEPROM + case 'O': // Not done - Set trigger (0-off,1-on) + case 'P': // Set AutoGuide Speed + case 'R': // Not done - Set Register + case 'S': // Not done - Set GoTo Target + case 'T': // Unknown + case 'U': // Not done - Set Break Step + case 'V': // Set Polar LED brightness + EQGRxState++; // Yes, so next state + break; + + default: + EQGRxState = EQG_INTERPRET; // No, so command complete + break; + } + } + else { + if (EQGRxChar != 0x0d) EQGRxState = EQG_WAITFORCR; + else EQGDone++; + EQGErrorValue = '3'; // Failure - Bad Parameters + } + break; + + case 0x04: // First nibble + EQGP1 = EQGRxChar; // EQGRxChar already converted to binary nibble + EQGRxCount++; // Increase character count + switch (EQGCmnd) { // Commands that send one nibble + case 'B': // Not done - Unknown + case 'O': // Set trigger (0-off,1-on) + case 'P': // Set ST4 Guiderate + EQGRxState = EQG_INTERPRET; + break; + default: + EQGRxState++; + break; + } + break; + + case 0x05: // Second nibble - first byte (B1 = N1N2) + EQGP2 = EQGRxChar; // EQGRxChar already converted to binary nibble + EQGP1 = ((EQGP1 << 4) | EQGP2); // First byte + EQGRxCount++; + switch (EQGCmnd) { // Commands that send one byte + case 'A': // Set register address + case 'G': // Set direction, range + case 'N': // Set EEPROM (:C) to xHH + case 'R': // Set Register (:A) to xHH + case 'V': // Set Polar LED brightness ro xHH + EQGRxState = EQG_INTERPRET; + break; + default: + EQGRxState++; // All the rest send 3 bytes + break; + } + break; + + case 0x06: // Third nibble - N3N1N2 + EQGP2 = EQGRxChar; // EQGRxChar already converted to binary nibble + EQGP1 |= (EQGP2 << 8); + EQGRxCount++; + EQGRxState++; + break; + + case 0x07: // Fourth nibble - N3N4N1N2 + EQGP1 &= 0xFF; // Clear all bar low byte + EQGP1 |= (EQGP2 << 12); // Get N3 into correct position + EQGP2 = EQGRxChar; // EQGRxChar already converted to binary nibble + EQGP1 |= (EQGP2 << 8); // Get N4 into correct position + EQGRxCount++; + switch (EQGCmnd) { // Commands that send two bytes + case 'C': // Set EEPROM address + EQGRxState = EQG_INTERPRET; + break; + default: + EQGRxState++; // All the rest send 3 bytes + break; + } + break; + + case 0x08: // Fifth nibble - N5xxN3N4N1N2 + + EQGP2 = EQGRxChar; + EQGP1 |= (EQGP2 << 20); + EQGRxCount++; + EQGRxState++; // Get next data + break; + + case 0x09: // Sixth nibble - N5N6N3N4N1N2 + + + EQGP2 = EQGRxChar; + EQGP1 |= (EQGP2 << 16); + EQGRxCount++; + EQGRxState = EQG_INTERPRET; // All done + break; + + case EQG_INTERPRET: + if ((EQGRxChar == 0x0d) && (EQGRxCount >= 3)) { + EQGRxState = EQG_CMNDSTART; // Reset state machine + switch (EQGCmnd) { + // Commands that have no data + case 'a': // Read steps per rotation + case 'b': // Read tracking scale + case 'c': // Read Motor Speed Change + case 'd': // Read Current Encoder count + case 'e': // Read firmware version + case 'f': // Read motor status + case 'g': // Read speed divisor + case 'j': // Read position + case 'm': // Read motor slowdown point + case 'n': // Read EEPROM data + case 'r': // Read register data + case 's': // Read steps per arcsec + case 'D': // Read track rate + case 'F': // Energise motors + case 'J': // GoTo position, track + case 'K': // Stop movement + EQGDone++; + break; + + // Commands that have three bytes + // ============================== + case 'q': // Read mount assets + case 'E': // Set current position + case 'H': // Set target position + case 'I': // Set GoTo speed + case 'M': // Set motor slowdown position ?? + case 'U': // Set motor slowdown speed ?? + if (EQGRxCount == (3 + 6)) { + EQGDone++; + } + else { + if (EQGRxChar != 0x0d) EQGRxState = EQG_WAITFORCR; + else EQGDone++; + EQGErrorValue = '3'; // Failure - Bad Parameters + } + break; + + // Commands that have two bytes + case 'C': // Set EEPROM address + if (EQGRxCount == (3 + 4)) { + EQGDone++; + } + else { + if (EQGRxChar != 0x0d) EQGRxState = EQG_WAITFORCR; + else EQGDone++; + EQGErrorValue = '3'; // Failure - Bad Parameters + } + break; + + // Commands that have one byte + case 'A': // Set register address + case 'G': // Set direction, range + case 'N': // Set EEPROM (:C) to xHH + case 'R': // Set Register (:A) to xHH + case 'V': // Set LED Brightness to xHH + if (EQGRxCount == (3 + 2)) { + EQGDone++; + } + else { + if (EQGRxChar != 0x0d) EQGRxState = EQG_WAITFORCR; + else EQGDone++; + EQGErrorValue = '3'; // Failure - Bad Parameters + } + break; + + // Commands that have one nibble + case 'P': // Set autoguide speed + case 'O': // Set Snap Port + if (EQGRxCount == (3 + 1)) { + EQGDone++; + } + else { + if (EQGRxChar != 0x0d) EQGRxState = EQG_WAITFORCR; + else EQGDone++; + EQGErrorValue = '3'; // Failure - Bad Parameters + } + break; + + default: + if (EQGRxChar != 0x0d) EQGRxState = EQG_WAITFORCR; + else EQGDone++; + EQGErrorValue = '0'; // Failure - Bad Command + break; + } // End - switch (EQGCmnd) + } + else { + if (EQGRxChar != 0x0d) EQGRxState = EQG_WAITFORCR; + else EQGDone++; + EQGErrorValue = '0'; // Failure - Bad Command + } + break; // End - if (((EQGRxChar == 0x0d) || (EQGRxChar == 0x0a)) && (EQGRxCount >= 3)) + + default: + EQGRxState = EQG_CMNDSTART; + } // End - switch (EQGRxState) + } // END - while ((EQGRxiPtr != EQGRxoPtr) && (EQGDone == 0)) +} + +void EQGError(unsigned char errorbyte) { + EQGTx('!') ; // Failure - Bad Parameters + EQGTx(errorbyte); + EQGTx(CR); + EQGDone = 0; // Process errors + EQGRxState = EQG_CMNDSTART; + EQGRxCount = 0; // Count for # parameters + EQGErrorValue = 0; +} + +// Received a valid command so execute it, if supported +// ==================================================== +void EQGAction(void) { + + EQGDone = 0; // Reset flag + EQGRxState = EQG_CMNDSTART; + EQGTx('='); // Answer (with parameters) + + switch (EQGCmnd) { + + case 'a': // Request total number of steps per revolution + EQGTxHex6(axis[EQGMOTOR].aVALUE); + break; + + case 'b': // Request step interrupt frequency + EQGTxHex6(axis[EQGMOTOR].bVALUE); // Let EQMOD calculate SIDEREAL, etc + break; + + case 'c': + EQGTxHex6(0x00D800); // Return same as EQ6 to keep the speed commands consistent + break; + + case 'd': // Request Mount center point +// EQGTxHex6(EQG_CENTRE); // Return same as EQ6 to keep the speed commands consistent + EQGTxHex6(axis[EQGMOTOR].Position); + break; + + case 'e': // Request version + EQGTxHex6(EQGVERSION); // + break; + + case 'f': // Request motor status + +// EQGMotorStatus bit definitions +// A: nibble 1 +// MOVESLEW 0x0001 // Step(0) Slew(1) +// MOVEDECR 0x0002 // Increasing(0) Decreasing(1) +// MOVEHIGH 0x0004 // Low(0) High(1) +// B: nibble2 +// MOVEAXIS 0x0010 // Stopped(0) Moving(1) +// MOVEFACE 0x0020 // Front(0) Rear(1) +// C: nibble3 +// MOVEACTIVE 0x0100 // Inactive(0) Active(1) + + EQGTxHex3(axis[EQGMOTOR].EQGMotorStatus); + + break; + + case 'g': // Request speed multiplier + EQGTxHex2(EQG_gVALUE); // Return same as EQ6 to keep the speed commands consistent + break; + + case 'j': // Request axis position + EQGTxHex6(axis[EQGMOTOR].Position); + break; + + case 'm': // GET Point at which to change from fast to slow + EQGTxHex6(axis[EQGMOTOR].SlowDown); + break; + + case 'q': // GET mount capabilities + EQGTxHex6(EQGASSETS); // Say EQ and AZ + break; + + case 's': // PEC period + EQGTxHex6(axis[EQGMOTOR].PEC); + break; + + case 'E': // Set current motor position + axis[EQGMOTOR].Position = EQGP1; + break; + + case 'F': // Initialize and activate motors + axis[EQGMOTOR].EQGMotorStatus |= MOVEACTIVE; + axis[EQGMOTOR].ETXMotorStatus |= MOVEACTIVE; + break; + + case 'G': // EQG 'G' Command :GxAB[0D] + + // See below for A +// B nibble + // B = '0' +CW and Nthn Hemi + // '1' -CCW and Nthn Hemi + // '2' +CW and Sthn Hemi + // '3' -CCW and Sthn Hemi + // xxx0 0 means +ve, 1 = -ve "motor" direction, ie code takes care of whats N/S/E/W etc + // +ve speed in RA is Axis moves CW when viewed from pole + // +ve speed in DEC is Axis moves CCW when viewed from above + // xx0x 0 means Nthn Hemi else Sthn Hemi ( ST4 guiding related ) ????? + // Note! when using :S type gotos, the direction bit here "appears" to be ignored + // Also note that EQMOD does not appear to send the Hemisphere bit + // + // xxx0 CW(0) CCW(1) DIRECTION + // xx0x North(0) South(1) HEMISPHERE + + axis[EQGMOTOR].DirnSpeed = (int)EQGP1; // Save the command value + switch (axis[EQGMOTOR].DirnSpeed & 0x03) { + case 0x00: + case 0x02: + axis[EQGMOTOR].EQGMotorStatus &= ~MOVEDECR; +// dbgSerial.print(" +CW "); + break; + case 0x01: + case 0x03: + axis[EQGMOTOR].EQGMotorStatus |= MOVEDECR; +// dbgSerial.print(" -CCW "); + break; + default: + break; + } + +// A nibble + // A = '0' high speed GOTO slewing, doesnt make "bitmapped" sense, but it is as coded by SkyWatcher????? ????? + // '1' low speed slewing mode, all other bytes use bitmapping ( incl :f ), this doesnt + // '2' low speed GOTO mode, + // '3' high speed slewing mode + // xxx0 0 means AutoGoto, 1 means manual slew or stopped + // xx0x 0 means HiRate if Goto else LoRate if Slew + // speed mode for AutoGoto is ignored for EQ6Pro + +// A + switch ((EQGP1 >> 4) & 0x03) { + case 00: // 0 HIGH SPEED GOTO + axis[EQGMOTOR].EQGMotorStatus &= ~MOVESLEW; // GoTo target + axis[EQGMOTOR].EQGMotorStatus |= MOVEHIGH; // Enable high speed multiplier +// dbgSerial.print("HIGH SPEED GOTO"); + break; + case 01: // 1 LOW SPEED SLEW + axis[EQGMOTOR].EQGMotorStatus |= MOVESLEW; // Just move the axis + axis[EQGMOTOR].EQGMotorStatus &= ~MOVEHIGH; // Disable high speed multiplier +// dbgSerial.print("LOW SPEED SLEW"); + break; + case 02: // 2 LOW SPEED GOTO + axis[EQGMOTOR].EQGMotorStatus &= ~MOVESLEW; // GoTo target + axis[EQGMOTOR].EQGMotorStatus &= ~MOVEHIGH; // Disable high speed multiplier +// dbgSerial.print("LOW SPEED GOTO"); + break; + case 03: // 3 HIGH SPEED SLEW + axis[EQGMOTOR].EQGMotorStatus |= MOVESLEW; // Just move the axis + axis[EQGMOTOR].EQGMotorStatus |= MOVEHIGH; // Enable high speed multiplier +// dbgSerial.print("HIGH SPEED SLEW"); + break; + } + + axis[EQGMOTOR].ETXMotorStatus = axis[EQGMOTOR].EQGMotorStatus; // Copy the status for ETXProtocol + + break; + + case 'H': // Set the goto target increment + axis[EQGMOTOR].Increment = EQGP1; + if (axis[EQGMOTOR].EQGMotorStatus & MOVEDECR) + axis[EQGMOTOR].Target = axis[EQGMOTOR].Position - axis[EQGMOTOR].Increment; // subtract the relative target + else + axis[EQGMOTOR].Target = axis[EQGMOTOR].Position + axis[EQGMOTOR].Increment; // add the relative target + axis[EQGMOTOR].MotorControl |= GoToHBX; + + break; + + case 'I': // Set motor speed +// long Speed; + +// From EQMOD +// Multiplier = EQGSidereal / :I +// MultiplierHi = EQGSidereal*g / :I + +// For ETX +// Speed = ETXSidereal * Multiplier +// SpeedHi = ETXSidereal * MultiplierHi + +// Calculation +// Speed = SiderealRate * (:ISidereal / ) +// SpeedHi = SiderealRate * ((Sidereal*g) / :I) + + axis[EQGMOTOR].TargetSpeed = EQGP1; // Set the target speed + break; + + case 'J': // Tell motor to Go + axis[EQGMOTOR].ETXMotorStatus |= MOVEAXIS; // Signal moving + axis[EQGMOTOR].ETXMotorState = ETXCheckStartup; // General entry + + break; + + case 'K': // Tell motor to stop + axis[EQGMOTOR].EQGMotorStatus |= MOVESLEW; // Set slew as default + axis[EQGMOTOR].ETXMotorStatus |= MOVESLEW; // Set slew as default + axis[EQGMOTOR].TargetSpeed = 0; + axis[EQGMOTOR].ETXMotorState = ETXCheckSpeed; // to enable motor slowdown + break; + + case 'L': // Tell motor to stop immediately + axis[EQGMOTOR].EQGMotorStatus |= MOVESLEW; // Clear speed change + axis[EQGMOTOR].TargetSpeed = 0; + axis[EQGMOTOR].ETXMotorState = ETXStopMotor; // Immediate stop + break; + + case 'M': // Set the break point increment + axis[EQGMOTOR].SlowDown = EQGP1; + if ((axis[EQGMOTOR].EQGMotorStatus & MOVEDECR) == 0) + axis[EQGMOTOR].SlowDown = axis[EQGMOTOR].Position + axis[EQGMOTOR].SlowDown; // add the relative target + else + axis[EQGMOTOR].SlowDown = axis[EQGMOTOR].Position - axis[EQGMOTOR].SlowDown; // subtract the relative target + axis[EQGMOTOR].MotorControl |= GoToHBX; // Signal pending GoTo + break; + + case 'O': + axis[EQGMOTOR].HBXSnapPort = (char)EQGP1; + break; + + case 'P': + axis[EQGMOTOR].HBXGuide = (char)EQGP1; + break; + + case 'U': // Set the break point steps +// JMA TODO axis[EQGMOTOR].SlowDown = EQGP1; + break; + + case 'V': // Set the LED brightness + axis[EQGMOTOR].LEDValue = EQGP1; + break; + + default: + EQGErrorValue = '0'; // Failure - Bad Command + break; + } + EQGTx(CR); +} + +void EQGTxHex(unsigned char data) { + if ((data & 0x0f) < 0x0a) EQGTx((data & 0x0f) + 0x30); + else EQGTx((data & 0x0f) + 0x37); +} + +void EQGTxHex2(unsigned char data) { + EQGTxHex(data >> 4); + EQGTxHex(data); +} + +void EQGTxHex3(unsigned int data) { + EQGTxHex((unsigned char)data); + EQGTxHex((unsigned char)(data >> 4)); + EQGTxHex((unsigned char)(data >> 8)); +} + +void EQGTxHex6(unsigned long data) { + EQGTxHex2((unsigned char)data); + EQGTxHex2((unsigned char)(data >> 8)); + EQGTxHex2((unsigned char)(data >> 16)); +} + +/********************************************** + Debug routines +***********************************************/ +void dbgRx(void) { + while (dbgSerial.available() > 0) { + dbgRxBuffer[dbgRxiPtr++] = dbgSerial.read(); + dbgRxiPtr &= dbgMASK; + } +} + +void putbyte(unsigned char data) { + dbgSerial.write(data); +} + +void puthexb(unsigned char data) { + if (((data >> 4) & 0x0f) < 0x0a) putbyte(((data >> 4) & 0x0f) + 0x30); + else putbyte(((data >> 4) & 0x0f) + 0x37); + if ((data & 0x0f) < 0x0a) putbyte((data & 0x0f) + 0x30); + else putbyte((data & 0x0f) + 0x37); +} + +void putdecb(unsigned char data) { + dbgSerial.print(data); +} + +void puthexw(unsigned int data) { + puthexb((data >> 8) & 0xFF); + puthexb(data & 0xFF); +} + +void putdecw(unsigned int data) { + dbgSerial.print(data); +} + +void puthex6(unsigned long data) { + puthexb((data >> 16) & 0xFF); + puthexw(data & 0xFFFF); +} + +void puthexl(unsigned long data) { + puthexw((data >> 16) & 0xFFFF); + puthexw(data & 0xFFFF); +} + +void putdecl(unsigned long data) { + dbgSerial.print(data); +} + +void EQGSend(unsigned char data) { + dbgSerial.write(data); +} + +void EQGSendHex(unsigned char data) { + if ((data & 0x0f) < 0x0a) EQGSend((data & 0x0f) + 0x30); + else EQGSend((data & 0x0f) + 0x37); +} + +void EQGSendHex2(unsigned char data) { + EQGSendHex(data >> 4); + EQGSendHex(data); +} + +void EQGSendHex6(unsigned long data) { + EQGSendHex2((unsigned char)data); + EQGSendHex2((unsigned char)(data >> 8)); + EQGSendHex2((unsigned char)(data >> 16)); +} + +void debugEQG() { + dbgSerial.println(""); + dbgSerial.print("Az:<"); + dbgSerial.print(axis[AzMotor].EQGMotorStatus, HEX); + dbgSerial.print("> Pos: "); + dbgSerial.print(axis[AzMotor].Position, HEX); + dbgSerial.print(" SD: "); + dbgSerial.print(axis[AzMotor].SlowDown, HEX); + dbgSerial.print(" Tgt: "); + dbgSerial.print(axis[AzMotor].Target, HEX); + dbgSerial.print(" Speed: "); + dbgSerial.print(axis[AzMotor].Speed, HEX); + + dbgSerial.print(", Alt:<"); + dbgSerial.print(axis[AltMotor].EQGMotorStatus, HEX); + dbgSerial.print(">Pos: "); + dbgSerial.print(axis[AltMotor].Position, HEX); + dbgSerial.print(" SD: "); + dbgSerial.print(axis[AltMotor].SlowDown, HEX); + dbgSerial.print(" Tgt: "); + dbgSerial.print(axis[AltMotor].Target, HEX); + dbgSerial.print(" Speed: "); + dbgSerial.print(axis[AltMotor].Speed, HEX); +/* + while (dbgRxoPtr != dbgRxiPtr) { + dbgCommand[dbgIndex] = dbgRxBuffer[dbgRxoPtr]; // Copy character + dbgSerial.write(dbgCommand[dbgIndex]); + if ((dbgCommand[dbgIndex] != ':') && (dbgFlag == 0)) { // Wait for start of command string + dbgSerial.write(dbgCommand[dbgIndex]); // Output to debug and skip + } + else { + if (dbgCommand[dbgIndex] == CR) { + dbgCommand[dbgIndex + 1] = 0; + processdbgCommand(); + dbgFlag = 0; + dbgIndex = 0; + } + else { + dbgFlag |= 1; + dbgIndex += 1; + } + } + dbgRxoPtr += 1; + dbgRxoPtr &= dbgMASK; + } + +} + +// Format - ":","Motor","Command","Paramters" +// Motor 1, 2 +// Command t, w, g, v, x + +void processdbgCommand(void) { + unsigned char m, t; + char argStr[256]; + + dbgSerial.println(""); dbgSerial.print("Scope: "); dbgSerial.println(telescope); + dbgSerial.println(dbgCommand); + + strcpy(argStr, &dbgCommand[3]); + m = dbgCommand[2] - '1'; + if ((m == 0) || (m == 1)) { + switch (dbgCommand[1]) { + + case 't': + t = dbgCommand[3] - '0'; + if (t > 9) t -= 7; + if ((t >= 0) & (t < 16)) + telescope = t; + dbgSerial.println(""); dbgSerial.print("Scope: "); dbgSerial.println(telescope); + break; + + case 'w': // Number of Worm teeth + sscanf(argStr, "%ld", &v); + dbgSerial.println(""); + dbgSerial.print("WormTeeth: "); + dbgSerial.print(argStr); + dbgSerial.print(" "); + dbgSerial.println(f); + ratio[telescope][m].WormTeeth = v; + break; + + case 'g': // Gearbox Ratio (float) + sscanf(argStr, "%f", &f); // Warning: you need the float libraries for this + dbgSerial.println(""); + dbgSerial.print("GearBox: "); // "-Wl,-u,vfscanf -lscanf_flt -lm" in platform.local.txt + dbgSerial.print(argStr); + dbgSerial.print(" "); + dbgSerial.println(f); + ratio[telescope][m].GbxRatio = f; + break; + + case 'v': // Number of optical vanes per revolution + sscanf(argStr, "%ld", &v); + dbgSerial.println(""); + dbgSerial.print("Vanes: "); + dbgSerial.print(argStr); + dbgSerial.print(" "); + dbgSerial.println(v); + ratio[telescope][m].Vanes = v; + break; + + case 'x': // Gear transfer ratio + sscanf(argStr, "%f", &f); // Warning: you need the float libraries for this + dbgSerial.println(""); + dbgSerial.print("XferRatio: "); // "-Wl,-u,vfscanf -lscanf_flt -lm" in platform.local.txt + dbgSerial.print(argStr); + dbgSerial.print(" "); + dbgSerial.println(f); + ratio[telescope][m].XferRatio = f; + break; + + default: + break; + + } + PrintRatioValues(telescope); + } + */ +} + + +/********************************************** + Handle EQG communications +***********************************************/ +bool EQGRx(void) { + if (EQGSerial.available() == 0) + return false; + digitalWrite(EQGLED, HIGH); + while (EQGSerial.available() > 0) { + EQGRxBuffer[EQGRxiPtr++] = EQGSerial.read(); + EQGRxiPtr &= EQGMASK; + } + digitalWrite(EQGLED, LOW); + return true; +} + +// Just put it in the output buffer +// Main loop handles transmission +void EQGTx(unsigned char data) { + EQGTxBuffer[EQGTxiPtr++] = data; + EQGTxiPtr &= EQGMASK; +} diff --git a/Software/EQG2HBXWiFi/ETXProtocol.h b/Software/EQG2HBXWiFi/ETXProtocol.h new file mode 100644 index 0000000..22eace9 --- /dev/null +++ b/Software/EQG2HBXWiFi/ETXProtocol.h @@ -0,0 +1,91 @@ +/* + * Copyright 2017, 2018 John Archbold +*/ +#include + +/******************************************************** + EQG Protocol function definitions + ================================= + *********************************************************/ +#ifndef ETXProtocol +#define ETXProtocol + +#define MotorAz 0x01 // Pin3 on HBX interface +#define MotorAlt 0x02 // Pin5 on HBX interface + +#define AzMotor MotorAz +#define AltMotor MotorAlt + +// ETX Bit Definitions +// Variable - axis[Motor].MotorControl +// nibble 4 +#define StartHBX 0x8000 // Motor start bit +#define StopHBX 0x4000 // Motor stop bit +#define SlewHBX 0x2000 // Move in progress +#define SpeedHBX 0x1000 // Speed change pending +#define GoToHBX 0x0800 // GoTo in process + +// ETX Known Commands +#define SpeedChnge 0x00 // Update "8.16" speed +#define SpeedStart 0x01 // Begin "8.16" speed +#define SetOffset 0x02 // Output "16" correction offset +#define SetLEDI 0x03 // Output "8" LED current +#define CalibrateLED 0x04 // None +#define Stop 0x05 // None +#define SlewReverse 0x06 // None +#define SlewForward 0x07 // None +#define GetStatus 0x08 // Input "16.8.1" ticks.pwm.error +#define GetLEDI 0x09 // Input "8" LED current +#define GetMotorType 0x0B // Input "8" Motor type +#define SleepHBX 0xE4 // None + +#define OffsetMax 0x0020 // Maximum for a SetOffset command +// ETX State Machine +#define ETXIdle 0 +#define ETXCheckStartup 1 +#define ETXSlewMotor 2 +#define ETXStepMotor 3 +#define ETXCheckSlowDown 4 +#define ETXCheckSpeed 5 +#define ETXCheckPosition 6 +#define ETXCheckStop 7 +#define ETXStopMotor 8 +#define ETXMotorEnd 9 + +const float ETX60PERIOD = 152.587891; // (1/6.5536mS) + +const unsigned long ETX_CENTRE = 0x00800000; // RA, DEC; + +const float MeadeSidereal = 6460.0900; // Refer Andrew Johansen - Roboscope +const float SiderealArcSecs = 15.041069; // Sidereal arcsecs/sec +const float ArcSecs360 = 1296000; // Arcsecs / 360 + + +#define ETXSlew1 1 // 1 x sidereal (0.25 arc-min/sec or 0.0042°/sec) +#define ETXSlew2 2 // 2 x sidereal (0.50 arc-min/sec or 0.0084°/sec) +#define ETXSlew3 8 // 8 x sidereal ( 2 arc-min/sec or 0.0334°/sec) +#define ETXSlew4 16 // 16 x sidereal ( 4 arc-min/sec or 0.0669°/sec) +#define ETXSlew5 64 // 64 x sidereal ( 16 arc-min/sec or 0.2674°/sec) +#define ETXSlew6 120 // 30 arc-min/sec or 0.5°/sec +#define ETXSlew7 240 // 60 arc-min/sec or 1.0°/sec +#define ETXSlew8 600 // 150 arc-min/sec or 2.5°/sec +#define ETXSlew9 1080 // 270 arc-min/sec or 4.5°/sec + +#define ETXSLOWPOSN 0x00000800 // Point at which to start slowdown + +#define H2X_INPUTPU INPUT_PULLUP // Set pin data input mode +#define H2X_INPUT INPUT // Set pin data input mode +#define H2X_OUTPUT OUTPUT // Set pin data output + +bool HBXGetStatus(unsigned char); + +bool HBXSetMotorState(unsigned char); +bool HBXCheckTargetStatus(unsigned char); +bool HBXUpdatePosn(void); +bool HBXStartMotor(unsigned char); +bool HBXStopMotor(unsigned char); +void PositionPoll(unsigned char); + + +#endif + diff --git a/Software/EQG2HBXWiFi/ETXProtocol.ino b/Software/EQG2HBXWiFi/ETXProtocol.ino new file mode 100644 index 0000000..1631d69 --- /dev/null +++ b/Software/EQG2HBXWiFi/ETXProtocol.ino @@ -0,0 +1,635 @@ +/* + * Copyright 2017, 2018 John Archbold +*/ +#include + +/******************************************************** + ETX Protocol related functions + ============================== + *********************************************************/ + +bool ETXState(unsigned char Motor) { + long distance; +// int s1; + + switch(axis[Motor].ETXMotorState) { + + case ETXIdle: + break; + + case ETXCheckStartup: + if (axis[Motor].ETXMotorStatus & MOVEAXIS) { // Start moving + +//dbgSerial.println(""); dbgSerial.print("ETXCheckStartup - Motor: "); dbgSerial.print(Motor); dbgSerial.print(" MOVE"); + + distance = axis[Motor].Target - axis[Motor].Position; // Distance to target + if (axis[Motor].ETXMotorStatus & MOVEDECR) // If it is decreasing + distance = TwosComplement(distance); + +//dbgSerial.print(" Distance: "); dbgSerial.print(distance); + + if (axis[Motor].ETXMotorStatus & MOVEHIGH) { // High Speed Slew ? + if (axis[Motor].ETXMotorStatus & MOVESLEW) { + axis[Motor].ETXMotorState = ETXSlewMotor; // Move axis using high speed multiplier + +//dbgSerial.print(" HIGH SLEW"); + + } + } + else { // GoTo or Low Speed Slew + axis[Motor].ETXMotorState = ETXCheckSpeed; + +//dbgSerial.print(" GOTO"); + + } + + if (axis[Motor].MotorControl & GoToHBX) { // Check GoTo? + if (axis[Motor].MotorControl & SlewHBX) { // May need to slew for large changes + axis[Motor].ETXMotorState = ETXSlewMotor; // Slew to M-point + +//dbgSerial.print(" SLEW"); + + } + else { + axis[Motor].ETXMotorState = ETXCheckSpeed; + axis[Motor].TargetSpeed = axis[Motor].DEGREERATE1; // Set initial speed for 'HIGH SPEED GOTO' + if (distance < axis[Motor].OneDegree) + axis[Motor].TargetSpeed = (axis[Motor].TargetSpeed >> 1); + if (distance < (axis[Motor].OneDegree >> 2)) + axis[Motor].TargetSpeed = (axis[Motor].TargetSpeed >> 1); + axis[Motor].Speed = 0; // Starting from 0 + +//dbgSerial.print(" STEP"); + } + if (distance < OffsetMax) { // Check for really small moves (< 16 steps) + axis[Motor].ETXMotorState = ETXMotorEnd; // Use Adjust offset + +//dbgSerial.print(" OFFSET"); + } + if (distance > (axis[Motor].OneDegree << 3)) { // Always slew for > 8 degrees + axis[Motor].ETXMotorState = ETXSlewMotor; +//dbgSerial.print(" GoToSLEW"); + } + } + } + break; + + case ETXSlewMotor: + +//dbgSerial.println(""); dbgSerial.print("ETXSlewMotor Motor: "); dbgSerial.print(Motor); dbgSerial.print(" SLEW Cmd: "); + + digitalWrite(SCOPELED, HIGH); // Turn on the LED + HBXSendCommand(Stop, Motor); // Stop the motor + + if (axis[Motor].ETXMotorStatus & MOVEDECR) // -ve i.e. -CCW + axis[Motor].Command = SlewReverse; + else + axis[Motor].Command = SlewForward; +//dbgSerial.print(axis[Motor].Command, HEX); + + // Send the command to the ETX + HBXSendCommand(axis[Motor].Command, Motor); // SLEW + + axis[Motor].EQGMotorStatus |= MOVEAXIS; // Tell EQx + axis[Motor].Speed = axis[Motor].DEGREERATE1; // Set "current speed" for later speed checks + + if (axis[Motor].MotorControl & GoToHBX) { // Check if slew was caused by a high speed long distance GoTo + distance = axis[Motor].Target - axis[Motor].Position; // Check Distance to target + if (axis[Motor].ETXMotorStatus & MOVEDECR) // If it is decreasing + distance = TwosComplement(distance); + if (distance < axis[Motor].OneDegree) { // Change to GoTo if within one degree + while(!(HBXSendCommand(Stop, Motor))); // Stop the motor command + axis[Motor].ETXMotorState = ETXStepMotor; + axis[Motor].MotorControl |= SpeedHBX; // Use 0x01 command for next speed + axis[Motor].TargetSpeed = axis[Motor].DEGREERATE1; // Set initial speed + axis[Motor].Speed = axis[Motor].DEGREERATE1; + axis[Motor].SpeedState = 0; +//dbgSerial.print(" GoToSLEW END"); + } + } + if (axis[Motor].MotorControl & SlewHBX) { // Slewing to M-point + axis[Motor].Speed = axis[Motor].DEGREERATE1; // Indicate current speed (approx) + axis[Motor].ETXMotorState = ETXCheckSlowDown; // Slew until SlowDown + } + +/* +dbgSerial.println(""); dbgSerial.print("ETXSlewMotor Motor: "); dbgSerial.print(Motor); +dbgSerial.print(" "); +dbgSerial.print(", Cmd: "); dbgSerial.print(axis[Motor].Command, HEX); +dbgSerial.print(", Pos: "); dbgSerial.print(axis[Motor].Position, HEX); +dbgSerial.print(", Inc: "); dbgSerial.print(axis[Motor].Increment, HEX); +dbgSerial.print("->Tgt: "); dbgSerial.print(axis[Motor].Target, HEX); +dbgSerial.print(" Speed: "); dbgSerial.print(axis[Motor].Speed, HEX); +dbgSerial.print(" TargetSpeed: "); dbgSerial.print(axis[Motor].TargetSpeed, HEX); +dbgSerial.print(" SpeedState: "); dbgSerial.println(axis[Motor].SpeedState, HEX); +*/ + break; + + case ETXStepMotor: + +//dbgSerial.println(""); dbgSerial.print("ETXStepMotor Motor: "); dbgSerial.print(Motor); dbgSerial.print(" STEP Cmd: "); + + digitalWrite(SCOPELED, HIGH); // Turn on the LED + if (axis[Motor].MotorControl & SpeedHBX) // Stepping, High or Low speed + axis[Motor].Command = SpeedStart; // Use SpeedStart to start motion + else { + axis[Motor].Command = SpeedChnge; // Use SpeedChnge once started + } + axis[Motor].MotorControl &= ~SpeedHBX; // Clear flag + // Set the speed, and direction + // ---------------------------- + P1 = axis[Motor].Speed; + if (axis[Motor].ETXMotorStatus & MOVEDECR) // If negative, change P + P1 = TwosComplement(P1); // to 2's complement + + axis[Motor].HBXP1 = (P1 >> 16) & 0xFF; // Initialize command bytes + axis[Motor].HBXP2 = (P1 >> 8) & 0xFF; + axis[Motor].HBXP3 = P1 & 0xFF; + +//dbgSerial.print(axis[Motor].Command, HEX); + + // Send the command + // ---------------- + if (HBXSendCommand(axis[Motor].Command, Motor)) // Command OK? + HBXSend3Bytes(Motor); // Send the speed + axis[Motor].EQGMotorStatus |= MOVEAXIS; // Tell EQx + + axis[Motor].ETXMotorState = ETXCheckSpeed; // Make sure we are up to target speed + if (axis[Motor].MotorControl & GoToHBX) { // If it is a GoTo and up to speed, check position + if (axis[Motor].Speed == axis[Motor].TargetSpeed) + axis[Motor].ETXMotorState = ETXCheckPosition; + } + else if (axis[Motor].Speed == 0) { // Stop issued + axis[Motor].ETXMotorState = ETXStopMotor; + } + else if (axis[Motor].Speed == axis[Motor].TargetSpeed) { // Else slewing at speed + axis[Motor].ETXMotorState = ETXIdle; + } + break; + + case ETXCheckSlowDown: + // Check if Slowdown reached + // Calculate absolute distance to slowdown + // --------------------------------------- + +/* +dbgSerial.println(""); dbgSerial.print("ETXCheckSlowDown Motor: "); dbgSerial.print(Motor); +dbgSerial.print(" "); +dbgSerial.print(", Cmd: "); dbgSerial.print(axis[Motor].Command, HEX); +dbgSerial.print(", Pos: "); dbgSerial.print(axis[Motor].Position, HEX); +dbgSerial.print(", SD: "); dbgSerial.print(axis[Motor].SlowDown, HEX); +dbgSerial.print("->Tgt: "); dbgSerial.print(axis[Motor].Target, HEX); +dbgSerial.print(" Speed: "); dbgSerial.print(axis[Motor].Speed, HEX); +dbgSerial.print(" TargetSpeed: "); dbgSerial.print(axis[Motor].TargetSpeed, HEX); +*/ +// distance = axis[Motor].SlowDown - axis[Motor].Position; + distance = (axis[Motor].Target - 0x1000) - axis[Motor].Position; // Distance to target + if (axis[Motor].Target < axis[Motor].Position) // If it is decreasing + distance = TwosComplement(distance); + +//dbgSerial.print(" distance: "); +//dbgSerial.print(distance, HEX); + + if (distance <= 0) { + while(!(HBXSendCommand(Stop, Motor))); // Stop the motor +// HBXSendCommand(Stop, Motor); // Stop the motor + axis[Motor].TargetSpeed = (axis[Motor].SIDEREALRATE << 7); // target is 128xSidereal + axis[Motor].ETXMotorState = ETXCheckSpeed; + axis[Motor].MotorControl &= ~SlewHBX; // Clear slew bit (if it was set) + axis[Motor].MotorControl |= SpeedHBX; // Use 0x01 command for first slow-down + } + break; + + case ETXCheckSpeed: + // Speeding Up + // =========== +/* +ETXSlew1 1 // 1 x sidereal (0.25 arc-min/sec or 0.0042°/sec) +ETXSlew2 2 // 2 x sidereal (0.50 arc-min/sec or 0.0084°/sec) +ETXSlew3 8 // 8 x sidereal ( 2 arc-min/sec or 0.0334°/sec) +ETXSlew4 16 // 16 x sidereal ( 4 arc-min/sec or 0.0669°/sec) +ETXSlew5 64 // 64 x sidereal ( 16 arc-min/sec or 0.2674°/sec) +ETXSlew6 120 // 30 arc-min/sec or 0.5°/sec +ETXSlew7 240 // 60 arc-min/sec or 1.0°/sec +ETXSlew8 600 // 150 arc-min/sec or 2.5°/sec +ETXSlew9 1080 // 270 arc-min/sec or 4.5°/sec +*/ + +/* +dbgSerial.println(""); dbgSerial.print("ETXCheckSpeed Motor: "); dbgSerial.print(Motor); +dbgSerial.print(" "); +dbgSerial.print(", Pos: "); dbgSerial.print(axis[Motor].Position, HEX); +dbgSerial.print(", Inc: "); dbgSerial.print(axis[Motor].Increment, HEX); +dbgSerial.print("->Tgt: "); dbgSerial.print(axis[Motor].Target, HEX); +dbgSerial.print(" iSpeed: "); dbgSerial.print(axis[Motor].Speed, HEX); +dbgSerial.print(" iTargetSpeed: "); dbgSerial.print(axis[Motor].TargetSpeed, HEX); +*/ + axis[Motor].ETXMotorState = ETXStepMotor; // Preset set speed as next action + +// Ramp up to speed + if ((axis[Motor].TargetSpeed != 0) && (axis[Motor].TargetSpeed > axis[Motor].Speed)) { + if ((axis[Motor].TargetSpeed - axis[Motor].Speed) > (axis[Motor].SIDEREALRATE << 6)) { // 64x sidereal + axis[Motor].Speed += ((axis[Motor].TargetSpeed - axis[Motor].Speed) >> 1); // Ramp up approx .5 difference +// while(!(HBXSendCommand(Stop, Motor))); // Stop the motor command + axis[Motor].MotorControl &= ~SpeedHBX; // Use 0x00 command + } + else { + axis[Motor].Speed = axis[Motor].TargetSpeed; + while(!(HBXSendCommand(Stop, Motor))); // Stop the motor command + axis[Motor].MotorControl |= SpeedHBX; // Use 0x01 command + } + } +// Ramp down to speed + else if ((axis[Motor].TargetSpeed != 0) && (axis[Motor].Speed > axis[Motor].TargetSpeed)) { + axis[Motor].Speed -= ((axis[Motor].Speed - axis[Motor].TargetSpeed) >> 2); // Approx .75 + if ((axis[Motor].Speed - axis[Motor].TargetSpeed) <= (axis[Motor].SIDEREALRATE << 7)) { + axis[Motor].Speed = axis[Motor].TargetSpeed; // Close enough at 128x sidereal, so set the speed +// while(!(HBXSendCommand(Stop, Motor))); // Stop the motor command + axis[Motor].MotorControl &= ~SpeedHBX; // Use 0x00 command + } + } +// Ramp down to stop + else if ((axis[Motor].TargetSpeed == 0) && (axis[Motor].Speed != 0)) { + if (axis[Motor].ETXMotorStatus & MOVESLEW) { + axis[Motor].ETXMotorState = ETXStopMotor; + } + else if (axis[Motor].Speed >= (axis[Motor].SIDEREALRATE << 7)) { // Ramp down to 128x sidereal + axis[Motor].Speed -= (axis[Motor].Speed >> 2); // Approximately .75 + while(!(HBXSendCommand(Stop, Motor))); // Stop the motor command + axis[Motor].MotorControl |= SpeedHBX; // Use 0x01 command + } + else + axis[Motor].ETXMotorState = ETXStopMotor; // OK, Stop the motor + } + +// Switch to position check, when we are at speed - check done in ETXStepMotor + +//dbgSerial.print(" oSpeed: "); dbgSerial.print(axis[Motor].Speed, HEX); +//dbgSerial.print(" oTargetSpeed: "); dbgSerial.print(axis[Motor].TargetSpeed, HEX); + + break; + + case ETXCheckPosition: + // Check if Target acquired + // ------------------------ + + // Calculate absolute distance to target + // ------------------------------------- + +/* +dbgSerial.println(""); dbgSerial.print("ETXCheckPosition Motor: "); dbgSerial.print(Motor); +dbgSerial.print(" "); +dbgSerial.print(", Pos: "); dbgSerial.print(axis[Motor].Position, HEX); +dbgSerial.print(", Inc: "); dbgSerial.print(axis[Motor].Increment, HEX); +dbgSerial.print("->Tgt: "); dbgSerial.print(axis[Motor].Target, HEX); +dbgSerial.print(" Speed: "); dbgSerial.print(axis[Motor].Speed, HEX); +dbgSerial.print(" TargetSpeed: "); dbgSerial.print(axis[Motor].TargetSpeed, HEX); +dbgSerial.print(" SpeedState: "); dbgSerial.print(axis[Motor].SpeedState, HEX); +*/ + if (!(axis[Motor].MotorControl & GoToHBX)) { // Slewing so update position + break; + } + + distance = axis[Motor].Target - axis[Motor].Position; // Distance to target + if (axis[Motor].ETXMotorStatus & MOVEDECR) // If it is decreasing + distance = TwosComplement(distance); + + if (distance == 0) + axis[Motor].ETXMotorState = ETXMotorEnd; + else if (distance > 0) { + // Start to slow motor if getting near target + // ------------------------------------------ + if (distance <= OffsetMax) { + axis[Motor].ETXMotorState = ETXMotorEnd; // Stop motor, set offset + axis[Motor].SpeedState = 0; + } + else if (axis[Motor].SpeedState == 3) { + axis[Motor].TargetSpeed = (axis[Motor].SIDEREALRATE) << 2; + axis[Motor].MotorControl &= ~SpeedHBX; // Use 0x00 command + axis[Motor].ETXMotorState = ETXStepMotor; // Change speed + axis[Motor].SpeedState += 1; + } + else if ((distance <= 0x100) && (axis[Motor].SpeedState == 2)) { + axis[Motor].TargetSpeed = axis[Motor].Speed >> 2; // 1/16 + axis[Motor].MotorControl &= ~SpeedHBX; // Use 0x00 command + axis[Motor].ETXMotorState = ETXStepMotor; // Change speed + axis[Motor].SpeedState += 1; + } + else if ((distance <= 0x200) && (axis[Motor].SpeedState == 1)) { + axis[Motor].TargetSpeed = axis[Motor].Speed >> 1; // 1/4 + axis[Motor].MotorControl &= ~SpeedHBX; // Use 0x00 command + axis[Motor].ETXMotorState = ETXStepMotor; // Change speed + axis[Motor].SpeedState += 1; + } + else if ((distance <= 0x400) && (axis[Motor].SpeedState == 0)) { + axis[Motor].TargetSpeed = axis[Motor].Speed >> 1; // 1/2 + while(!(HBXSendCommand(Stop, Motor))); // Stop the motor command + axis[Motor].MotorControl |= SpeedHBX; // Use 0x01 command + axis[Motor].ETXMotorState = ETXStepMotor; // Change speed + axis[Motor].SpeedState += 1; + } + } + else { + if ((TwosComplement(distance)) > OffsetMax) { // Not sure how good offset is! + // Motor has over-shot the target + // ------------------------------ + if (axis[Motor].ETXMotorStatus & MOVEDECR) // ETX -> change direction + axis[Motor].ETXMotorStatus &= ~MOVEDECR; + else + axis[Motor].ETXMotorStatus |= MOVEDECR; + while(!(HBXSendCommand(Stop, Motor))); // Stop the motor command + axis[Motor].MotorControl |= SpeedHBX; // Use 0x01 command + axis[Motor].TargetSpeed = (axis[Motor].SIDEREALRATE) << 2; + axis[Motor].ETXMotorState = ETXStepMotor; // Change ETX speed + } + else{ + axis[Motor].ETXMotorState = ETXMotorEnd; // Stop motor, set offset + axis[Motor].SpeedState = 0; + } + } + break; + + case ETXStopMotor: + +//dbgSerial.println(""); dbgSerial.print("ETXStopMotor Motor: "); dbgSerial.print(Motor); + + while(!(HBXSendCommand(Stop, Motor))); // Stop the motor + axis[Motor].ETXMotorStatus |= MOVESLEW; // ETX Set slewing mode + axis[Motor].ETXMotorStatus &= ~MOVEHIGH; // and speed + axis[Motor].ETXMotorStatus &= ~MOVEAXIS; // Clear the motor moving flag + axis[Motor].MotorControl &= ~GoToHBX; // Clear the GoTo flag + axis[Motor].MotorControl &= ~SlewHBX; // and the slew flag + axis[Motor].EQGMotorStatus |= MOVESLEW; // EQG Set slewing mode + axis[Motor].EQGMotorStatus &= ~MOVEHIGH; // and speed + axis[Motor].EQGMotorStatus &= ~MOVEAXIS; // Clear the motor moving flag + axis[Motor].ETXMotorState = ETXCheckStartup; + axis[Motor].TargetSpeed = axis[Motor].Speed; // For any subsequent move + axis[Motor].Speed = 0; + break; + + case ETXMotorEnd: +/* +dbgSerial.println(""); dbgSerial.print("ETXMotorEnd Motor: "); dbgSerial.print(Motor); +dbgSerial.print(" "); +dbgSerial.print(", Pos: "); dbgSerial.print(axis[Motor].Position, HEX); +dbgSerial.print(", Inc: "); dbgSerial.print(axis[Motor].Increment, HEX); +dbgSerial.print("->Tgt: "); dbgSerial.print(axis[Motor].Target, HEX); +*/ + digitalWrite(SCOPELED, LOW); // Turn off the LED + distance = axis[Motor].Target - axis[Motor].Position; // Distance to target + if (axis[Motor].Target < axis[Motor].Position) // If it is decreasing + distance = TwosComplement(distance); + + if (distance == 0) { + axis[Motor].ETXMotorState = ETXStopMotor; // Stop the motor + } + else { + axis[Motor].HBXP1 = (distance >> 8) & 0xFF; // Initialize offset bytes + axis[Motor].HBXP2 = distance & 0xFF; + axis[Motor].Command = SetOffset; + if (HBXSendCommand(axis[Motor].Command, Motor)) // Command OK? + HBXSend2Bytes(Motor); // Send the offset + +/* +dbgSerial.print(" OFFSET"); +dbgSerial.print(" "); dbgSerial.print(axis[Motor].Command, HEX); +dbgSerial.print(" "); dbgSerial.print(axis[Motor].HBXP1, HEX); +dbgSerial.print(" "); dbgSerial.print(axis[Motor].HBXP2, HEX); +dbgSerial.print(" "); dbgSerial.print(axis[Motor].HBXP3, HEX); +*/ + } + axis[Motor].Position = axis[Motor].Target; + axis[Motor].MotorControl &= ~GoToHBX; // Clear the flag + axis[Motor].ETXMotorState = ETXStopMotor; + break; + + default: + break; + } + return(true); +} + +// Motor functions + +bool HBXGetStatus(unsigned char Motor) { + if (!HBXSendCommand(GetStatus, Motor)) { + dbgSerial.println(""); dbgSerial.print("HBXGetStatus Motor: "); dbgSerial.print(Motor); dbgSerial.println(" Cmd Fail"); + return(false); + } + HBXGet3Bytes(Motor); + P1 = (axis[Motor].HBXP1 << 8); // Update calculated position + P1 |= axis[Motor].HBXP2; // Convert to 16bits + if (axis[Motor].HBXP1 & 0x80) + P1 |= 0xffff0000; // Sign extend HBXP1 for 2s complement + axis[Motor].Position += P1; + + // Wrap the position if above (+12H or +90deg) + if (axis[Motor].Position >= (ETX_CENTRE + (axis[Motor].aVALUE >> 1))) + axis[Motor].Position -= axis[Motor].aVALUE; + // Wrap the position if below (-12H or -90deg) + if (axis[Motor].Position <= (ETX_CENTRE - (axis[Motor].aVALUE >> 1))) + axis[Motor].Position += axis[Motor].aVALUE; + + if (StatusCount++ > 51) { + StatusCount = 0; + dbgSerial.println(""); dbgSerial.print("Motor: "); dbgSerial.print(Motor); dbgSerial.print(", Posn: "); dbgSerial.print(axis[Motor].Position, HEX); + } + return(true); +} + +bool HBXGet2Status(void) { + int i; + do { + i = 0; + if (HBXGetStatus(AzMotor)) i += 1; + TimerDelayuS(HBXBitTime << 2); + + if (HBXGetStatus(AltMotor)) i += 1; + TimerDelayuS(HBXBitTime << 2); + + } while (i < 2); + return(true); +} + +// Calibrate motor LED +// =================== +void CalibrateLEDs(void) { + + + while (!(HBXSendCommand(Stop, AzMotor))); // Stop both motors + TimerDelaymS(100); + while (!(HBXSendCommand(Stop, AltMotor))); + TimerDelaymS(100); + + while (!(HBXGetStatus(AzMotor))) + TimerDelaymS(100); + while (!(HBXGetStatus(AltMotor))) + TimerDelaymS(100); + + HBXSendCommand(SleepHBX, AzMotor); // Issue Sleep + TimerDelaymS(100); + HBXSendCommand(CalibrateLED, AzMotor); // Issue Calibrate LED + TimerDelaymS(4000); + + HBXSendCommand(SleepHBX, AltMotor); + TimerDelaymS(100); + HBXSendCommand(CalibrateLED, AltMotor); + TimerDelaymS(4000); + + HBXSendCommand(GetLEDI, AzMotor); HBXGetByte(AzMotor); + HBXSendCommand(GetLEDI, AltMotor); HBXGetByte(AltMotor); + + while (!(HBXGetStatus(AzMotor))) + TimerDelaymS(100); + while (!(HBXGetStatus(AltMotor))) + TimerDelaymS(100); + + // Read the calibration + dbgSerial.println(""); dbgSerial.print("Read LEDs - AzMotor: "); + if (HBXSendCommand(GetLEDI, AzMotor)) + axis[AzMotor].HBXLEDI = HBXGetByte(AzMotor); + dbgSerial.print(axis[AzMotor].HBXLEDI); + dbgSerial.print(", AltMotor: "); + if (HBXSendCommand(GetLEDI, AltMotor)) + axis[AltMotor].HBXLEDI = HBXGetByte(AltMotor); + dbgSerial.print(axis[AltMotor].HBXLEDI); + + // Save it to preferences + // Prefences.begin must be set by the caller + preferences.putUChar("AzLEDI", axis[AzMotor].HBXLEDI); + preferences.putUChar("AltLEDI", axis[AltMotor].HBXLEDI); +} + +void WaitForMotors(void) { +// GetLED commands always return a vaild value - motors not online until this happens +// "Valid" values are not 0 and not 0xFF for Az, Alt. (exception here is if LEDRA || LEDAlt == 0xff) +// + bool PAz = false; + bool PAlt = false; + do { + if (!PAz) { + if (HBXSendCommand(GetLEDI, AzMotor)) + axis[AzMotor].HBXLEDI = HBXGetByte(AzMotor); +// dbgSerial.print("Az HBXLEDI: "); dbgSerial.println(axis[AzMotor].HBXLEDI); + TimerDelaymS(CMNDTIME); + if ((axis[AzMotor].HBXLEDI != 0) && (axis[AzMotor].HBXLEDI != 0xFF)) + PAz = true; + } + if (!PAlt) { + if (HBXSendCommand(GetLEDI, AltMotor)) + axis[AltMotor].HBXLEDI = HBXGetByte(AltMotor); + TimerDelaymS(CMNDTIME); +// dbgSerial.print("Alt HBXLEDI: "); dbgSerial.println(axis[AltMotor].HBXLEDI); + if ((axis[AltMotor].HBXLEDI != 0) && (axis[AltMotor].HBXLEDI != 0xFF)) + PAlt = true; + } + // Attempt to reset Motor Controller + if (!PAz) + HBXMotorReset(AzMotor); + if (!PAlt) + HBXMotorReset(AltMotor); + } while ((!PAz) || (!PAlt)); +} + +void AzInitialise(unsigned char scope) { +// Telescope specific +// telescope steps + axis[AzMotor].Vanes = ratio[scope][AzMotor-1].Vanes; + axis[AzMotor].GbxRatio = ratio[scope][AzMotor-1].GbxRatio; + axis[AzMotor].XferRatio = ratio[scope][AzMotor-1].XferRatio; + axis[AzMotor].WormTeeth = ratio[scope][AzMotor-1].WormTeeth; +// EQMOD values + axis[AzMotor].aVALUE = axis[AzMotor].Vanes * (float)4 * axis[AzMotor].GbxRatio * axis[AzMotor].XferRatio * axis[AzMotor].WormTeeth; + axis[AzMotor].MeadeRatio = axis[AzMotor].aVALUE / ArcSecs360; // Distance for one arcsec + axis[AzMotor].bVALUE = (MeadeSidereal * axis[AzMotor].MeadeRatio * axis[AzMotor].aVALUE * SiderealArcSecs) / ArcSecs360; + axis[AzMotor].SIDEREALRATE = MeadeSidereal * axis[AzMotor].MeadeRatio; + axis[AzMotor].SOLARRATE = axis[AzMotor].SIDEREALRATE * SOLARSECS / SIDEREALSECS; + axis[AzMotor].LUNARRATE = axis[AzMotor].SIDEREALRATE * LUNARSECS / SIDEREALSECS; + axis[AzMotor].DEGREERATE1 = axis[AzMotor].SIDEREALRATE * ETXSlew7; + axis[AzMotor].PEC = axis[AzMotor].aVALUE / axis[AzMotor].WormTeeth; +// ETX + axis[AzMotor].HBXP1 = 0x00; + axis[AzMotor].HBXP2 = 0x00; + axis[AzMotor].HBXP3 = 0x00; + axis[AzMotor].HBXP4 = 0x00; + axis[AzMotor].Position = ETX_CENTRE; // ETX RA initially at 0 hours + axis[AzMotor].OneDegree = axis[AzMotor].aVALUE / (float)360; // Distance for one degree + axis[AzMotor].Target = axis[AzMotor].Position; + axis[AzMotor].DirnSpeed = 0x000; + axis[AzMotor].ETXMotorStatus = MOVESLEW; + axis[AzMotor].EQGMotorStatus = MOVESLEW; + axis[AzMotor].ETXMotorState = ETXCheckStartup; +} + +void AltInitialise(unsigned char scope) { +// Telescope specific +// telescope steps + axis[AltMotor].Vanes = ratio[scope][AltMotor-1].Vanes; + axis[AltMotor].GbxRatio = ratio[scope][AltMotor-1].GbxRatio; + axis[AltMotor].XferRatio = ratio[scope][AltMotor-1].XferRatio; + axis[AltMotor].WormTeeth = ratio[scope][AltMotor-1].WormTeeth; +// EQMOD values + axis[AltMotor].aVALUE = axis[AltMotor].Vanes * (float)4 * axis[AltMotor].GbxRatio * axis[AltMotor].XferRatio * axis[AltMotor].WormTeeth; + axis[AltMotor].MeadeRatio = axis[AltMotor].aVALUE / ArcSecs360; // Distance for one arcsec + axis[AltMotor].bVALUE = MeadeSidereal * axis[AltMotor].MeadeRatio * axis[AltMotor].aVALUE * SiderealArcSecs / ArcSecs360; + axis[AltMotor].SIDEREALRATE = MeadeSidereal * axis[AltMotor].MeadeRatio; + axis[AltMotor].SOLARRATE = axis[AltMotor].SIDEREALRATE * SOLARSECS / SIDEREALSECS; + axis[AltMotor].LUNARRATE = axis[AltMotor].SIDEREALRATE * LUNARSECS / SIDEREALSECS; + axis[AltMotor].DEGREERATE1 = axis[AltMotor].SIDEREALRATE * ETXSlew7; + axis[AltMotor].PEC = axis[AltMotor].aVALUE / axis[AltMotor].WormTeeth; +// ETX + axis[AltMotor].HBXP1 = 0x00; + axis[AltMotor].HBXP2 = 0x00; + axis[AltMotor].HBXP3 = 0x00; + axis[AltMotor].HBXP4 = 0x00; + axis[AltMotor].Position = ETX_CENTRE - (axis[AltMotor].aVALUE >> 2); // Initially at -90 degrees + axis[AltMotor].OneDegree = axis[AltMotor].aVALUE / (float)360; // Distance for one degree + axis[AltMotor].Target = axis[AltMotor].Position; + axis[AltMotor].DirnSpeed = 0x000; + axis[AltMotor].ETXMotorStatus = MOVESLEW; + axis[AltMotor].EQGMotorStatus = MOVESLEW; + axis[AltMotor].ETXMotorState = ETXCheckStartup; +} + +void PrintHbxValues(unsigned char Motor) { + if (Motor == AzMotor) + dbgSerial.println("AzMotor"); + else + dbgSerial.println("AltMotor"); + + dbgSerial.print("Vanes "); dbgSerial.print(axis[Motor].Vanes); + dbgSerial.print(", GbxRatio "); dbgSerial.print(axis[Motor].GbxRatio,4); + dbgSerial.print(", XferRatio "); dbgSerial.print(axis[Motor].XferRatio,4); + dbgSerial.print(", WormTeeth "); dbgSerial.println(axis[Motor].WormTeeth); + dbgSerial.print("MeadeRatio "); dbgSerial.print(axis[Motor].MeadeRatio,6); + dbgSerial.print(", MeadeSidereal "); dbgSerial.println(MeadeSidereal,4); + + dbgSerial.print("aVALUE 0x"); dbgSerial.print(axis[Motor].aVALUE, HEX); + dbgSerial.print(", bVALUE 0x"); dbgSerial.print(axis[Motor].bVALUE, HEX); + dbgSerial.print(", PEC 0x"); dbgSerial.println(axis[Motor].PEC, HEX); + + dbgSerial.print("SIDEREALRATE 0x"); dbgSerial.print(axis[Motor].SIDEREALRATE, HEX); + dbgSerial.print(", SOLARRATE 0x"); dbgSerial.print(axis[Motor].SOLARRATE, HEX); + dbgSerial.print(", LUNARRATE 0x"); dbgSerial.print(axis[Motor].LUNARRATE, HEX); + dbgSerial.print(", DEGREERATE1 0x"); dbgSerial.println(axis[Motor].DEGREERATE1, HEX); + + dbgSerial.print("One DEGREE 0x"); dbgSerial.println(axis[Motor].OneDegree, HEX); + dbgSerial.println(""); +} + +void PrintRatioValues(unsigned char scope) { + int j; + float r; + for (j = 0; j < 2; j++) { + if (j == 0) + dbgSerial.print("AzMotor: "); + else + dbgSerial.print("AltMotor: "); + dbgSerial.print("Vanes "); dbgSerial.print(ratio[scope][j].Vanes); + dbgSerial.print(", GbxRatio "); dbgSerial.print(ratio[scope][j].GbxRatio,4); + dbgSerial.print(", XferRatio "); dbgSerial.print(ratio[scope][j].XferRatio,4); + dbgSerial.print(", WormTeeth "); dbgSerial.print(ratio[scope][j].WormTeeth); + r = (ratio[scope][j].Vanes * (float) 4 * ratio[scope][j].GbxRatio * ratio[scope][j].XferRatio * ratio[scope][j].WormTeeth) / (float) 1296000; + dbgSerial.print(", MeadeRatio "); dbgSerial.println(r,6); + } +} diff --git a/Software/EQG2HBXWiFi/HBXComms.h b/Software/EQG2HBXWiFi/HBXComms.h new file mode 100644 index 0000000..43fa4ae --- /dev/null +++ b/Software/EQG2HBXWiFi/HBXComms.h @@ -0,0 +1,85 @@ +/* + * Copyright 2017, 2018 John Archbold +*/ +#include + +/******************************************************** + EQG Protocol function definitions + ================================= + *********************************************************/ +#ifndef HBXComms +#define HBXComms + +#include + + // Serial port definitions for HBX interface + // ========================================= + +#ifdef mESP32 +#define dbgSerial Serial +#define EQGSerial Serial2 +#endif + +// Pin definitions for HBX interface +// ================================= + +#ifdef mESP32 +#define HDA1 27 // Pin2 on HBX interface +#define HCL1 26 // Pin3 on HBX interface +#define HDA2 14 // Pin4 on HBX interface +#define HCL2 25 // Pin5 on HBX interface +#define HDAX 14 // Pin6 on HBX interface +#define HCLX 25 // Pin7 on HBX interface +#endif + +#define CR 0x0d +#define LF 0x0a + +#define HBXLEN 16 +#define HBXMASK HBXLEN-1 + +#define H2XRESETTIME 25 // Reset H2X bus +#define BITTIME 96 // H2X clock ~200uS i.e 100us Low/High + +#define STATUSDELAY 50 // H2X ETX status poll delay (mS) +#define STATEDELAY 6.55 // H2X ETX state poll delay (mS) +#define CMNDTIME 1 // H2X command delay (mS) +#define STARTTIME 50 // H2X startup time for motors +#define CLOCKTIMEOUT 50 // H2X Clock transition timeout (uS) (for monitor mode) +#define MOTORDETECT 500 // H2X Detect Motor controller + +unsigned char HBXBitTime = BITTIME; +void TimerDelayuS(unsigned int); + +bool HBXSendCommand(unsigned char, unsigned char); +void HBXMotorReset(unsigned char); +bool HBXStartSequence(unsigned char); +void HBXSendByte(unsigned char, unsigned char); +unsigned char HBXGetByte(unsigned char); +void HBXSend2Bytes(unsigned char); +void HBXSend3Bytes(unsigned char); +void HBXGet3Bytes(unsigned char); + +void HDAListen(void); +void HDATalk(void); +void HCL1Listen(void); +void HCL1Talk(void); +void HCL2Listen(void); +void HCL2Talk(void); +void HBXReset(void); +//bool ResetMotor(unsigned char); + +long TwosComplement(long); + +//unsigned long eeprom_crc(void); +//unsigned long get_eeprom_crc(void); +//bool set_eeprom_crc(void); +//bool check_eeprom_crc(void); + +// Testing +void HBXTestLoop(void); +void HBXTest(void); +bool HBXGet2Status(void); + +#endif + diff --git a/Software/EQG2HBXWiFi/HBXComms.ino b/Software/EQG2HBXWiFi/HBXComms.ino new file mode 100644 index 0000000..c08761b --- /dev/null +++ b/Software/EQG2HBXWiFi/HBXComms.ino @@ -0,0 +1,294 @@ +/* + * Copyright 2017, 2018 John Archbold +*/ +#include + +/******************************************************** + HBX Comms related functions + =========================== + *********************************************************/ + + // HBX Attempt to reset + // -------------------- +void HBXMotorReset(unsigned char Motor) +{ + /* + // Write LOW + HDATalk(); + digitalWrite(HDA1, LOW); + TimerDelayuS(HBXBitTime); + for (i = 0; i < 8; i++) + { + if (Motor == MotorAz) digitalWrite(HCL1, LOW); + else digitalWrite(HCL2, LOW); + TimerDelayuS(HBXBitTime); + if (Motor == MotorAz) digitalWrite(HCL1, HIGH); + else digitalWrite(HCL2, HIGH); + TimerDelayuS(HBXBitTime); + } + + // Write HIGH + digitalWrite(HDA1, HIGH); + TimerDelayuS(HBXBitTime); + for (i = 0; i < 8; i++) + { + if (Motor == MotorAz) digitalWrite(HCL1, LOW); + else digitalWrite(HCL2, LOW); + TimerDelayuS(HBXBitTime); + if (Motor == MotorAz) digitalWrite(HCL1, HIGH); + else digitalWrite(HCL2, HIGH); + TimerDelayuS(HBXBitTime); + } + + // Read, and discard, a byte + HDAListen(); + for (i = 0; i < 8; i++) { + if (Motor == MotorAz) digitalWrite(HCL1, LOW); + else digitalWrite(HCL2, LOW); + TimerDelayuS(HBXBitTime); + if (Motor == MotorAz) digitalWrite(HCL1, HIGH); + else digitalWrite(HCL2, HIGH); + TimerDelayuS(HBXBitTime); + } + */ + + // Force Clock Low, High for reset, time ~.75s + if (Motor == MotorAz) digitalWrite(HCL1, LOW); + else digitalWrite(HCL2, LOW); + TimerDelaymS(MOTORDETECT); + if (Motor == MotorAz) digitalWrite(HCL1, HIGH); + else digitalWrite(HCL2, HIGH); + TimerDelaymS(MOTORDETECT >> 1); +} + +// HBX transmission functions +// ========================== + +// HBX Send a command +// ------------------ +bool HBXSendCommand(unsigned char Command, unsigned char Motor) { + + if (Command != GetStatus){ + dbgSerial.println(""); dbgSerial.print("+++ "); dbgSerial.print(Motor); + } + axis[Motor].Command = Command; + +// Send the start sequence +// ----------------------- + if (HBXStartSequence(Motor)) { +// Send the command byte +// --------------------- + HBXSendByte(Command, Motor); + return(true); + } + else { + return(false); + } +} + +// HBX Initiate start sequence +// --------------------------- +bool HBXStartSequence(unsigned char Motor) { +// 1. HDA as input + HDAListen(); +// 2. Set clock low + if (Motor == MotorAz) digitalWrite(HCL1, LOW); + else digitalWrite(HCL2, LOW); + TimerDelayuS(HBXBitTime >> 1); // 1/2 bit-time +// 3. Wait for data low (HDA1 = 0) by MC, or timeout + H2XStart = micros(); + do { + H2XTimer = micros() - H2XStart; + } while ((digitalRead(HDA1) == 1) && (H2XTimer < (HBXBitTime << 3))); + TimerDelayuS((HBXBitTime >> 5)); // 1/32 bit-time delay, in case of data line glitch +// 4. Re-read data line, check if (data low) or (MC timeout) + if ((digitalRead(HDA1) == 1) || (H2XTimer >= (HBXBitTime << 3))) { + if (Motor == MotorAz) digitalWrite(HCL1, HIGH); + else digitalWrite(HCL2, HIGH); // Set clock high, and + return(false); // error exit if no response from Motor + } +// 5. Set clock high if data low occurred (i.e. MC acknowledged clock low) + if (Motor == MotorAz) digitalWrite(HCL1, HIGH); + else digitalWrite(HCL2, HIGH); + TimerDelayuS(HBXBitTime >> 1); +// 6. Wait for data line release (HDA1 = 1) by MC, or timeout + H2XStart = micros(); + do { + H2XTimer = micros() - H2XStart; + } while ((digitalRead(HDA1) == 0) && (H2XTimer < (HBXBitTime << 3))); + TimerDelayuS(HBXBitTime); // Wait one bit-time, in case of success +// 7. Check timeout for data line released or no response from MC + if (H2XTimer >= (HBXBitTime << 3)) { + return(false); // Error Exit if no response from MC + } + return(true); // Success +} + +// HBX Send a single byte +// ---------------------- +void HBXSendByte(unsigned char databyte, unsigned char Motor) { + + unsigned char mask; + if (axis[Motor].Command != GetStatus) { + dbgSerial.print("-> "); dbgSerial.print(databyte, HEX); + } + + HDATalk(); // HDA as output + axis[Motor].HBXBitCount = 8; // 8bits to go + mask = 0x80; // MSB first +// Clock was set high before entry + TimerDelayuS(HBXBitTime); + do { + axis[Motor].HBXBitCount -= 1; +// Set data bit + if (databyte & mask) digitalWrite(HDA1, HIGH); + else digitalWrite(HDA1, LOW); + TimerDelayuS(HBXBitTime >> 1); // Let data stabilise + mask = mask >> 1; // Next data bit +// Set clock low + if (Motor == MotorAz) digitalWrite(HCL1, LOW); + else digitalWrite(HCL2, LOW); + TimerDelayuS(HBXBitTime); +// Set clock high + if (Motor == MotorAz) digitalWrite(HCL1, HIGH); + else digitalWrite(HCL2, HIGH); + TimerDelayuS(HBXBitTime-(HBXBitTime >> 1)); // Data is written DSTABLE before clock low +// for 8 bits + } while (axis[Motor].HBXBitCount); + TimerDelayuS(HBXBitTime >> 1); // Last high clock + HDAListen(); + TimerDelayuS(HBXBitTime); +} + +// HBX Send two bytes in sequence +// ------------------------------ +void HBXSend2Bytes(unsigned char Motor) { + HBXSendByte(axis[Motor].HBXP1, Motor); + HBXSendByte(axis[Motor].HBXP2, Motor); +} + +// HBX Send three bytes in sequence +// -------------------------------- +void HBXSend3Bytes(unsigned char Motor) { + HBXSendByte(axis[Motor].HBXP1, Motor); + HBXSendByte(axis[Motor].HBXP2, Motor); + HBXSendByte(axis[Motor].HBXP3, Motor); +} + +// HBX Get a single byte +// ---------------------- +unsigned char HBXGetByte(unsigned char Motor) { + +// HDA as input + HDAListen(); + axis[Motor].HBXBitCount = 8; + axis[Motor].HBXData = 0; +// Clock was set high before entry + while (axis[Motor].HBXBitCount) { +// Set clock low + if (Motor == MotorAz) digitalWrite(HCL1, LOW); + else digitalWrite(HCL2, LOW); + TimerDelayuS(HBXBitTime >> 1); +// Read data bit + axis[Motor].HBXData = axis[Motor].HBXData << 1; // Shift previous bit + if (digitalRead(HDA1)) axis[Motor].HBXData |= 0x01; // Read next bit + axis[Motor].HBXBitCount--; // Need eight bits + TimerDelayuS(HBXBitTime-(HBXBitTime >> 1)); // Wait for low time +// Set clock high + if (Motor == MotorAz) digitalWrite(HCL1, HIGH); + else digitalWrite(HCL2, HIGH); + TimerDelayuS(HBXBitTime); + } + TimerDelayuS(HBXBitTime); + if (axis[Motor].Command != GetStatus) { + dbgSerial.print("<- "); dbgSerial.print(axis[Motor].HBXData, HEX); + } +// Return data byte + axis[Motor].HBXCount = 1; + return (axis[Motor].HBXData); +} + +// HBX Get the status bytes (25 bits) +// ---------------------------------- +void HBXGet3Bytes(unsigned char Motor) { + + axis[Motor].HBXP1 = HBXGetByte(Motor); + TimerDelayuS(HBXBitTime); + axis[Motor].HBXP2 = HBXGetByte(Motor); + TimerDelayuS(HBXBitTime); + axis[Motor].HBXP3 = HBXGetByte(Motor); + TimerDelayuS(HBXBitTime); + axis[Motor].HBXP4 = 0; + +// Read 'byte4' = error bit +// ------------------------ + if (Motor == MotorAz) digitalWrite(HCL1, LOW); + else digitalWrite(HCL2, LOW); + TimerDelayuS(HBXBitTime >> 1); + axis[Motor].HBXP4 |= digitalRead(HDA1); // Read the battery error bit + TimerDelayuS(HBXBitTime-(HBXBitTime >> 1)); + if (Motor == MotorAz)digitalWrite(HCL1, HIGH); + else digitalWrite(HCL2, HIGH); + TimerDelayuS(HBXBitTime); + if (axis[Motor].Command != GetStatus) { + dbgSerial.print("- "); dbgSerial.print(axis[Motor].HBXP4, HEX); + } + axis[Motor].HBXCount = 4; +} + +// H2X Low level Functions +// ----------------------- +void HDAListen() { + digitalWrite(HDA1, HIGH); + pinMode(HDA1, H2X_INPUTPU); +} +void HDAFloat() { + pinMode(HDA1, H2X_INPUT); +} +void HDATalk() { + digitalWrite(HDA1, HIGH); + pinMode(HDA1, H2X_OUTPUT); +} +void HCL1Listen() { + pinMode(HCL1, H2X_INPUT); +} +void HCL1Talk() { + digitalWrite(HCL1, HIGH); + pinMode(HCL1, H2X_OUTPUT); +} +void HCL2Listen() { + pinMode(HCL2, H2X_INPUT); +} +void HCL2Talk() { + digitalWrite(HCL2, HIGH); + pinMode(HCL2, H2X_OUTPUT); +} + +void HBXReset() { + int ClockCount = 0; + // Set clocks high + HCL1Talk(); + HCL2Talk(); + digitalWrite(HCL1, HIGH); + digitalWrite(HCL2, HIGH); + // Set data inbound + HDAListen(); + TimerDelayuS(HBXBitTime); + // Data should be high + while ((!digitalRead(HDA1)) && (ClockCount < 25)) { + digitalWrite(HCL1, LOW); + digitalWrite(HCL2, LOW); + TimerDelayuS(HBXBitTime); + digitalWrite(HCL1, HIGH); + digitalWrite(HCL2, HIGH); + TimerDelayuS(HBXBitTime); + ClockCount += 1; + } +} + +long TwosComplement(long p) { // Calculate 2s complement + long q; + q = ~p; // Bitwise invert + q = q + 1; // +1 + return q; +} diff --git a/Software/EQG2HBXWiFi/HBXFileSystem.h b/Software/EQG2HBXWiFi/HBXFileSystem.h new file mode 100644 index 0000000..a07117c --- /dev/null +++ b/Software/EQG2HBXWiFi/HBXFileSystem.h @@ -0,0 +1,12 @@ +#pragma once +/************************************************************** + * SPIFFS filesystem + * Only invoked in STA mode +*/ + +#include +#include +#include +#include + +#define FORMAT_SPIFFS_IF_FAILED true diff --git a/Software/EQG2HBXWiFi/HBXFileSystem.ino b/Software/EQG2HBXWiFi/HBXFileSystem.ino new file mode 100644 index 0000000..3b52315 --- /dev/null +++ b/Software/EQG2HBXWiFi/HBXFileSystem.ino @@ -0,0 +1,101 @@ +/* SPIFFS functions + */ + +void listDir(fs::FS &fs, const char * dirname, uint8_t levels) { + Serial.printf("Listing directory: %s\r\n", dirname); + + File root = fs.open(dirname); + if (!root) { + Serial.println("- failed to open directory"); + return; + } + if (!root.isDirectory()) { + Serial.println(" - not a directory"); + return; + } + + File file = root.openNextFile(); + while (file) { + if (file.isDirectory()) { + Serial.print(" DIR : "); + Serial.println(file.name()); + if (levels) { + listDir(fs, file.name(), levels - 1); + } + } + else { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print("\tSIZE: "); + Serial.println(file.size()); + } + file = root.openNextFile(); + } +} + +void readFile(fs::FS &fs, const char * path) { + Serial.printf("Reading file: %s\r\n", path); + + File file = fs.open(path); + if (!file || file.isDirectory()) { + Serial.println("- failed to open file for reading"); + return; + } + + Serial.println("- read from file:"); + while (file.available()) { + Serial.write(file.read()); + } +} + +void writeFile(fs::FS &fs, const char * path, const char * message) { + Serial.printf("Writing file: %s\r\n", path); + + File file = fs.open(path, FILE_WRITE); + if (!file) { + Serial.println("- failed to open file for writing"); + return; + } + if (file.print(message)) { + Serial.println("- file written"); + } + else { + Serial.println("- frite failed"); + } +} + +void appendFile(fs::FS &fs, const char * path, const char * message) { + Serial.printf("Appending to file: %s\r\n", path); + + File file = fs.open(path, FILE_APPEND); + if (!file) { + Serial.println("- failed to open file for appending"); + return; + } + if (file.print(message)) { + Serial.println("- message appended"); + } + else { + Serial.println("- append failed"); + } +} + +void renameFile(fs::FS &fs, const char * path1, const char * path2) { + Serial.printf("Renaming file %s to %s\r\n", path1, path2); + if (fs.rename(path1, path2)) { + Serial.println("- file renamed"); + } + else { + Serial.println("- rename failed"); + } +} + +void deleteFile(fs::FS &fs, const char * path) { + Serial.printf("Deleting file: %s\r\n", path); + if (fs.remove(path)) { + Serial.println("- file deleted"); + } + else { + Serial.println("- delete failed"); + } +} diff --git a/Software/EQG2HBXWiFi/HBXWiFiServer.h b/Software/EQG2HBXWiFi/HBXWiFiServer.h new file mode 100644 index 0000000..3eab860 --- /dev/null +++ b/Software/EQG2HBXWiFi/HBXWiFiServer.h @@ -0,0 +1,118 @@ +/* + * Copyright 2017, 2018 John Archbold +*/ + +/******************************************************** + EQG Serial WiFi + =============== + *********************************************************/ +#ifndef HBXWiFiServer +#define HBXWiFiServer + +#include +#include "WiFi.h" +//#include "AsyncUdp.h" +#include "AsyncTCP.h" +#include "ESPAsyncWebServer.h" +#include +#include +#include + +/************************************************************** + * WiFi communications buffers and pointers + * WiFi variables + **************************************************************/ + +uint8_t smac[] = { 0x5C, 0xCF, 0x7F, 0x88, 0x88, 0x88 }; // Hopefully :) Unique Espressif mac +uint8_t mmac[] = { 0x5C, 0xCF, 0x7F, 0x00, 0x00, 0x00 }; // Master mac address +const uint8_t WIFI_CHANNEL = 4; + +String ssid; // char ssid[64] = "EQMODWiFi"; +String pass; // char pass[64] = "CShillit0"; + +const char* http_username = "admin"; +const char* http_password = "eqmod"; +//flag to use from web update to reboot the ESP +bool shouldReboot = false; +bool loginValid = false; + +IPAddress ip(192, 168, 88, 1); +IPAddress netmask(255, 255, 255, 0); + +/************************************************************** + * WiFi WebServer + * Only invoked in STA mode +*/ + +AsyncWebServer server(80); +AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws +AsyncEventSource events("/events"); // event source (Server-Sent events) + +/************************************************************** + * WiFi ESP_NOW +*/ +esp_now_peer_info serial_peer; + +/************************************************************** + * WiFi UDP +*/ +WiFiUDP udp; +IPAddress remoteIp; +const int localUdpPort = 11880; + +/************************************************************** + * WiFi Data Buffers +*/ +struct __attribute__((packed)) DataStruct { + char text[ESP_NOW_MAX_DATA_LEN]; + uint8_t len; +}; + +DataStruct sendWiFi; +DataStruct recvWiFi; +uint8_t sendWiFi8[sizeof(sendWiFi)]; +uint8_t recvWiFi8[sizeof(recvWiFi)]; + +/************************************************************** + * WiFi EQMOD virtualization +*/ +#define EQxTimeout 10 +#define EQxSize ESP_NOW_MAX_DATA_LEN-1 + +uint8_t RxD; +uint8_t TxD; +uint8_t TxDIndex; + +unsigned long RxTimeout; +unsigned long WiFiTimeout; +unsigned long LastmS; +unsigned long CheckmS = 1000; +bool Connected = false; +bool dataSending = false; +bool waitingForReply = false; + +unsigned long TxDuS; +unsigned long AckuS; +unsigned long RxDuS; + +unsigned long LastLEDmS; +unsigned long FastLEDmS = 200; +unsigned long SlowLEDmS = 800; +unsigned long BlinkmS = SlowLEDmS; +unsigned long SavedBlinkmS = SlowLEDmS; + +bool UDPFlag = false; +bool APFlag = false; +bool SerialFlag = false; + +void HBXWiFiSetup(); +bool HBXCheckRx(); +void HBXCheckTx(); +void InitESPNow(); +void recvCallBack(const uint8_t*, const uint8_t* , int ); +void sendData(const esp_now_peer_info_t* ); +void sendCallBack(const uint8_t* , esp_now_send_status_t ); +void putRxDataIntoMountInputBuffer(void); +void getTxDataFromMountOutputBuffer(void); + +#endif // HBXWiFiServer diff --git a/Software/EQG2HBXWiFi/HBXWiFiServer.ino b/Software/EQG2HBXWiFi/HBXWiFiServer.ino new file mode 100644 index 0000000..32a1633 --- /dev/null +++ b/Software/EQG2HBXWiFi/HBXWiFiServer.ino @@ -0,0 +1,484 @@ +// HBXSerialServer.ino +// https://esp-idf.readthedocs.io/en/latest/api-reference/wifi/esp_now.html + +// Init ESP Now with fallback +void InitESPNow() { + WiFi.disconnect(); + if (esp_now_init() == ESP_OK) { + dbgSerial.println("ESPNow Init Success"); + } + else { + dbgSerial.println("ESPNow Init Failed"); + // Retry InitESPNow, add a counter and then restart? + // InitESPNow(); + // or Simply Restart + ESP.restart(); + } +} + +// Handle ESP_NOW WiFi data +// Data in recvWiFi.text, length recvWiFi.len +void recvCallBack(const uint8_t *senderMac, const uint8_t *incomingData, int len) { + +// Get data from WiFi buffer + memcpy(&recvWiFi.text, incomingData, len); // Receive data from EQMOD Tx + recvWiFi.text[len] = 0; // Null terminate + recvWiFi.len = len; + +// Check if serial device is requesting restart + if ((len == 20) && ((strncmp(recvWiFi.text, "Mount, please reply", 15) == 0))) { + // Capture the serial device mac address + for (byte n = 0; n < ESP_NOW_ETH_ALEN; n++) { + serial_peer.peer_addr[n] = senderMac[n]; + } + // Discard data from reconnect request, clear flags + recvWiFi.len = 0; + Connected = false; + dataSending = false; + // Reply to reconnect request + strcpy(sendWiFi.text, "EQMOD WiFi Mount V1.0\n"); + sendWiFi.len = sizeof("EQMOD WiFi Mount V1.0\n"); + dbgSerial.println("Reconnecting"); + sendData(&serial_peer); + return; + } +} + +// Send data to ESP_NOW +void sendData(const esp_now_peer_info_t* s_peer) { + // If first time, create a peer2peer connection with the sender mac address + if (!Connected) { + esp_now_add_peer(s_peer); // Only one paired device - the first one to respond + } + + // Send data, if not waiting for previous send to complete + if (!dataSending) { + memcpy(sendWiFi8, &sendWiFi, sendWiFi.len); // Need to satisfy esp_now + esp_now_send(serial_peer.peer_addr, sendWiFi8, sendWiFi.len); + dataSending = true; + } +} + +// Get Send data status +void sendCallBack(const uint8_t* mac, esp_now_send_status_t sendStatus) { + if (sendStatus == 0) { + sendWiFi.len = 0; // Data successfully sent + Connected = true; + dataSending = false; + } + // Do some error checking? +} + +// Put received WiFi (UDP/ESP_NOW) data into the mount input data buffer for processing +// Data in recvWiFi.text, length recvWiFi.len +void putRxDataIntoMountInputBuffer(void) { + uint8_t n; + n = 0; + while (n < recvWiFi.len) { + EQGRxBuffer[EQGRxiPtr++] = recvWiFi.text[n]; + EQGRxiPtr &= EQGMASK; + n += 1; + } + recvWiFi.len = 0; +} + +// Get mount data to send to WiFi (UDP/NOW) into WiFi buffer +// Data in sendWiFi.text, length sendWiFi.len +void getTxDataFromMountOutputBuffer(void) { + while (EQGTxoPtr != EQGTxiPtr) { + dbgSerial.write(EQGTxBuffer[EQGTxoPtr]); + sendWiFi.text[TxDIndex++] = EQGTxBuffer[EQGTxoPtr++]; + EQGTxoPtr &= EQGMASK; + } + // Send when a CR is detected + if (sendWiFi.text[TxDIndex - 1] == 0x0d) { + sendWiFi.text[TxDIndex] = 0; + sendWiFi.len = TxDIndex; + } +} + +// Webserver functions for ssid, pass, telescope etc +// ================================================= + +void onRequest(AsyncWebServerRequest *request){ + //Handle Unknown Request + request->send(404, "text/plain", "The content you are looking for was not found."); +} + +void onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){ + if (!index) { + dbgSerial.printf("BodyStart: %u B\n", total); + } + for (size_t i = 0; i < len; i++) { + dbgSerial.write(data[i]); + } + if (index + len == total) { + dbgSerial.printf("BodyEnd: %u B\n", total); + } +} + +void onUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + if (!index) { + dbgSerial.printf("UploadStart: %s\n", filename.c_str()); + } + for (size_t i = 0; i < len; i++) { + dbgSerial.write(data[i]); + } + if (final) { + dbgSerial.printf("UploadEnd: %s, %u B\n", filename.c_str(), index + len); + } +} + +void AsyncServerResponseSetup(void) { + + // respond to GET requests on URL /scan + //First request will return 0 results unless you start scan from somewhere else (loop/setup) + //Do not request more often than 3-5 seconds + + server.on("/scan", HTTP_GET, [](AsyncWebServerRequest *request){ + String json = "["; + int n = WiFi.scanComplete(); + if(n == -2){ + WiFi.scanNetworks(true); + } else if(n){ + for (int i = 0; i < n; ++i) { + if(i) json += ","; + json += "{"; + json += "\"rssi\":"+String(WiFi.RSSI(i)); + json += ",\"ssid\":\""+WiFi.SSID(i)+"\""; + json += ",\"bssid\":\""+WiFi.BSSIDstr(i)+"\""; + json += ",\"channel\":"+String(WiFi.channel(i)); + json += ",\"secure\":"+String(WiFi.encryptionType(i)); +// json += ",\"hidden\":"+String(WiFi.isHidden(i)?"true":"false"); + json += "}\r\n"; + } + WiFi.scanDelete(); + if(WiFi.scanComplete() == -2){ + WiFi.scanNetworks(true); + } + } + json += "]"; + request->send(200, "application/json", json); + json = String(); + }); + + // upload a file to /upload + server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request){ + request->send(200); + }, onUpload); + + // send /index.htm file when /index is requested + server.on("/index", HTTP_ANY, [](AsyncWebServerRequest *request){ + request->send(SPIFFS, "/www/index.htm"); + }); + + // send /settings.htm file when /settings is requested + server.on("/settings", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(SPIFFS, "/www/settings.htm"); + int paramsNr = request->params(); + dbgSerial.print("Number of settings parameters: "); dbgSerial.println(paramsNr); + if (paramsNr) { + for (int i = 0; i < paramsNr; i++) { + AsyncWebParameter* p = request->getParam(i); + dbgSerial.print("Param name: "); dbgSerial.print(p->name()); + dbgSerial.print(", value: "); dbgSerial.println(p->value()); + if ((p->name()) == "ssid") + ssid = (p->value()); + if ((p->name()) == "pass") + pass = (p->value()); + if ((p->name()) == "scope") + strcpy(scope, (p->value()).c_str()); + } + + preferences.begin("EQG2HBX", false); // Access EQG2HBX namespace + + dbgSerial.print("Original - ssid: "); dbgSerial.print(preferences.getString("STA_SSID")); + dbgSerial.print(", pass: "); dbgSerial.print(preferences.getString("STA_PASS")); + dbgSerial.print(", Telescope: "); dbgSerial.println(preferences.getString("TELESCOPE")); + + if (strlen(ssid.c_str()) != 0) preferences.putString("STA_SSID", ssid); + if (strlen(pass.c_str()) != 0) preferences.putString("STA_PASS", pass); + if (strlen(scope) != 0) preferences.putString("TELESCOPE", scope); + + dbgSerial.print("Updated - ssid: "); dbgSerial.print(preferences.getString("STA_SSID")); + dbgSerial.print(", pass: "); dbgSerial.print(preferences.getString("STA_PASS")); + dbgSerial.print(", Telescope: "); dbgSerial.println(preferences.getString("TELESCOPE")); + preferences.end(); + } + }); + + // HTTP basic authentication + server.on("/login", HTTP_GET, [](AsyncWebServerRequest *request){ + dbgSerial.print("B4 http_username: "); + dbgSerial.print(http_username); + dbgSerial.print(", http_password: "); + dbgSerial.println(http_password); + + if(!request->authenticate(http_username, http_password)) + return request->requestAuthentication(); + else { + dbgSerial.print("http_username: "); + dbgSerial.print(http_username); + dbgSerial.print(", http_password: "); + dbgSerial.println(http_password); + } + request->send(200, "text/plain", "Login Success!"); + loginValid = true; + }); + + // Simple Firmware Update Form + + // send /update.htm file when /index is requested + server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request) { + if (loginValid == true) { + request->send(SPIFFS, "/update.htm"); + } + else { + request->send(200, "text/plain", "Please Login"); + } + }); + + // Process POST from Update Form + server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){ + shouldReboot = !Update.hasError(); + AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", shouldReboot?"OK":"FAIL"); + response->addHeader("Connection", "close"); + request->send(response); + },[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){ + if(!index){ + dbgSerial.printf("Update Start: %s\n", filename.c_str()); + +// Update.runAsync(true); +// if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { + if (!Update.begin(0x140000)) { + Update.printError(dbgSerial); + } + } + if(!Update.hasError()){ + if(Update.write(data, len) != len){ + Update.printError(dbgSerial); + } + } + if(final){ + if(Update.end(true)){ + dbgSerial.printf("Update Success: %uB\n", index+len); + } else { + Update.printError(dbgSerial); + } + } + }); +} + +void browseService(const char * service, const char * proto) { + Serial.printf("Browsing for service _%s._%s.local. ... ", service, proto); + int n = MDNS.queryService(service, proto); + if (n == 0) { + Serial.println("no services found"); + } + else { + Serial.print(n); + Serial.println(" service(s) found"); + for (int i = 0; i < n; ++i) { + // Print details for each service found + Serial.print(" "); + Serial.print(i + 1); + Serial.print(": "); + Serial.print(MDNS.hostname(i)); + Serial.print(" ("); + Serial.print(MDNS.IP(i)); + Serial.print(":"); + Serial.print(MDNS.port(i)); + Serial.println(")"); + } + } + Serial.println(); +} + +// ================================================================================================ + +void HBXWiFiSetup() { + + // TODO + // Read from EEPROM + + UDPFlag = true; // Either UDP or ESPNOW + APFlag = true; // Either AP or STA + + if (APFlag) { + // AP mode device connects directly to EQMODWiFi (no router) + // For AP mode: UDP2Serial: This ESP assigns IP addresses + // For AP mode: ESP IP is always 192.168.88.1 (set above) + + dbgSerial.println("EQMODWiFi Access Point Mode"); + // Check preferences for ssid, pass + preferences.begin("EQG2HBX", false); // Access EQG2HBX namespace + if (preferences.getString("AP_SSID", "none") == "none") + preferences.putString("AP_SSID", "EQMODWiFi"); // Default EQMOD + if (preferences.getString("AP_PASS", "none") == "none") + preferences.putString("AP_PASS", "CShillit0"); + ssid = preferences.getString("AP_SSID", "none"); + pass = preferences.getString("AP_PASS", "none"); + preferences.end(); + + WiFi.mode(WIFI_AP); + WiFi.softAPConfig(ip, ip, netmask); // softAP ip + WiFi.softAP(ssid.c_str(), pass.c_str()); // softAP ssid, pass + dbgSerial.print("SoftAP IP address: "); dbgSerial.println(WiFi.softAPIP()); + } + else { + // STA mode EQMODWiFi connects to network router and gets an IP + // For STA mode: Host software must detect that IP + // For STA mode: UDP2Serial router network assigns IP address + // For STA mode: Start webserver, mDNS and accept changes to ssid, pass, Telescope etc + + dbgSerial.println("EQMODWiFi Station Mode"); + // Check preferences for ssid, pass + preferences.begin("EQG2HBX", false); // Access EQG2HBX namespace + if (preferences.getString("STA_SSID", "none") == "none") + preferences.putString("STA_SSID", "HAPInet"); // Default Home Network + if (preferences.getString("STA_PASS", "none") == "none") + preferences.putString("STA_PASS", "HAPIconnect"); + ssid = preferences.getString("STA_SSID", "none"); + pass = preferences.getString("STA_PASS", "none"); + preferences.end(); + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid.c_str(), pass.c_str()); + int i = 0; + while ((WiFi.status() != WL_CONNECTED) && (i++ < 50)) { + delay(100); + dbgSerial.print(i); dbgSerial.print(", ssid: "); dbgSerial.print(ssid.c_str()); dbgSerial.print(", pass: "); dbgSerial.println(pass.c_str()); + + } + if (i >= 50) { + WiFi.begin("HAPInet", "HAPIconnect"); + while (WiFi.status() != WL_CONNECTED) { + delay(100); + dbgSerial.print("!"); + } + } + dbgSerial.println(" connected"); + + if (!MDNS.begin("eqmodwifi")) { + Serial.println("Error setting up MDNS responder!"); + while (1) { + delay(1000); + } + } + Serial.println("mDNS responder started"); + + // Load 'server.on' responses + AsyncServerResponseSetup(); + WiFi.scanNetworks(); + + // attach filesystem root at URL /fs + if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) { + dbgSerial.println("SPIFFS Mount Failed, SPIFF formatted"); + } + else + dbgSerial.println("SPIFFS Mounted .. "); + writeFile(SPIFFS, "/www/index.htm", EQ2HBX_Version.c_str()); + listDir(SPIFFS, "/", 0); + + server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.htm"); + + // Catch-All Handlers + // Any request that can not find a Handler that canHandle it + // ends in the callbacks below. + server.onNotFound(onRequest); + server.onFileUpload(onUpload); + server.onRequestBody(onBody); + + server.begin(); + // Add service to MDNS-SD + MDNS.addService("_http", "_tcp", 80); + MDNS.addService("_osc", "_udp", localUdpPort); + + // browseService("http", "tcp"); + } + + dbgSerial.print("ssid: "); dbgSerial.println(ssid.c_str()); + dbgSerial.print("pass: "); dbgSerial.println(pass.c_str()); + + if (!UDPFlag) { + // ESP_NOW mode (EQMODWiFi responds to MAC protocol) + dbgSerial.println("ESP_NOW Mode"); + InitESPNow(); + dbgSerial.println("ESPNOW2SerialServer"); + dbgSerial.print("Mount soft mac: "); dbgSerial.println(WiFi.softAPmacAddress()); + dbgSerial.print("Mount hard mac: "); dbgSerial.println(WiFi.macAddress()); +// esp_now_set_self_role(ESP_NOW_ROLE_COMBO); + esp_now_register_recv_cb(recvCallBack); + esp_now_register_send_cb(sendCallBack); + } + else { + dbgSerial.println("UDP Mode"); + udp.begin(localUdpPort); + dbgSerial.println("UDP2SerialServer"); + if (APFlag) + dbgSerial.printf("Now listening at IP %s, UDP port %d\n", WiFi.softAPIP().toString().c_str(), localUdpPort); + else + dbgSerial.printf("Now listening at IP %s, UDP port %d\n", WiFi.localIP().toString().c_str(), localUdpPort); + } + + TxDIndex = 0; + sendWiFi.len = 0; + recvWiFi.len = 0; +} + +// ------------------------------------------------------------------------------------------- + +bool HBXCheckRx() { + if (UDPFlag) { // UDP - SynScanPro UDP + recvWiFi.len = udp.parsePacket(); + if (recvWiFi.len > 0) { + // receive incoming UDP packets + recvWiFi.len = udp.read(recvWiFi.text, 250); + if (recvWiFi.len > 0) { + recvWiFi.text[recvWiFi.len] = 0; // Null terminate + +// dbgSerial.print("RxUDP - len: "); +// dbgSerial.print(recvWiFi.len); +// dbgSerial.print(", data: "); +// dbgSerial.println(recvWiFi.text); + + } + } + } + if (recvWiFi.len > 0) { // Send data to mount Input data buffer for processing + putRxDataIntoMountInputBuffer(); + return true; // Data received from UDP or ESP_NOW callback + } + else + return false; // No Data received +} + +void HBXCheckTx() { + if (UDPFlag) { + // Get data to send to SynScanPro via UDP into WiFi buffer + getTxDataFromMountOutputBuffer(); + // send back a reply, to the IP address and port we got the packet from + if (sendWiFi.len > 0) { + + // dbgSerial.print("TxUDP - len: "); + // dbgSerial.print(sendWiFi.len); + // dbgSerial.print(", data: "); + // dbgSerial.println(sendWiFi.text); + + udp.beginPacket(udp.remoteIP(), udp.remotePort()); + memcpy(sendWiFi8, &sendWiFi.text, sendWiFi.len); + udp.write(sendWiFi8, sendWiFi.len); + udp.endPacket(); + } + sendWiFi.len = 0; + TxDIndex = 0; + } + else { + // Get data to send to ESP_NOW - EQMOD/SynScanPro Serial WiFi + if (Connected) { + getTxDataFromMountOutputBuffer(); + if (sendWiFi.len > 0) + sendData(&serial_peer); + } + } +} diff --git a/Software/EQG2HBXWiFi/HBXzTest.ino b/Software/EQG2HBXWiFi/HBXzTest.ino new file mode 100644 index 0000000..1849e15 --- /dev/null +++ b/Software/EQG2HBXWiFi/HBXzTest.ino @@ -0,0 +1,519 @@ +/* + * Copyright 2017, 2018 John Archbold +*/ +#include + +/******************************************************** + Test HBX communications + ======================= + *********************************************************/ +void HBXTestLoop(void) { + TestCount = 0; + //while (digitalRead(TESTHBX) == 0) { + while (1) { + dbgSerial.println("Test HBX commands to ETX"); + HBXTest(); + TestCount += 1; + TestLoopTime = millis(); + // Read motor status until jumper removed + while((millis() - TestLoopTime) < 1000) { // 5s between tests +// HBXGet2Status(); + } + } +} + +void HBXTest(void) +{ + + // Initialize HBX communications as outputs + // It will use I2C-like communications + dbgSerial.println("**********************************************"); + dbgSerial.print("Test Number - "); + dbgSerial.println(TestCount); + dbgSerial.println("**********************************************"); + + HCL1Talk(); // Set for Talking on RAClk + HCL2Talk(); // Set for Talking on DECClk + HDAListen(); + TimerDelaymS(STARTTIME); + axis[AzMotor].PrintStatus0 = 1; // Enable print of status = no change + axis[AltMotor].PrintStatus0 = 1; // Enable print of status = no change + +//if (TestCount == 0) { + + dbgSerial.println("Test - HBX Initialization"); + axis[AzMotor].Position = ETX_CENTRE; + axis[AzMotor].Target = axis[AzMotor].Position; + axis[AzMotor].DirnSpeed = 0x000; + axis[AzMotor].Speed = 0x000000; + axis[AzMotor].ETXMotorStatus = MOVESLEW; + + axis[AltMotor].Position = ETX_CENTRE; + axis[AltMotor].Target = axis[AltMotor].Position; + axis[AltMotor].DirnSpeed = 0x000; + axis[AltMotor].Speed = 0x000000; + axis[AltMotor].ETXMotorStatus = MOVESLEW; + + // Reset the motors (RA and DEC) + dbgSerial.println("Test - Wait for motors"); + + // GetLED commands always return a vaild value - motors not online until this happens + // Valid values are not 0 and not 0xFF for Az, Alt. (exception here is if LEDRA || LEDAlt == 0xff) + + do { + P1 = 0; + if (HBXSendCommand(GetLEDI, AzMotor)) + P2 = HBXGetByte(AzMotor); + if ((P2 != 0) && (P2 != 0xFF)) P1 += 1; + TimerDelaymS(CMNDTIME); + if (HBXSendCommand(GetLEDI, AltMotor)) + P2 = HBXGetByte(AltMotor); + if ((P2 != 0) && (P2 != 0xFF)) P1 += 1; + TimerDelaymS(CMNDTIME); + } while (P1 < 2); + + + // Stop the motors (Az and Alt) + dbgSerial.println("Test - Stop motors"); + HBXStop2Motors(); + + // Read status + dbgSerial.println(""); + dbgSerial.println("1 Test - Read Status"); + HBXGet2Status(); // Check and read both motor states + + dbgSerial.println("Test - Get Motor Type and set ETX Encoder LED currents"); + axis[AzMotor].MotorType = 0x00; + while (!axis[AzMotor].MotorType) { + if (HBXSendCommand(GetMotorType, AzMotor)) + axis[AzMotor].MotorType = HBXGetByte(AzMotor); + HBXPrintStatus(AzMotor); + } + + preferences.begin("EQG2HBX", false); // Access EQG2HBX namespace + +// Handle position sensors LED current +// ----------------------------------- + if (preferences.getUChar("AzLEDI", 0) == 0) { // If it does not exist, return 0 + // Calibrate motors + if (HBXSendCommand(CalibrateLED, AzMotor)) TimerDelaymS(2500); + if (HBXSendCommand(CalibrateLED, AltMotor)) TimerDelaymS(2500); + // Read the calibration + dbgSerial.print("Read LEDs - AzMotor: "); + if (HBXSendCommand(GetLEDI, AzMotor)) + axis[AzMotor].HBXLEDI = HBXGetByte(AzMotor); + dbgSerial.print(axis[AzMotor].HBXLEDI); + dbgSerial.print(", AltMotor: "); + if (HBXSendCommand(GetLEDI, AltMotor)) + axis[AltMotor].HBXLEDI = HBXGetByte(AltMotor); + dbgSerial.println(axis[AltMotor].HBXLEDI); + // Save it to preferences + preferences.putUChar("AzLEDI", axis[AzMotor].HBXLEDI); + preferences.putUChar("AltLEDI", axis[AltMotor].HBXLEDI); + } + // Read stored LED currents + axis[AzMotor].HBXLEDI = preferences.getUChar("AzLEDI", 0); + axis[AltMotor].HBXLEDI = preferences.getUChar("AltLEDI", 0); + preferences.end(); + + // Set the MC values + if (HBXSendCommand(SetLEDI, AzMotor)) + HBXSendByte(axis[AzMotor].HBXLEDI, AzMotor); + axis[AzMotor].HBXP1 = axis[AzMotor].HBXLEDI; + HBXPrintStatus(AzMotor); + + if (HBXSendCommand(SetLEDI, AltMotor)) + HBXSendByte(axis[AltMotor].HBXLEDI, AltMotor); + axis[AltMotor].HBXP1 = axis[AltMotor].HBXLEDI; + HBXPrintStatus(AltMotor); + + // Set the Offset to Zero + axis[AzMotor].HBXP1 = 0x00; + axis[AzMotor].HBXP2 = 0x00; + axis[AzMotor].HBXP3 = 0x00; + axis[AltMotor].HBXP1 = 0x00; + axis[AltMotor].HBXP2 = 0x00; + axis[AltMotor].HBXP3 = 0x00; + + // Set the Offset Clear Command + dbgSerial.println("Test - Reset any ETX offset bytes"); + if (HBXSendCommand(SetOffset, AzMotor)) + HBXSend2Bytes(AzMotor); + HBXPrintStatus(AzMotor); + TimerDelaymS(CMNDTIME); + + if (HBXSendCommand(SetOffset, AltMotor)) + HBXSend2Bytes(AltMotor); + HBXPrintStatus(AltMotor); + TimerDelaymS(CMNDTIME); + +//} +/* + dbgSerial.println("Test - Stop motors"); + HBXStop2Motors(); +// First Read, clear counters + dbgSerial.println("Test - Read Both Motor States"); + HBXGet2Status(); + + dbgSerial.println("Test - Begin motor move tests"); +//} + // Test different motor speeds + dbgSerial.println("Test - Motor Speed Tests"); + dbgSerial.println("========================"); + + dbgSerial.println("Test - SIDEREAL"); + axis[AzMotor].ETXMotorStatus |= MOVEDIRN; // Forward + axis[AltMotor].ETXMotorStatus |= MOVEDIRN; + axis[AzMotor].Speed = AzSIDEREALRATE; // Sidereal + axis[AltMotor].Speed = AltSIDEREALRATE; + HBXPrintPosn(5, 1000); // Show location each second + dbgSerial.println("Test - SIDEREAL - Stop motors"); + HBXStop2Motors(); + + dbgSerial.println("Test - OneDegree/sec forward"); + axis[AzMotor].ETXMotorStatus |= MOVEDIRN; // Forward + axis[AltMotor].ETXMotorStatus |= MOVEDIRN; + axis[AzMotor].Speed = AzDEGREERATE1; // One degree/sec + axis[AltMotor].Speed = AltDEGREERATE1; // One degree/sec + HBXPrintPosn(5, 500); // Show location each tenth of a second + dbgSerial.println("Test - OneDegree/sec forward - Stop motors"); + HBXStop2Motors(); + + dbgSerial.println("Test - OneDegree/sec reverse"); + axis[AzMotor].ETXMotorStatus &= ~MOVEDIRN; // Reverse + axis[AltMotor].ETXMotorStatus &= ~MOVEDIRN; + axis[AzMotor].Speed = AzDEGREERATE1; // One degree/sec + axis[AltMotor].Speed = AltDEGREERATE1; // One degree/sec + HBXPrintPosn(5, 500); // Show location each each tenth of a second + dbgSerial.println("Test - OneDegree/sec reverse - Stop motors"); + HBXStop2Motors(); + + dbgSerial.println("Test - TwoDegrees/sec forward"); + axis[AzMotor].ETXMotorStatus |= MOVEDIRN; // Forward + axis[AltMotor].ETXMotorStatus |= MOVEDIRN; + axis[AzMotor].Speed = AzDEGREERATE1*2; // Two degrees/sec + axis[AltMotor].Speed = AltDEGREERATE1*2; // Two degrees/sec + HBXPrintPosn(5, 1000); // Show location each tenth of a second + dbgSerial.println("Test - TwoDegrees/sec forward - Stop motors"); + HBXStop2Motors(); + + dbgSerial.println("Test - TwoDegrees/sec reverse"); + axis[AzMotor].ETXMotorStatus &= ~MOVEDIRN; // Reverse + axis[AltMotor].ETXMotorStatus &= ~MOVEDIRN; + axis[AzMotor].Speed = AzDEGREERATE1*2; // Two degrees/sec + axis[AltMotor].Speed = AltDEGREERATE1*2; // Two degrees/sec + HBXPrintPosn(5, 1000); // Show location each each tenth of a second + dbgSerial.println("Test - TwoDegrees/sec reverse - Stop motors"); +*/ + HBXStop2Motors(); + + dbgSerial.println(""); + dbgSerial.println("Test - 0x01 Command"); + axis[AzMotor].MotorControl &= ~MOVEDECR; // Forward + axis[AltMotor].MotorControl &= ~MOVEDECR; + axis[AzMotor].MotorControl |= SpeedHBX; // High speed + axis[AltMotor].MotorControl |= SpeedHBX; + axis[AzMotor].Speed = axis[AzMotor].SIDEREALRATE; // Start at Sidereal + axis[AltMotor].Speed = axis[AltMotor].SIDEREALRATE; + + do { + // HBXPrintSpeed(1, 10000); // Print location + axis[AzMotor].Speed += axis[AzMotor].SIDEREALRATE; + axis[AzMotor].ETXMotorState = ETXStepMotor; + ETXState(AzMotor); + TimerDelaymS(CMNDTIME); +// delay(2000); +// } while (axis[AzMotor].Speed < 0x600000); + +// do { +// HBXPrintSpeed(1, 10000); // Print location + axis[AltMotor].Speed += axis[AltMotor].SIDEREALRATE; + axis[AltMotor].ETXMotorState = ETXStepMotor; + ETXState(AltMotor); + TimerDelaymS(CMNDTIME); + HBXGet2Status(); + TimerDelaymS(CMNDTIME); + TimerDelaymS(2000); + } while (axis[AltMotor].Speed < (axis[AltMotor].SIDEREALRATE << 3)); + + dbgSerial.println("Test - 0x01 Command - Stop motors"); + HBXStop2Motors(); + +// Read status + dbgSerial.println(""); + dbgSerial.println("2 Test - Read Status"); + HBXGet2Status(); + + // Stop the motors (Az and Alt) + dbgSerial.println("Test - Stop motors"); + HBXStop2Motors(); + + dbgSerial.println("Test - End of Loop"); + +} + + +void HBXPrintSpeed(unsigned int count, unsigned int duration) { + int j = 0; + + axis[AzMotor].Position = ETX_CENTRE; // Reset position + axis[AltMotor].Position = ETX_CENTRE; + axis[AzMotor].Increment = 0; + axis[AltMotor].Increment = 0; + PreviousTime = millis(); + dbgSerial.print("Az, "); + dbgSerial.println(axis[AzMotor].Speed); + dbgSerial.print("Alt, "); + dbgSerial.println(axis[AltMotor].Speed); + HBXStart2Motors(); // Start the motors + do { + ETXState(AzMotor); // Check the Az motor state + ETXState(AltMotor); // Check the Alt motor state + dbgSerial.print(millis() - PreviousTime); + dbgSerial.print(", "); + SendSpeed(duration); // Duration is delay between reads + j += 1; + while ((millis() - PreviousTime) < duration); + PreviousTime = millis(); + } while(j < count); +} + +void SendSpeed(unsigned long duration) { + + HBXGetStatus(AzMotor); + axis[AzMotor].Increment = axis[AzMotor].HBXP1 * 256 + axis[AzMotor].HBXP2; + HBXGetStatus(AltMotor); + axis[AltMotor].Increment = axis[AltMotor].HBXP1 * 256 + axis[AltMotor].HBXP2; + + dbgSerial.println(""); + dbgSerial.print("Az-Speed: "); + dbgSerial.print(axis[AzMotor].Speed); + dbgSerial.print(",Posn: "); + dbgSerial.print(axis[AzMotor].Position); + dbgSerial.print(",Incr: "); + dbgSerial.println(axis[AzMotor].Increment); + dbgSerial.print("Alt-Speed: "); + dbgSerial.print(axis[AltMotor].Speed); + dbgSerial.print(",Posn: "); + dbgSerial.print(axis[AltMotor].Position); + dbgSerial.print(",Incr: "); + dbgSerial.println(axis[AltMotor].Increment); +} + +void HBXPrintPosn(unsigned int count, unsigned int duration) { + int j = 0; + + axis[AzMotor].Position = ETX_CENTRE; // Reset position + axis[AltMotor].Position = ETX_CENTRE; + axis[AzMotor].Increment = 0; + axis[AltMotor].Increment = 0; + PreviousTime = millis(); + + HBXStart2Motors(); // Start the motors + + do { + ETXState(AzMotor); // Check the Az motor state + ETXState(AltMotor); // Check the Alt motor state + SendData(duration); // Duration is delay between reads + j += 1; + } while(j < count); +} + +void SendData(unsigned int duration) { + +// dbgSerial.println(" SendPosn"); + + while ((millis() - PreviousTime) < duration) ; // 1/10 second + dbgSerial.print(millis() - PreviousTime); + dbgSerial.print(" - "); + PreviousTime = millis(); + + HBXGetStatus(AzMotor); + axis[AzMotor].Increment = axis[AzMotor].HBXP1 * 256 + axis[AzMotor].HBXP2; + + HBXGetStatus(AltMotor); + axis[AltMotor].Increment = axis[AltMotor].HBXP1 * 256 + axis[AltMotor].HBXP2; + + dbgSerial.print("Az = "); + dbgSerial.print(axis[AzMotor].Position); + dbgSerial.print(" : "); + dbgSerial.print(axis[AzMotor].Increment); + dbgSerial.print(", Alt = "); + dbgSerial.print(axis[AltMotor].Position); + dbgSerial.print(" : "); + dbgSerial.println(axis[AltMotor].Increment); +} + +void HBXPrintStatus(unsigned char Motor) { + axis[Motor].HBXCount = 0; + if ((axis[Motor].Command != GetStatus) || (axis[Motor].HBXP1 | axis[Motor].HBXP2 | axis[Motor].HBXP3 | axis[Motor].HBXP4) || axis[Motor].PrintStatus0 ) { + +/* dbgSerial.println(""); + dbgSerial.print("Motor: "); + dbgSerial.print(Motor); + dbgSerial.print(", Cmnd: "); + dbgSerial.print(axis[Motor].Command, HEX); + dbgSerial.print(" - "); + */ + switch (axis[Motor].Command) { + case SpeedChnge: +// dbgSerial.print("SpeedChnge "); + axis[Motor].HBXCount = 3; + break; + case SpeedStart: +// dbgSerial.print("SpeedStart "); + axis[Motor].HBXCount = 3; + break; + case SetOffset: +// dbgSerial.print("SetOffset "); + axis[Motor].HBXCount = 4; + break; + case SetLEDI: +// dbgSerial.print("SetLEDI "); + axis[Motor].HBXCount = 1; + break; + case CalibrateLED: +// dbgSerial.print("CalibrateLED "); + break; + case Stop: +// dbgSerial.print("Stop "); + break; + case SlewReverse: +// dbgSerial.print("SlewReverse "); + break; + case SlewForward: +// dbgSerial.print("SlewForward "); + break; + case GetStatus: +// dbgSerial.print("GetStatus "); + axis[Motor].HBXCount = 4; + break; + case GetLEDI: +// dbgSerial.print("GetLEDI "); + axis[Motor].HBXCount = 1; + break; + case GetMotorType: +// dbgSerial.print("GetMotorType "); + axis[Motor].HBXP1 = axis[Motor].MotorType; + axis[Motor].HBXCount = 1; + break; + case SleepHBX: +// dbgSerial.print("SleepHBX "); + break; + default: + dbgSerial.print("UNKNOWN "); + break; + } + + if (axis[Motor].HBXCount) { +// dbgSerial.print(", Data: "); +// dbgSerial.print(axis[Motor].HBXP1, HEX); + if (axis[Motor].HBXCount > 1) dbgSerial.print(", "); + axis[Motor].HBXCount -= 1; + } + if (axis[Motor].HBXCount) { +// dbgSerial.print(axis[Motor].HBXP2, HEX); + if (axis[Motor].HBXCount > 1) dbgSerial.print(", "); + axis[Motor].HBXCount -= 1; + } + if (axis[Motor].HBXCount) { +// dbgSerial.print(axis[Motor].HBXP3, HEX); + if (axis[Motor].HBXCount > 1) dbgSerial.print(", "); + axis[Motor].HBXCount -= 1; + } + if (axis[Motor].HBXCount) { +// dbgSerial.print(axis[Motor].HBXP4, HEX); + axis[Motor].HBXCount -= 1; + } +// dbgSerial.println(""); + } +} + +bool HBXStop2Motors(void) { + axis[AzMotor].ETXMotorState = ETXStopMotor; + ETXState(AzMotor); + axis[AltMotor].ETXMotorState = ETXStopMotor; + ETXState(AltMotor); + return(true); +} + +bool HBXStart2Motors(void) { + axis[AzMotor].ETXMotorState = ETXCheckStartup; + axis[AzMotor].EQGMotorStatus |= MOVEAXIS; + dbgSerial.print("Az, "); + dbgSerial.println(axis[AzMotor].Speed); + ETXState(AzMotor); + dbgSerial.print("Az, "); + dbgSerial.println(axis[AzMotor].Speed); + axis[AltMotor].ETXMotorState = ETXCheckStartup; + axis[AltMotor].EQGMotorStatus |= MOVEAXIS; + dbgSerial.print("Az, "); + dbgSerial.println(axis[AzMotor].Speed); + ETXState(AltMotor); + dbgSerial.print("Az, "); + dbgSerial.println(axis[AzMotor].Speed); + return(true); +} + +void HBXPrintPosn(unsigned char Motor) { + if (Motor == MotorAz) { + dbgSerial.println(""); + dbgSerial.print("Time: "); + dbgSerial.print(millis()); +// dbgSerial.print(StatusTimer - StatusTime); +// StatusTime = StatusTimer; + + +/* dbgSerial.print(" AzRaw: "); + puthexb(axis[AzMotor].HBXP1); + putbyte(','); + puthexb(axis[AzMotor].HBXP2); + putbyte(','); + puthexb(axis[AzMotor].HBXP3); + putbyte(','); + puthexb(axis[AzMotor].HBXP4); +*/ + dbgSerial.print(" AzPosn: "); + puthexl(axis[AzMotor].Position); + putbyte(','); + puthexl(axis[AzMotor].Target); + putbyte(','); + puthexl(axis[AzMotor].SlowDown); + dbgSerial.print(" AzSpeed: "); + puthexl(axis[AzMotor].Speed); + putbyte(','); + puthexl(axis[AzMotor].TargetSpeed); + putbyte('-'); + puthexw(axis[AzMotor].EQGMotorStatus); + putbyte(','); + puthexw(axis[AzMotor].MotorControl); + + } + else { +/* + * dbgSerial.print(" AltRaw: "); + puthexb(axis[AltMotor].HBXP1); + putbyte(','); + puthexb(axis[AltMotor].HBXP2); + putbyte(','); + puthexb(axis[AltMotor].HBXP3); + putbyte(','); + puthexb(axis[AltMotor].HBXP4); + */ + dbgSerial.print(" AltPosn: "); + puthexl(axis[AltMotor].Position); + putbyte(','); + puthexl(axis[AltMotor].Target); + putbyte(','); + puthexl(axis[AltMotor].SlowDown); + dbgSerial.print(" AltSpeed: "); + puthexl(axis[AltMotor].Speed); + putbyte(','); + puthexl(axis[AltMotor].TargetSpeed); + putbyte('-'); + puthexw(axis[AltMotor].EQGMotorStatus); + putbyte(','); + puthexw(axis[AltMotor].MotorControl); + dbgSerial.println(""); + } +}