Telescope Control Library
Axis.cpp
- Committer:
- caoyu@caoyuan9642-desktop.MIT.EDU
- Date:
- 2018-09-24
- Revision:
- 19:fd854309cb4c
- Parent:
- 15:0bdda8dadafe
File content as of revision 19:fd854309cb4c:
/* * Axis.cpp * * Created on: 2018��2��24�� * Author: caoyuan9642 */ #include <Axis.h> #define AXIS_DEBUG 0 static inline double min(double x, double y) { return (x < y) ? x : y; } Axis::Axis(double stepsPerDeg, StepperMotor *stepper, const char *name) : stepsPerDeg(stepsPerDeg), stepper(stepper), axisName(name), currentSpeed( 0), currentDirection(AXIS_ROTATE_POSITIVE), slewSpeed( TelescopeConfiguration::getDouble("default_slew_speed")), trackSpeed( TelescopeConfiguration::getDouble( "default_track_speed_sidereal") * sidereal_speed), guideSpeed( TelescopeConfiguration::getDouble( "default_guide_speed_sidereal") * sidereal_speed), status( AXIS_STOPPED), slewState(AXIS_NOT_SLEWING), slew_finish_sem(0, 1), slew_finish_state(FINISH_COMPLETE), guiding(false), pec(NULL), pecEnabled(false) { if (stepsPerDeg <= 0) error("Axis: steps per degree must be > 0"); if (!stepper) error("Axis: stepper must be defined"); taskName = new char[strlen(name) + 10]; strcpy(taskName, name); strcat(taskName, " task"); /*Start the task-handling thread*/ task_thread = new Thread(osPriorityAboveNormal, OS_STACK_SIZE, NULL, taskName); task_thread->start(callback(this, &Axis::task)); tim.start(); } Axis::~Axis() { // Wait until the axis is stopped if (status != AXIS_STOPPED) { stop(); while (status != AXIS_STOPPED) { Thread::wait(100); } } // Terminate the task thread to prevent illegal access to destroyed objects. task_thread->terminate(); delete task_thread; delete taskName; } void Axis::task() { /*Main loop of RotationAxis*/ while (true) { /*Get next message*/ msg_t *message; enum msg_t::sig_t signal; float value; axisrotdir_t dir; bool wc; // Wait for next message osEvent evt = task_queue.get(); if (evt.status == osEventMessage) { /*New message arrived. Copy the data and free is asap*/ message = (msg_t*) evt.value.p; signal = message->signal; value = message->value; dir = message->dir; wc = message->withCorrection; task_pool.free(message); } else { /*Error*/ debug("%s: Error fetching the task queue.\n", axisName); continue; } debug_if(AXIS_DEBUG, "%s: MSG %d %f %d 0x%8x\n", axisName, signal, value, dir); /*Check the type of the signal, and start corresponding operations*/ switch (signal) { case msg_t::SIGNAL_SLEW_TO: if (status == AXIS_STOPPED) { slew(dir, value, false, wc); } else { debug("%s: being slewed while not in STOPPED mode.\n", axisName); } debug_if(0, "%s: SIG SLEW 0x%08x\n", axisName, Thread::gettid()); slew_finish_sem.release(); /*Send a signal so that the caller is free to run*/ break; case msg_t::SIGNAL_SLEW_INDEFINITE: if (status == AXIS_STOPPED || status == AXIS_INERTIAL) { slew(dir, 0.0, true, false); } else { debug("%s: being slewed while not in STOPPED mode.\n", axisName); } break; case msg_t::SIGNAL_TRACK: if (status == AXIS_STOPPED) { track(dir); } else { debug("%s: trying to track while not in STOPPED mode.\n", axisName); } break; default: debug("%s: undefined signal %d\n", axisName, message->signal); } } } void Axis::slew(axisrotdir_t dir, double dest, bool indefinite, bool useCorrection) { if (!indefinite && (isnan(dest) || isinf(dest))) { debug("%s: invalid angle.\n", axisName); return; } if (dir == AXIS_ROTATE_STOP) { debug("%s: skipped.\n", axisName); return; } slew_mode(); // Switch to slew mode Thread::signal_clr( AXIS_STOP_SIGNAL | AXIS_EMERGE_STOP_SIGNAL | AXIS_SPEEDCHANGE_SIGNAL); // Clear flags bool isInertial = (status == AXIS_INERTIAL); status = AXIS_SLEWING; slewState = AXIS_NOT_SLEWING; slew_finish_state = FINISH_COMPLETE; currentDirection = dir; stepdir_t sd = (dir == AXIS_ROTATE_POSITIVE) ? STEP_FORWARD : STEP_BACKWARD; /* Calculate the angle to rotate*/ bool skip_slew = false; double angleDeg = getAngleDeg(); double delta; delta = (dest - angleDeg) * (dir == AXIS_ROTATE_POSITIVE ? 1 : -1); /*delta is the actual angle to rotate*/ if (fabs(delta) < 1e-5) { // FIX: delta 0->360degrees when angleDeg is essentially equal to dest delta = 0; } delta = remainder(delta - 180.0, 360.0) + 180.0; /*Shift to 0-360 deg*/ debug_if(AXIS_DEBUG, "%s: start=%f, end=%f, delta=%f\n", axisName, angleDeg, dest, delta); double startSpeed = 0; double endSpeed = slewSpeed, waitTime; unsigned int ramp_steps; double acceleration = TelescopeConfiguration::getDouble("acceleration"); if (!indefinite) { // Use the GOTO speed from configuration for slewing endSpeed = TelescopeConfiguration::getDouble("goto_slew_speed"); // Ensure that delta is more than the minimum slewing angle, calculate the correct endSpeed and waitTime if (delta > TelescopeConfiguration::getDouble("min_slew_angle")) { /*The motion angle is decreased to ensure the correction step is in the same direction*/ delta = delta - 0.5 * TelescopeConfiguration::getDouble("min_slew_angle"); double angleRotatedDuringAcceleration; /* The actual endSpeed we get might be different than this due to the finite time resolution of the stepper driver * Here we set the dummy frequency and obtain the actual frequency it will be set to, so that the slewing time will be more accurate */ // Calculate the desired endSpeed. If delta is small, then endSpeed will correspondingly be reduced endSpeed = min(sqrt(delta * acceleration), stepper->setFrequency(stepsPerDeg * endSpeed) / stepsPerDeg); ramp_steps = (unsigned int) (endSpeed / (TelescopeConfiguration::getInt("acceleration_step_time") / 1000.0) / acceleration); if (ramp_steps < 1) ramp_steps = 1; angleRotatedDuringAcceleration = 0; /*Simulate the slewing process to get an accurate estimate of the actual angle that will be slewed*/ for (unsigned int i = 1; i <= ramp_steps; i++) { double speed = stepper->setFrequency( stepsPerDeg * endSpeed / ramp_steps * i) / stepsPerDeg; angleRotatedDuringAcceleration += speed * (TelescopeConfiguration::getInt( "acceleration_step_time") / 1000.0) * (i == ramp_steps ? 1 : 2); // Count both acceleration and deceleration } waitTime = (delta - angleRotatedDuringAcceleration) / endSpeed; if (waitTime < 0.0) waitTime = 0.0; // With the above calculations, waitTime should no longer be zero. But if it happens to be so, let the correction do the job debug_if(AXIS_DEBUG, "%s: endspeed = %f deg/s, time=%f, acc=%f\n", axisName, endSpeed, waitTime, acceleration); } else { // Angle difference is too small, skip slewing skip_slew = true; } } else { // Indefinite slewing mode waitTime = INFINITY; // If was in inertial mode, use startSpeed if (isInertial) startSpeed = currentSpeed; // No need for correction useCorrection = false; } /*Slewing -> accel, wait, decel*/ if (!skip_slew) { int wait_ms; uint32_t flags; /*Acceleration*/ slewState = AXIS_SLEW_ACCELERATING; ramp_steps = (unsigned int) ((endSpeed - startSpeed) / (TelescopeConfiguration::getInt("acceleration_step_time") / 1000.0) / acceleration); if (ramp_steps < 1) ramp_steps = 1; debug_if(AXIS_DEBUG, "%s: accelerate in %d steps\n", axisName, ramp_steps); // TODO: DEBUG for (unsigned int i = 1; i <= ramp_steps; i++) { currentSpeed = stepper->setFrequency( stepsPerDeg * ((endSpeed - startSpeed) / ramp_steps * i + startSpeed)) / stepsPerDeg; // Set and update currentSpeed with actual speed if (i == 1) stepper->start(sd); /*Monitor whether there is a stop/emerge stop signal*/ uint32_t flags = osThreadFlagsWait( AXIS_STOP_SIGNAL | AXIS_EMERGE_STOP_SIGNAL, osFlagsWaitAny, TelescopeConfiguration::getInt("acceleration_step_time")); if (flags == osFlagsErrorTimeout) { /*Nothing happened, we're good*/ continue; } else if ((flags & osFlagsError) == 0) { // We're stopped! useCorrection = false; if (flags & AXIS_EMERGE_STOP_SIGNAL) { slew_finish_state = FINISH_EMERG_STOPPED; goto emerge_stop; } else if (flags & AXIS_STOP_SIGNAL) { slew_finish_state = FINISH_STOPPED; goto stop; } } } /*Keep slewing and wait*/ slewState = AXIS_SLEW_CONSTANT_SPEED; debug_if(AXIS_DEBUG, "%s: wait for %f\n", axisName, waitTime); // TODO wait_ms = (isinf(waitTime)) ? osWaitForever : (int) (waitTime * 1000); tim.reset(); while (wait_ms) { flags = osThreadFlagsWait( AXIS_STOP_SIGNAL | AXIS_EMERGE_STOP_SIGNAL | (indefinite ? AXIS_SPEEDCHANGE_SIGNAL : 0), osFlagsWaitAny, wait_ms); /*Wait the remaining time*/ if (flags != osFlagsErrorTimeout) { if (flags & AXIS_EMERGE_STOP_SIGNAL) { slew_finish_state = FINISH_EMERG_STOPPED; useCorrection = false; goto emerge_stop; } else if (flags & AXIS_STOP_SIGNAL) { slew_finish_state = FINISH_STOPPED; useCorrection = false; goto stop; } else if (flags & AXIS_SPEEDCHANGE_SIGNAL) { // Change speed, therefore also changing waittime. Only applies to indefinite slew double newSpeed = slewSpeed; startSpeed = currentSpeed; endSpeed = newSpeed; ramp_steps = (unsigned int) (fabs(endSpeed - startSpeed) / (TelescopeConfiguration::getInt( "acceleration_step_time") / 1000.0) / acceleration); if (ramp_steps < 1) ramp_steps = 1; debug_if(AXIS_DEBUG, "%s: accelerate in %d steps\n", axisName, ramp_steps); // TODO: DEBUG for (unsigned int i = 1; i <= ramp_steps; i++) { currentSpeed = stepper->setFrequency( stepsPerDeg * ((endSpeed - startSpeed) / ramp_steps * i + startSpeed)) / stepsPerDeg; // Set and update currentSpeed with actual speed /*Monitor whether there is a stop/emerge stop signal*/ uint32_t flags = osThreadFlagsWait( AXIS_STOP_SIGNAL | AXIS_EMERGE_STOP_SIGNAL, osFlagsWaitAny, TelescopeConfiguration::getInt( "acceleration_step_time")); if (flags == osFlagsErrorTimeout) { /*Nothing happened, we're good*/ continue; } else if ((flags & osFlagsError) == 0) { // We're stopped! useCorrection = false; if (flags & AXIS_EMERGE_STOP_SIGNAL) { slew_finish_state = FINISH_EMERG_STOPPED; goto emerge_stop; } else if (flags & AXIS_STOP_SIGNAL) { slew_finish_state = FINISH_STOPPED; goto stop; } } } } } if (!indefinite) { wait_ms -= tim.read_ms(); tim.reset(); } } stop: /*Now deceleration*/ slewState = AXIS_SLEW_DECELERATING; endSpeed = currentSpeed; ramp_steps = (unsigned int) (currentSpeed / (TelescopeConfiguration::getInt("acceleration_step_time") / 1000.0) / acceleration); if (ramp_steps < 1) ramp_steps = 1; debug_if(AXIS_DEBUG, "%s: decelerate in %d steps from %f\n", axisName, ramp_steps, endSpeed); // TODO: DEBUG for (unsigned int i = ramp_steps - 1; i >= 1; i--) { currentSpeed = stepper->setFrequency( stepsPerDeg * endSpeed / ramp_steps * i) / stepsPerDeg; // set and update accurate speed // Wait. Now we only handle EMERGENCY STOP signal, since stop has been handled already flags = osThreadFlagsWait( AXIS_EMERGE_STOP_SIGNAL | AXIS_STOP_KEEPSPEED_SIGNAL, osFlagsWaitAny, TelescopeConfiguration::getInt("acceleration_step_time")); if (flags != osFlagsErrorTimeout) { if (flags & AXIS_EMERGE_STOP_SIGNAL) { // We're stopped! useCorrection = false; slew_finish_state = FINISH_EMERG_STOPPED; goto emerge_stop; } else if (flags & AXIS_STOP_KEEPSPEED_SIGNAL) { // Keep current speed status = AXIS_INERTIAL; slewState = AXIS_NOT_SLEWING; return; } } } emerge_stop: /*Fully pull-over*/ slewState = AXIS_NOT_SLEWING; stepper->stop(); currentSpeed = 0; } if (useCorrection) { // Switch mode correction_mode(); double correctionSpeed = TelescopeConfiguration::getDouble( "correction_speed_sidereal") * sidereal_speed; /*Use correction to goto the final angle with high resolution*/ angleDeg = getAngleDeg(); debug_if(AXIS_DEBUG, "%s: correct from %f to %f deg\n", axisName, angleDeg, dest); // TODO: DEBUG double diff = remainder(angleDeg - dest, 360.0); if (diff > TelescopeConfiguration::getDouble("max_correction_angle")) { debug( "%s: correction too large: %f. Check hardware configuration.\n", axisName, diff); stop(); idle_mode(); currentSpeed = 0; slew_finish_state = FINISH_EMERG_STOPPED; status = AXIS_STOPPED; return; } int nTry = 3; // Try 3 corrections at most while (--nTry && fabsf(diff) > TelescopeConfiguration::getDouble( "correction_tolerance")) { /*Determine correction direction and time*/ sd = (diff > 0.0) ? STEP_BACKWARD : STEP_FORWARD; /*Perform correction*/ currentSpeed = stepper->setFrequency(stepsPerDeg * correctionSpeed) / stepsPerDeg; // Set and update actual speed int correctionTime_ms = (int) (fabs(diff) / currentSpeed * 1000); // Use the accurate speed for calculating time debug_if(AXIS_DEBUG, "%s: correction: from %f to %f deg. time=%d ms\n", axisName, angleDeg, dest, correctionTime_ms); //TODO: DEBUG if (correctionTime_ms < TelescopeConfiguration::getInt("min_correction_time")) { break; } /*Start, wait, stop*/ stepper->start(sd); uint32_t flags = osThreadFlagsWait(AXIS_EMERGE_STOP_SIGNAL, osFlagsWaitAny, correctionTime_ms); stepper->stop(); if (flags != osFlagsErrorTimeout) { // Emergency stop! slew_finish_state = FINISH_EMERG_STOPPED; goto emerge_stop2; } angleDeg = getAngleDeg(); diff = remainder(angleDeg - dest, 360.0); } if (!nTry) { debug("%s: correction failed. Check hardware configuration.\n", axisName); } debug_if(AXIS_DEBUG, "%s: correction finished: %f deg\n", axisName, angleDeg); //TODO:DEBUG } emerge_stop2: // Set status to stopped currentSpeed = 0; status = AXIS_STOPPED; idle_mode(); } void Axis::track(axisrotdir_t dir) { track_mode(); if (trackSpeed != 0 && dir != AXIS_ROTATE_STOP) { stepdir_t sd = (dir == AXIS_ROTATE_POSITIVE) ? STEP_FORWARD : STEP_BACKWARD; currentSpeed = stepper->setFrequency(trackSpeed * stepsPerDeg) / stepsPerDeg; currentDirection = dir; stepper->start(sd); } else { // For DEC axis dir = AXIS_ROTATE_STOP; trackSpeed = 0; currentSpeed = 0; currentDirection = AXIS_ROTATE_POSITIVE; } status = AXIS_TRACKING; Thread::signal_clr( AXIS_STOP_SIGNAL | AXIS_EMERGE_STOP_SIGNAL | AXIS_GUIDE_SIGNAL); // Empty the guide queue while (!guide_queue.empty()) guide_queue.get(); while (true) { // Now we wait for SOMETHING to happen - either STOP, EMERGE_STOP or GUIDE uint32_t flags = osThreadFlagsWait( AXIS_GUIDE_SIGNAL | AXIS_STOP_SIGNAL | AXIS_EMERGE_STOP_SIGNAL, osFlagsWaitAny, osWaitForever); if ((flags & osFlagsError) == 0) // has flag { if (flags & (AXIS_STOP_SIGNAL | AXIS_EMERGE_STOP_SIGNAL)) { // We stop tracking break; } else if (flags & AXIS_GUIDE_SIGNAL) { bool stopped = false; guiding = true; // Guide. Process all commands in the queue while (true) { osEvent evt = guide_queue.get(0); // try to get a message if (evt.status == osEventMessage) { int guideTime_ms = (int) evt.value.p; if (guideTime_ms == 0) continue; // Nothing to guide // Determine guide direction axisrotdir_t guide_dir = (guideTime_ms > 0) ? AXIS_ROTATE_POSITIVE : AXIS_ROTATE_NEGATIVE; double newSpeed = (guide_dir == currentDirection) ? trackSpeed + guideSpeed : trackSpeed - guideSpeed; /*Determine speed in the original direction (currentDirection)*/ // Clamp to maximum guide time guideTime_ms = abs(guideTime_ms); if (guideTime_ms > TelescopeConfiguration::getInt( "max_guide_time")) { debug("Axis: Guiding time too long: %d ms\n", abs(guideTime_ms)); guideTime_ms = TelescopeConfiguration::getInt( "max_guide_time"); } bool dirswitch = false; if (newSpeed > 0) { currentSpeed = stepper->setFrequency( newSpeed * stepsPerDeg) / stepsPerDeg; //set and update accurate speed if (trackSpeed == 0) { // For DEC, we also need to start the motor stepper->start(STEP_FORWARD); // Reverse direction } } else if (newSpeed < 0) { // stepper->stop(); currentSpeed = stepper->setFrequency( -newSpeed * stepsPerDeg) / stepsPerDeg; //set and update accurate speed stepper->start( (currentDirection == AXIS_ROTATE_POSITIVE) ? STEP_BACKWARD : STEP_FORWARD); // Reverse direction dirswitch = true; } else { // newSpeed == 0, just stop the motor currentSpeed = 0; stepper->stop(); dirswitch = true; // Make sure to recover the original speed } uint32_t flags = osThreadFlagsWait( AXIS_STOP_SIGNAL | AXIS_EMERGE_STOP_SIGNAL, osFlagsWaitAny, guideTime_ms); if (flags != osFlagsErrorTimeout) { //break and stop; stopped = true; break; } if (dirswitch || trackSpeed == 0) { stepper->stop(); // Restart the motor in original direction if (trackSpeed != 0) { stepper->start( (currentDirection == AXIS_ROTATE_POSITIVE) ? STEP_FORWARD : STEP_BACKWARD); // Reverse direction } } // Restore to normal speed if (trackSpeed != 0) currentSpeed = stepper->setFrequency( trackSpeed * stepsPerDeg) / stepsPerDeg; else currentSpeed = 0; // End guiding } else { // No more message to get. Break out break; } } guiding = false; if (stopped) { //Complete break out break; } } } } // Stop currentSpeed = 0; stepper->stop(); status = AXIS_STOPPED; idle_mode(); }