From b46f187a5f5c99277573a2c8fbcd23d589eb0555 Mon Sep 17 00:00:00 2001 From: Thomas Carpenter <strange_orange_fish@hotmail.com> Date: Wed, 29 Feb 2012 17:43:13 +0000 Subject: [PATCH] Initial commit of project. Currently tracking is at incorrect speed, I'm working on it :S --- AstroEQ/AstroEQ.ino | 417 +++++++++++++++++++++++++++++++++++++++++++ AstroEQ/commands.cpp | 181 +++++++++++++++++++ AstroEQ/commands.h | 64 +++++++ AstroEQ/synta.cpp | 149 ++++++++++++++++ AstroEQ/synta.h | 44 +++++ README | 17 ++ 6 files changed, 872 insertions(+) create mode 100644 AstroEQ/AstroEQ.ino create mode 100644 AstroEQ/commands.cpp create mode 100644 AstroEQ/commands.h create mode 100644 AstroEQ/synta.cpp create mode 100644 AstroEQ/synta.h diff --git a/AstroEQ/AstroEQ.ino b/AstroEQ/AstroEQ.ino new file mode 100644 index 0000000..5e6e56d --- /dev/null +++ b/AstroEQ/AstroEQ.ino @@ -0,0 +1,417 @@ +/* + Code written by Thomas Carpenter 2012 + + With thanks Chris over at the EQMOD Yahoo group for explaining the Skywatcher protocol + + + Equatorial mount tracking system for integration with EQMOD using the Skywatcher/Syntia + communication protocol. + + Works with EQ5, HEQ5, and EQ6 mounts (Not EQ3-2, they have a different gear ratio) + + Current Verison: 3.5 +*/ + +#include "synta.h" + +//Create an instance of the mount +Synta synta(1281,4505600,32368,16,31289); + +//Synta synta(e,a,b,g,s); +// +// e = version number (this doesnt need chaning) +// +// a = number of steps per complete axis revolution. +// +// b = sidereal rate. This again depends on number of microsteps per step. +// +// g = high speed multiplyer. This is the EQMOD recommended default. In highspeed mode, the step rate will be 16 times larger. +// +// s = steps per worm revolution +// +// mount specifics can be found here: http://tech.groups.yahoo.com/group/EQMOD/database?method=reportRows&tbl=5&sortBy=5 +// +// The motor portion of the code I have is written is for EQ5 Mounts. +// It may work for EQ6 mounts if you change the numbers above to: +// (1281,4512000,32466,16,50133) +// And possibly for HEQ5 mounts if you change the numbers above to: +// (1281,4512000,32466,16,66844) +// + +//Define the two axes +#define RA 0 +#define DC 1 + +unsigned long gotoPosn[2] = {0,0}; //where to slew to +unsigned int timerOVF[2]; + +//Pins +const char resetPin[2] = {A1,A0}; +const char errorPin[2] = {2,6}; +const char dirPin[2] = {3,7}; +const char enablePin[2] = {4,8}; +const char stepPin[2] = {5,9}; +//Used for direct port register write +#define STEP1PORT PORTE +#define _STEP1_HIGH 0b00001000 +#define _STEP1_LOW 0b11110111 +#define STEP2PORT PORTH +#define _STEP2_HIGH 0b01000000 +#define _STEP2_LOW 0b10111111 + + +void setup() +{ + for(byte i = 0;i < 2;i++){ //for each motor... + pinMode(errorPin[i],INPUT); //enable the Error pin + pinMode(enablePin[i],OUTPUT); //enable the Enable pin + digitalWrite(enablePin[i],HIGH); //IC disabled + pinMode(stepPin[i],OUTPUT); //enable the step pin + digitalWrite(stepPin[i],LOW); //default is low + pinMode(dirPin[i],OUTPUT); //enable the direction pin + digitalWrite(dirPin[i],LOW); //default is low + pinMode(resetPin[i],OUTPUT); //enable the reset pin + digitalWrite(resetPin[i],LOW); //active low reset + delay(1); //allow ic to reset + digitalWrite(resetPin[i],HIGH); //complete reset + } + + // start Serial port: + Serial.begin(9600); //SyncScan runs at 9600Baud, use a serial port of your choice (Serial, Serial, Serial2, or Serial3) + while(Serial.available()){ + Serial.read(); //Clear the buffer + } + + Serial1.begin(115200); + configureTimer(); //setup the motor pulse timers. +} + + +void loop(){ + char decodedPacket[11]; //temporary store for completed command ready to be processed + if (Serial.available()) { //is there a byte in buffer + char recievedChar = Serial.read(); //get the next character in buffer + Serial1.print(recievedChar); + char decoded = synta.recieveCommand(decodedPacket,recievedChar); //once full command packet recieved, decodedPacket returns either error packet, or valid packet + if (decoded == 1){ //Valid Packet + decodeCommand(synta.command(),decodedPacket); //decode the valid packet + } else if (decoded == -1){ //Error + Serial.print(decodedPacket); //send the error packet + } //otherwise command not yet fully recieved, so wait for next byte + } +} + +void decodeCommand(char command, char* packetIn){ //each command is axis specific. The axis being modified can be retrieved by calling synta.axis() + char response[11]; //generated response string + unsigned long responseData; //data for response + + switch(command){ + case 'e': //readonly, return the eVal (version number) + responseData = synta.cmd.eVal(synta.axis()); //response to the e command is stored in the eVal function for that axis. + break; + case 'a': //readonly, return the aVal (steps per axis) + responseData = synta.cmd.aVal(synta.axis()); //response to the a command is stored in the aVal function for that axis. + break; + case 'b': //readonly, return the bVal (sidereal rate) + responseData = synta.cmd.bVal(synta.axis()); //response to the b command is stored in the bVal function for that axis. + break; + case 'g': //readonly, return the gVal (high speed multiplier) + responseData = synta.cmd.gVal(synta.axis()); //response to the g command is stored in the gVal function for that axis. + break; + case 's': //readonly, return the sVal (steps per worm rotation) + responseData = synta.cmd.sVal(synta.axis()); //response to the s command is stored in the sVal function for that axis. + break; + case 'f': //readonly, return the fVal (axis status) + responseData = synta.cmd.fVal(synta.axis()); //response to the f command is stored in the fVal function for that axis. + break; + case 'j': //readonly, return the jVal (current position) + responseData = synta.cmd.jVal(synta.axis()); //response to the j command is stored in the jVal function for that axis. + break; + case 'K': //stop the motor, return empty response + + + motorStop(0); //No specific response, just stop the motor (Feel free to customise motorStop function to suit your needs + + + responseData = 0; + break; + case 'G': //set mode and direction, return empty response + synta.cmd.GVal(synta.axis(), (packetIn[0] - 48)); //Store the current mode for the axis + synta.cmd.dir(synta.axis(),(packetIn[1] - 48)); //Store the current direction for that axis + responseData = 0; + break; + case 'H': //set goto position, return empty response (this sets the number of steps to move from cuurent position if in goto mode) + synta.cmd.HVal(synta.axis(), synta.hexToLong(packetIn)); //set the goto position container (convert string to long first) + responseData = 0; + break; + case 'I': //set slew speed, return empty response (this sets the speed to move at if in slew mode) + synta.cmd.IVal(synta.axis(), synta.hexToLong(packetIn)); //set the speed container (convert string to long first) + + //This will be different if you use a different motor code + calculateRate(); //Used to convert speed into the number of 0.5usecs per step. Result is stored in TimerOVF + + + responseData = 0; + break; + case 'E': //set the current position, return empty response + synta.cmd.jVal(synta.axis(), synta.hexToLong(packetIn)); //set the current position (used to sync to what EQMOD thinks is the current position at startup + responseData = 0; + break; + case 'F': //Enable the motor driver, return empty response + + + motorEnable(); //This enables the motors - gives the motor driver board power + + + responseData = 0; + break; + default: //Return empty response (deals with commands that don't do anything before the response sent (i.e 'J'), or do nothing at all (e.g. 'M', 'L') ) + responseData = 0; + break; + } + + synta.assembleResponse(response, command, responseData); //generate correct response (this is required as is) + Serial.print(response); //send response to the serial port + + if(command == 'J'){ //J tells + if(synta.cmd.GVal(synta.axis()) & 1){ + + //This is the funtion that enables a slew type move. + slewMode(); //Slew type + + + } else { + + //This is the function for goto mode. You may need to customise it for a different motor driver + gotoMode(); //Goto Mode + + + } + } +} + +// +//Everything below this point needs to be customised for your motor driver. As yet code below here is untested +// +// +// +// +// +// +// +// +// +// + +//Calculates the rate based on the recieved I value +void calculateRate(){ + + //steps per second = IVal / BVal; for lowspeed move + //or steps per second = IVal / (BVal * gVal); for highspeed move + // + //whether to use highspeed move or not is determined by the GVal; + // + + unsigned long rate; + if((synta.cmd.GVal(synta.axis()) == 2)||(synta.cmd.GVal(synta.axis()) == 1)){ //Normal Speed + rate = 2000000L * synta.cmd.IVal(synta.axis()); + rate /= synta.cmd.bVal(synta.axis()); + } else if ((synta.cmd.GVal(synta.axis()) == 0)||(synta.cmd.GVal(synta.axis()) == 3)){ //High Speed + rate = 2000000L * synta.cmd.IVal(synta.axis()); + rate /= (synta.cmd.bVal(synta.axis()) * synta.cmd.gVal(synta.axis())); + } + + //Now truncate to an unsigned int with a sensible max value (the int is to avoid register issues with the 16 bit timer) + if(rate < 539){ //We do 65535 - result as timer counts up from this number to 65536 then interrupts + timerOVF[synta.axis()] = 64997L; + } else { + timerOVF[synta.axis()] = 65535L - rate; + } +} + +void slewMode(){ + digitalWrite(dirPin[synta.axis()],synta.cmd.dir(synta.axis())); //set the direction + if(timerOVF[synta.axis()] > 38000L){ + accellerate(64); //accellerate to calculated rate in 64 steps + } else { + accellerate(32); //accellerate to calculated rate in 32 steps (faster to avoid missing serial messages) + } + if(synta.axis()){ + TCNT4 = timerOVF[synta.axis()]; + TIMSK4 |= (1<<TOIE4); //Enable timer interrupt + } else { + TCNT3 = timerOVF[synta.axis()]; + TIMSK3 |= (1<<TOIE3); //Enable timer interrupt + } + synta.cmd.stopped(synta.axis(), 0); +} + +void gotoMode(){ + digitalWrite(dirPin[synta.axis()],synta.cmd.dir(synta.axis())); //set the direction + synta.cmd.IVal(synta.axis(), 140); //Set GOTO speed + calculateRate(); + unsigned int temp = 0; + byte accellerateSteps; + if (synta.cmd.HVal(synta.axis()) < 32){ + synta.cmd.HVal(synta.axis(), 32); //This will cause a small error on VERY TINY Goto's though in reality should never be an issue. + } + if (synta.cmd.HVal(synta.axis()) < 64){ + synta.cmd.HVal(synta.axis(),(synta.cmd.HVal(synta.axis()) - 16)); + accellerateSteps = 16; + } else if(synta.cmd.HVal(synta.axis()) < 96){ + synta.cmd.HVal(synta.axis(),(synta.cmd.HVal(synta.axis()) - 32)); + accellerateSteps = 32; + } else if(synta.cmd.HVal(synta.axis()) < 128){ + synta.cmd.HVal(synta.axis(),(synta.cmd.HVal(synta.axis()) - 48)); + accellerateSteps = 48; + } else { + synta.cmd.HVal(synta.axis(),(synta.cmd.HVal(synta.axis()) - 64)); + accellerateSteps = 64; + } + gotoPosn[synta.axis()] = synta.cmd.jVal(synta.axis()) + (synta.cmd.stepDir(synta.axis()) * synta.cmd.HVal(synta.axis())); //current position + if(synta.cmd.HVal(synta.axis()) < 124){ //there are 496 steps of decelleration, so we need to find the correct part of the decelleration curve + //find point in decelleration curve (timerOVF[synta.axis()] -= 76; for each step) + temp = 124 - synta.cmd.HVal(synta.axis()); //number of decelleration steps into the curve + temp *= 304; + timerOVF[synta.axis()] -= temp; + } + accellerate(accellerateSteps); //accellerate to calculated rate in 64 steps + synta.cmd.gotoEn(synta.axis(),1); + synta.cmd.stopped(synta.axis(),0); + if(synta.axis()){ + TCNT4 = timerOVF[synta.axis()]; + TIMSK4 |= (1<<TOIE4); //Enable timer interrupt + } else { + TCNT3 = timerOVF[synta.axis()]; + TIMSK3 |= (1<<TOIE3); //Enable timer interrupt + } +} + +void motorStop(boolean caller){ + if(!synta.cmd.stopped(synta.axis())){ //only has an effect if not already stopped + if(!caller && !synta.cmd.gotoEn(synta.axis())){ //If not in goto mode, then decellerate + synta.cmd.HVal(synta.axis(), (64997 - timerOVF[synta.axis()])); //work out where in decelleration curve we are to know how many steps to run + synta.cmd.HVal(synta.axis(), (synta.cmd.HVal(synta.axis()) / 76)); //19 0.5us's per step + synta.cmd.HVal(synta.axis(), (496 - synta.cmd.HVal(synta.axis()))); //find steps + gotoPosn[synta.axis()] = synta.cmd.jVal(synta.axis()) + (synta.cmd.stepDir(synta.axis()) * synta.cmd.HVal(synta.axis())); //current position + synta.cmd.gotoEn(synta.axis(),1); //Enter short goto mode stint + } else if (caller){ //otherwise, if decelleration complete, stop. + if(synta.axis()){ //switch off correct axis + TIMSK4 &= ~(1<<TOIE4); //disable timer + } else { + TIMSK3 &= ~(1<<TOIE3); //disable timer + } + synta.cmd.gotoEn(synta.axis(),0); //Cancel goto mode + synta.cmd.stopped(synta.axis(),1); //mark as stopped + } else if (synta.cmd.gotoEn(synta.axis())){ //otherwise, abort GOTO and decellerate. + synta.cmd.HVal(synta.axis(), (64997 - timerOVF[synta.axis()])); //work out where in decelleration curve we are to know how many steps to run + synta.cmd.HVal(synta.axis(), (synta.cmd.HVal(synta.axis()) / 76)); //76 0.5us's per step + synta.cmd.HVal(synta.axis(), (496 - synta.cmd.HVal(synta.axis()))); //find steps + gotoPosn[synta.axis()] = synta.cmd.jVal(synta.axis()) + (synta.cmd.stepDir(synta.axis()) * synta.cmd.HVal(synta.axis())); //current position + } + } +} + +void motorEnable(){ + digitalWrite(enablePin[synta.axis()],LOW); + synta.cmd.FVal(synta.axis(),1); +} + +void accellerate(byte accellerateSteps){ + //x steps to speed up to timerOVF[synta.axis()] + unsigned int speedPerStep = (timerOVF[synta.axis()] - 10036L) / accellerateSteps; //Initial Speed is 10036 + unsigned int microDelay = 27750L; //number of uS to wait for initial speed + for(int i = 0;i < accellerateSteps;i++){ + for(byte j = 0;j < 2;j++){ + delayMicroseconds(microDelay - 2); + if(synta.axis()){ //Step + writeSTEP2(HIGH); + delayMicroseconds(2); + writeSTEP2(LOW); + } else { + writeSTEP1(HIGH); + delayMicroseconds(2); + writeSTEP1(LOW); + } + } + microDelay -= speedPerStep; + } + //Now at speed, so return, and timers will be used from now on. +} + +//Timer Interrupt----------------------------------------------------------------------------- +void configureTimer(){ + TIMSK3 &= ~(1<<TOIE3); //disable timer so it can be configured + TIMSK4 &= ~(1<<TOIE4); //disable timer so it can be configured + //set to normal counting mode + TCCR3A &= ~((1<<WGM31) | (1<<WGM30)); + TCCR3B &= ~((1<<WGM33) | (1<<WGM32)); + TCCR4A &= ~((1<<WGM41) | (1<<WGM40)); + TCCR4B &= ~((1<<WGM43) | (1<<WGM42)); + //Disable compare interrupt (only interested in overflow) + TIMSK3 &= ~((1<<OCIE3A) | (1<<OCIE3B) | (1<<OCIE3C)); + TIMSK4 &= ~((1<<OCIE4A) | (1<<OCIE4B) | (1<<OCIE4C)); + //Set prescaler to F_CPU/1 + TCCR3B &= ~(1<<CS32); //0 + TCCR3B |= (1<<CS30);//1 + TCCR3B &= ~(1<<CS31);//0 + TCCR4B &= ~(1<<CS42); //0 + TCCR4B |= (1<<CS40);//1 + TCCR4B &= ~(1<<CS41);//0 +} + +/*Timer Interrupt Vector*/ +ISR(TIMER3_OVF_vect) { + TCNT3 = timerOVF[0]; //reset timer straight away to avoid compounding errors + motorStep(0); +} + +/*Timer Interrupt Vector*/ +ISR(TIMER4_OVF_vect) { + TCNT4 = timerOVF[1]; //reset timer straight away to avoid compounding errors + motorStep(1); +} + +void motorStep(byte motor){ + static byte divider = 0; + if(motor){ + writeSTEP2(HIGH); + delayMicroseconds(2); + writeSTEP2(LOW); + } else { + writeSTEP1(HIGH); + delayMicroseconds(2); + writeSTEP1(LOW); + } + divider++; + if((divider & 7) == 0){ + synta.cmd.jVal(motor, (synta.cmd.jVal(motor) + synta.cmd.stepDir(motor))); + if(synta.cmd.gotoEn(motor)){ + long stepsLeft = gotoPosn[motor] - synta.cmd.jVal(motor); + stepsLeft *= synta.cmd.stepDir(motor); + if(stepsLeft <= 0){ + motorStop(1); + } else if (stepsLeft < 124){ + timerOVF[motor] -= 304; //decelleration region + } + } + } +} + +void writeSTEP1(boolean bitValue){ + if (bitValue){ + STEP1PORT |= _STEP1_HIGH; + } else { + STEP1PORT &= _STEP1_LOW; + } +} + +void writeSTEP2(boolean bitValue){ + if (bitValue){ + STEP2PORT |= _STEP2_HIGH; + } else { + STEP2PORT &= _STEP2_LOW; + } +} +//-------------------------------------------------------------------------------------------- + diff --git a/AstroEQ/commands.cpp b/AstroEQ/commands.cpp new file mode 100644 index 0000000..607cc8c --- /dev/null +++ b/AstroEQ/commands.cpp @@ -0,0 +1,181 @@ +//command Structures --------------------------------------------------------- +// +// Definition of the commands used by the Synta protocol, and variables in which responses +// are stored +// +// Data structure of flag Flag: +// flag = xxxx00ds000g000f where bits: +// x = dont care +// d = dir +// s = stopped +// g = goto +// f = energised +// +// Only dir can be used to set the direction, but stepDir method can be used +// to returns it in a more useful format +// +//---------------------------------------------------------------------------- + +#if ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#include "commands.h" + +void Commands::init(unsigned long eVal,unsigned long aVal,unsigned long bVal,byte gVal,unsigned long sVal){ + for(byte i = 0;i < 2;i++){ + _flag[i] = 0x100; + _jVal[i] = 0x800000; //Current position, 0x800000 is the centre + _IVal[i] = 0; //Recieved Speed + _GVal[i] = 0; //Mode recieved from :G command + _HVal[i] = 0; //Value recieved from :H command + _eVal[i] = eVal; //version number + _aVal[i] = aVal; //steps/axis + _bVal[i] = bVal; //sidereal rate + _gVal[i] = gVal; //High speed scalar + _sVal[i] = sVal; //steps/worm rotation + } +} + +const char Commands::command[numberOfCommands][3] = { {'e', 0, 6}, + {'a', 0, 6}, + {'b', 0, 6}, + {'g', 0, 2}, + {'s', 0, 6}, + {'K', 0, 0}, + {'E', 6, 0}, + {'j', 0, 6}, + {'f', 0, 3}, + {'G', 2, 0}, + {'H', 6, 0}, + {'M', 6, 0}, + {'I', 6, 0}, + {'J', 0, 0}, + {'P', 1, 0}, + {'F', 0, 0}, + {'L', 0, 0} }; + +char Commands::getLength(char cmd, boolean sendRecieve){ + for(byte i = 0;i < numberOfCommands;i++){ + if(command[i][0] == cmd){ + if(sendRecieve){ + return command[i][1]; + } else { + return command[i][2]; + } + } + } + return -1; +} + +unsigned int Commands::fVal(byte target){ + return _flag[target]; +} + +char Commands::stepDir(byte target){ //Get Method + char stepDir = 1 - ((_flag[target] >> 8) & 0x02); //get step direction + return stepDir; +} + +byte Commands::dir(byte target, char dir){ //Set Method + if(dir == 1){ + //set direction + _flag[target] |= (1 << 9); //set bit + } else if (dir == 0){ + _flag[target] &= ~(1 << 9); //unset bit + } else { + dir = (_flag[target] >> 9) & 0x01; //get direction + } + return dir; +} + +byte Commands::stopped(byte target, byte stopped){ //Set Method + if(stopped == 1){ + _flag[target] |= (1 << 8); //set bit + } else if (stopped == 0){ + _flag[target] &= ~(1 << 8); //unset bit + } else { + stopped = (_flag[target] >> 8) & 0x01; + } + return stopped; +} + +byte Commands::gotoEn(byte target, byte gotoEn){ //Set Method + if(gotoEn == 1){ + _flag[target] |= (1 << 4); //set bit + } else if (gotoEn == 0){ + _flag[target] &= ~(1 << 4); //unset bit + } else { + gotoEn = (_flag[target] >> 4) & 0x01; + } + return gotoEn; +} + +byte Commands::FVal(byte target, byte FVal){ //Set Method + if(FVal == 1){ + _flag[target] |= (1 << 0); //set bit + } else if (FVal == 0){ + _flag[target] &= ~(1 << 0); //unset bit + } else { + FVal = (_flag[target] >> 0) & 0x01; + } + return FVal; +} + +unsigned long Commands::jVal(byte target, unsigned long jVal){ //Set Method + if(jVal < 0x01000000){ + _jVal[target] = jVal; + } else { + jVal = _jVal[target]; + } + return jVal; +} + +unsigned long Commands::IVal(byte target, unsigned long IVal){ //Set Method + if(IVal < 0x01000000){ + _IVal[target] = IVal; + } else { + IVal = _IVal[target]; + } + return IVal; +} + +byte Commands::GVal(byte target, byte GVal){ //Set Method + if(GVal < 4){ + _GVal[target] = GVal; + } else { + GVal = _GVal[target]; + } + return GVal; +} + +unsigned long Commands::HVal(byte target, unsigned long HVal){ //Set Method + if(HVal < 0x01000000){ + _HVal[target] = HVal; + } else { + HVal = _HVal[target]; + } + return HVal; +} + +unsigned long Commands::eVal(byte target){ + return _eVal[target]; +} + +unsigned long Commands::aVal(byte target){ + return _aVal[target]; +} + +unsigned long Commands::bVal(byte target){ + return _bVal[target]; +} + +byte Commands::gVal(byte target){ + return _gVal[target]; +} + +unsigned long Commands::sVal(byte target){ + return _sVal[target]; +} diff --git a/AstroEQ/commands.h b/AstroEQ/commands.h new file mode 100644 index 0000000..bd78e96 --- /dev/null +++ b/AstroEQ/commands.h @@ -0,0 +1,64 @@ +//_fVal Flag get/set callers ------------------------------------------------- +// +//Data structure of _fVal Flag: +// _fVal = xxxx00ds000g000f where bits: +// x = dont care +// d = dir +// s = stopped +// g = goto +// f = energised +// +//Only dir can be used to set the direction, but stepDir method can be used +//to returns it in a more useful format +// +//---------------------------------------------------------------------------- +#ifndef commands_h +#define commands_h + #if ARDUINO >= 100 + #include "Arduino.h" + #else + #include "WProgram.h" + #endif + + #define numberOfCommands 17 + + class Commands{ + public: + void init(unsigned long eVal,unsigned long aVal,unsigned long bVal,byte gVal,unsigned long sVal); + + //Command definitions + static const char command[numberOfCommands][3]; + + //Methods for accessing class variables + char stepDir(byte target); //Get Method + byte dir(byte target, char dir = 2); //Get Method + byte stopped(byte target, byte stopped = 2); //Get Method + byte gotoEn(byte target, byte gotoEn = 2); //Get Method + byte FVal(byte target, byte FVal = 2); //Get Method + unsigned int fVal(byte target); //return the flag to main program + unsigned long jVal(byte target, unsigned long jVal = 0x01000000); //Get Method + unsigned long IVal(byte target, unsigned long IVal = 0x01000000); //Get Method + byte GVal(byte target, byte GVal = 4); //Get Method + unsigned long HVal(byte target, unsigned long HVal = 0x01000000); //Get Method + unsigned long eVal(byte target); //Get Method + unsigned long aVal(byte target); //Get Method + unsigned long bVal(byte target); //Get Method + byte gVal(byte target); //Get Method + unsigned long sVal(byte target); //Get Method + + char getLength(char cmd, boolean sendRecieve); + + private: + //class variables + unsigned long _jVal[2]; //_jVal: Current position + unsigned long _IVal[2]; //_IVal: speed to move if in slew mode + byte _GVal[2]; //_GVal: slew/goto mode + unsigned long _HVal[2]; //_HVal: steps to move if in goto mode + unsigned int _flag[2]; //_fVal: 00ds000g000f; d = dir, s = stopped, g = goto, f = energised + unsigned long _eVal[2]; //_eVal: Version number + unsigned long _aVal[2]; //_aVal: Steps per axis revolution + unsigned long _bVal[2]; //_bVal: Sidereal Rate of axis + byte _gVal[2]; //_gVal: Speed scalar for highspeed slew + unsigned long _sVal[2]; //_sVal: Steps per worm gear revolution + }; +#endif diff --git a/AstroEQ/synta.cpp b/AstroEQ/synta.cpp new file mode 100644 index 0000000..133ce43 --- /dev/null +++ b/AstroEQ/synta.cpp @@ -0,0 +1,149 @@ + +#include "synta.h" + +Synta::Synta(unsigned long eVal,unsigned long aVal,unsigned long bVal,byte gVal,unsigned long sVal){ + validPacket = 0; + _axis = 0; + commandIndex = 0; + clearBuffer(commandString,sizeof(commandString)); + cmd.init(eVal, aVal, bVal, gVal, sVal); +} + +const char Synta::startInChar = ':'; +const char Synta::startOutChar = '='; +const char Synta::errorChar = '!'; +const char Synta::endChar = '\r'; + +void Synta::error(char* buf){ + buf[0] = errorChar; + buf[1] = endChar; + buf[2] = 0; +} + +void Synta::clearBuffer(char* buf, byte len){ + strncpy(buf,"",len); +} + +void Synta::success(char* buf, char data[], byte dataLen){ + strcpy(buf + 1,data); + buf[0] = startOutChar; + buf[dataLen] = endChar; + buf[dataLen + 1] = 0; +} + +void Synta::assembleResponse(char* dataPacket, char commandOrError, unsigned long responseData){ + char replyLength = cmd.getLength(commandOrError,0); //get the number of data bytes for response + + char tempStr[11]; + + switch (replyLength){ + case -1: + error(dataPacket); //In otherwords, invalid command, so send error + return; + case 0: + clearBuffer(tempStr,sizeof(tempStr)); //empty temporary string + break; + case 2: + byteToHex(tempStr,responseData); + break; + case 3: + intToHex(tempStr,responseData); + break; + case 6: + longToHex(tempStr,responseData); + break; + } + success(dataPacket,tempStr,replyLength + 1); //compile response + return; +} + +boolean Synta::validateCommand(byte len){ + _command = commandString[0]; //first byte is command + _axis = commandString[1] - 49; //second byte is axis + if(_axis > 1){ + return 0; //incorrect axis + } + char requiredLength = cmd.getLength(_command,1); //get the required length of this command + len -= 3; //Remove the command and axis bytes, aswell as the end char; + if(requiredLength != len){ //If invalid command, or not required length + return 0; + } + + byte i; + for(i = 0;i < len;i++){ + commandString[i] = commandString[i + 2]; + } + commandString[i] = 0; //Null + return 1; +} + +char Synta::recieveCommand(char* dataPacket, char character){ + if(validPacket){ + if (character == startInChar){ + goto error; //new command without old finishing! (dataPacket contains error message) + } + + commandString[commandIndex++] = character; //Add character to current string build + + if(character == endChar){ + if(validateCommand(commandIndex)){ + strcpy(dataPacket,commandString); //Return decoded packet + validPacket = 0; + return 1; //Successful decode (dataPacket contains decoded packet) + } else { + goto error; //Decode Failed (dataPacket contains error message) + } + } else if (commandIndex == sizeof(commandString)){ + goto error; //Message too long! (dataPacket contains error message) + } + } else if (character == startInChar){ + //Begin new command + commandIndex = 0; + validPacket = 1; + clearBuffer(commandString,sizeof(commandString)); + } + return 0; //Decode not finished (dataPacket unchanged) +error: + error(dataPacket); + validPacket = 0; + return -1; +} + +unsigned long Synta::hexToLong(char* hex){ + char *boo; //waste point for strtol + char str[7]; //Destination of rearranged hex + strncpy(str,&hex[4],2); //Lower Byte + strncpy(str+2,&hex[2],2); //Middle Byte + strncpy(str+4,hex,2); //Upper Byte + str[6] = 0; + return strtol(str,&boo,16); //convert hex to long integer +} + +void Synta::longToHex(char* hex, unsigned long data){ + byte bytes[3]; + bytes[0] = (data >> 16) & 0xFF; + bytes[1] = (data >> 8) & 0xFF; + bytes[2] = (data) & 0xFF; + sprintf(hex,"%02X%02X%02X",bytes[2],bytes[1],bytes[0]); +} + +void Synta::intToHex(char* hex, unsigned int data){ + data &= 0xFFF; + sprintf(hex,"%03X",data); +} + +void Synta::byteToHex(char* hex, byte data){ + data &= 0xFF; + sprintf(hex,"%02X",data); +} + +char Synta::command(){ + return _command; +} + +byte Synta::axis(byte axis){ + if(axis < 2){ + _axis = axis; + } + return _axis; +} diff --git a/AstroEQ/synta.h b/AstroEQ/synta.h new file mode 100644 index 0000000..e3fabb7 --- /dev/null +++ b/AstroEQ/synta.h @@ -0,0 +1,44 @@ + +#ifndef synta_h +#define synta_h + #if ARDUINO >= 100 + #include "Arduino.h" + #else + #include "WProgram.h" + #endif + + #include "commands.h" + + class Synta{ + public: + Synta(unsigned long eVal,unsigned long aVal,unsigned long bVal,byte gVal,unsigned long sVal); + Commands cmd; + void assembleResponse(char* dataPacket, char commandOrError, unsigned long responseData); + char recieveCommand(char* dataPacket, char character); + byte axis(byte axis = 2); //make target readonly to outside world. + char command(); //make current command readonly to outside world. + + unsigned long hexToLong(char* hex); + void longToHex(char* hex, unsigned long data); + void intToHex(char* hex, unsigned int data); + void byteToHex(char* hex, byte data); + + private: + void clearBuffer(char* buf, byte len); + void success(char* buf, char data[], byte dataLen); + void error(char* buf); + boolean validateCommand(byte len); + + boolean validPacket; + char commandString[11]; + char commandIndex; + + byte _axis; + char _command; + + static const char startInChar; + static const char startOutChar; + static const char errorChar; + static const char endChar; + }; +#endif diff --git a/README b/README index e69de29..c0edb1d 100644 --- a/README +++ b/README @@ -0,0 +1,17 @@ +AstroEQ Arduino code. Hardware schematics to follow + +Currently it doesn't track at the correct speed, I am working on it. Will add a ZIP download when it is properly functional. + +/* + Code written by Thomas Carpenter 2012 + + With thanks Chris over at the EQMOD Yahoo group for explaining the Skywatcher protocol + + + Equatorial mount tracking system for integration with EQMOD using the Skywatcher/Synta + communication protocol. + + Works with EQ5, HEQ5, and EQ6 mounts + + Current Verison: 3.5 +*/ \ No newline at end of file -- GitLab