/* * Stepper Driver for focus controller * V2 handles two focusers, a stepper and a DC motor driven one. * V3 has backlash on the stepper * V4 has light and Dew Heater outputs. * */ #include //#include #include #include #include // pin definitions // Stepper Motor, these are bits 0 to 3 of PORTB #define pin_StepperA 8 #define pin_StepperB 9 #define pin_StepperC 10 #define pin_StepperD 11 // DC motor, assumes both are PWM capable #define pin_DC_A 5 #define pin_DC_B 6 // Flat field light control #define pin_LightBox 7 // Light box pin, up to 500 mA #define pin_DewHeater 12 // TODO: check the hardware and put a logic MOSFET on a suitable PWM pin. // Button control #define pin_FocusIn 2 #define pin_FocusOut 3 #define pin_FocusSelect 4 // EEPROM Addresses #define EEPROM_counts 0 #define EEPROM_interval 2 #define EEPROM_target 4 #define EEPROM_offset 6 #define EEPROM_focuser 8 #define EEPROM_speed 9 #define EEPROM_relative 10 #define EEPROM_backlash 12 #define HALFSTEPS // stepper pulse array, half steps // arranged to match motor wiring const byte motorHalfSteps[] = { B00000001, B00000011, B00000010, B00000110, B00000100, B00001100, B00001000, B00001001 }; const byte motorSteps[] = { B00000001, B00000010, B00000100, B00001000, }; // focus motor states enum MotorMode { stopped = 0, moveIn, moveOut, moveToPosition, moveRelative, moveBacklash }; MotorMode motorMode = stopped; // used in the stepper ISR so must be volatile volatile boolean rotIn; volatile unsigned int counts; unsigned int target; int backlash; unsigned int bltarget; // DC motor relative move amount, signed. int relative; unsigned int interval = 5; // focuser selection, 0 is stepper, 1 is DC motor byte selectFocuser = 1; byte focuserSpeed = 128; // temperature measurement, use an LM355 on an analog port #define TemperaturePort A0 float kelvin = 293.15; int offset = 0; // flat light and dew heater control bool light; int brightness; int dewHeater; // Interrupt Service Routines // These all use MsTimer2 to control either the stepper // motor for absolute moves or the DC motor for relative moves // // NOTE! // The DC motor and Stepper motor cannot be moving at the same time. // Stepper motor interrupt service routine. // Use MsTimer2 to call this at the rate required // to give the requested motor speed. // Updates the counts variable. void StepperISR() { // toggle the led for every step. static boolean ledState = HIGH; digitalWrite(13, ledState); ledState = !ledState; // step or half step the motor, use counts to index into // the stepper pulse array #ifdef HALFSTEPS PORTB = (PORTB & B11110000) | motorHalfSteps[counts & B0111]; #else PORTB = (PORTB & B11110000) | motorSteps[counts & B0011]; #endif // change the value of count if (rotIn) { counts--; } else { counts++; } } // DC motor interrupt. // Called by MsTimer2 after the motor duration has elapsed to // stop the DC motor void DCMotorISR() { SetMode(stopped); } // Start of the Arduino code, // sets port states // gets the current properties from the EEPROM void setup() { // set pins 2 and 3 for the button inputs pinMode(pin_FocusIn, INPUT); pinMode(pin_FocusOut, INPUT); pinMode(pin_FocusSelect, INPUT); digitalWrite(pin_FocusIn, HIGH); digitalWrite(pin_FocusOut, HIGH); digitalWrite(pin_FocusSelect, HIGH); // set pins 8 to 11 as outputs // DDRB = DDRB | B00101111; pinMode(pin_StepperA, OUTPUT); pinMode(pin_StepperB, OUTPUT); pinMode(pin_StepperC, OUTPUT); pinMode(pin_StepperD, OUTPUT); // and the DC motor outputs pinMode(pin_DC_A, OUTPUT); pinMode(pin_DC_B, OUTPUT); pinMode(pin_LightBox, OUTPUT); pinMode(pin_DewHeater, OUTPUT); pinMode(13, OUTPUT); // read parameters from EEPROM interval = EEPROM_read_int(EEPROM_interval); counts = EEPROM_read_int(EEPROM_counts); target = EEPROM_read_int(EEPROM_target); offset = EEPROM_read_int(EEPROM_offset); selectFocuser = EEPROM.read(EEPROM_focuser); relative = EEPROM_read_int(EEPROM_relative); focuserSpeed = EEPROM.read(EEPROM_speed); backlash = EEPROM_read_int(EEPROM_backlash); // set the temperature pin input pinMode(TemperaturePort, INPUT); // set up the light box output SoftPWMBegin(); SoftPWMSet(pin_LightBox, 0); Serial.begin(9600); } // The Arduino loop code, called continually. void loop() { // check the serial input if (Serial.available() > 0) { Decode(ReadSerial()); } switch (motorMode) { case moveToPosition: if (counts == target) { SetMode(stopped); } break; case moveBacklash: if (counts == bltarget) { rotIn = TargetDirection(target); motorMode = moveToPosition; } break; } HandleButtons(); static unsigned long nextRead = 0; if (millis() >= nextRead) { nextRead = millis() + 100; ReadTemperature(); } } // hardware protocol // Commands and replies are a single ASCII character followed by an optional // parameter and terminated with a '#' character. // Every command has a response that has the same character. // Numbers are decimal integers and are represented as nnnn in the // definition below. // // Command Reply Description // m# mn# return the current focus motor mode // Mn# Mn# set the focus motor mode, starts and stops movement as required // stopped = 0 // moveIn = 1 // moveOut = 2 // moveToPosition = 3 - for stepper motor only // move relative = 4 - for DC motor only // move to backlash position = 5 // c# cnnnn# return the focuser position in counts // Cnnnn# Cnnnn# set the focus motor position in counts // t# tnnnn# return the current target position // Tnnnn# Tnnnn# set the target position // k# knnn# return the temperature in 1/10 deg Kelvin // Knnn# Knnn# set the current temperature in 1/10 deg Kelvin // S# Snnn# set the motor speed in millisecs per step // s# snnn# get the motor speed in millisecs per step // v# vnn# return the hardware version // b# bnnn# get the backlash in steps // Bnnn# Bnnn# set the backlash in steps // Fn# Fn# set the focuser number, 0 is stepper, 1 is DC motor // f# fn# return the focuser number // Rnnnn# Rnnnn# set the relative move amount // r# rnnnn# return the relative move // Dnnn# Dnnn# Set the DC motor speed, 0 to 255 // d# dnnn# return the DC motor speed // // flat light control // Ln# Ln# set the light intensity, 0 (off) to 255 (full on) // l# ln# get the light intensity // // dew heater control // Hn# Hn# set the dew heater value, 0 (off) to 255 (full on) // h# hn# get the dew heater value void Decode(char cmd) { int out; int val; switch (cmd) { case '#': return; case 'M': val = ReadDecimal(); SetMode(val); case 'm': out = motorMode; break; case 'C': counts = ReadDecimal(); EEPROM_write_int(EEPROM_counts, counts); case 'c': out = counts; break; case 'T': target = ReadDecimal(); EEPROM_write_int(EEPROM_target, target); case 't': out = target; break; case 'K': offset = (int)(kelvin * 10) - ReadDecimal(); EEPROM_write_int(EEPROM_offset, offset); case 'k': //kelvin = analogRead(TemperaturePort); out = (int)(kelvin * 10) - offset; break; case 'S': interval = ReadDecimal(); EEPROM_write_int(EEPROM_interval, interval); SetMode(motorMode); case 's': out = interval; break; case 'v': out = 4; break; case 'X': // save position in EEPROM EEPROM_write_int(EEPROM_counts, counts); break; case 'F': selectFocuser = ReadDecimal(); if (selectFocuser > 1) selectFocuser = 1; if (selectFocuser <= 0) selectFocuser = 0; EEPROM.write(EEPROM_focuser, selectFocuser); case 'f': out = selectFocuser; break; case 'R': relative = ReadDecimal(); EEPROM_write_int(EEPROM_relative, relative); case 'r': out = relative; break; case 'D': focuserSpeed = ReadDecimal(); focuserSpeed = constrain(focuserSpeed, 0, 255); EEPROM.write(EEPROM_speed, focuserSpeed); case 'd': out = focuserSpeed; break; case 'B': backlash = ReadDecimal(); EEPROM_write_int(EEPROM_backlash, backlash); case 'b': out = backlash; break; case 'L': val = ReadDecimal(); brightness = constrain(val, 0, 255); SoftPWMSet(pin_LightBox, brightness); light = brightness > 0; case 'l': out = brightness; break; case 'H': dewHeater = constrain(ReadDecimal(), 0, 255); analogWrite(pin_DewHeater, dewHeater); case 'h': out = dewHeater; break; case '>': Alnitak(); return; default: Serial.print('?'); Serial.write(cmd); Serial.print('#'); return; } Serial.write(cmd); Serial.print(out); Serial.print('#'); } // waits for a serial character to be available and reads it char ReadSerial() { while (Serial.available() == 0) { } return Serial.read(); } void SetMode(int mode) { motorMode = (MotorMode)mode; switch(motorMode) { case stopped: // stop all motors MsTimer2::stop(); PORTB = (PORTB & B11110000) | B00000000; digitalWrite(pin_DC_A, LOW); digitalWrite(pin_DC_B, LOW); digitalWrite(13, LOW); break; case moveIn: rotIn = true; if (selectFocuser == 0) { MsTimer2::set(interval, StepperISR); MsTimer2::start(); } else { analogWrite(pin_DC_A, focuserSpeed); digitalWrite(pin_DC_B, LOW); } break; case moveOut: rotIn = false; if (selectFocuser == 0) { MsTimer2::set(interval, StepperISR); MsTimer2::start(); } else { digitalWrite(pin_DC_A, LOW); analogWrite(pin_DC_B, focuserSpeed); } break; case moveToPosition: // stepper only if ((backlash < 0 && (target > counts)) || (backlash > 0 && (target < counts))) { bltarget = target - backlash; rotIn = TargetDirection(bltarget); motorMode = moveBacklash; } else { rotIn = TargetDirection(target); } MsTimer2::set(interval, StepperISR); MsTimer2::start(); break; case moveRelative: // DC motor only if (relative < 0) { analogWrite(pin_DC_A, focuserSpeed); digitalWrite(pin_DC_B, LOW); } else { digitalWrite(pin_DC_A, LOW); analogWrite(pin_DC_B, focuserSpeed); } MsTimer2::set(abs(relative), DCMotorISR); MsTimer2::start(); break; } } boolean TargetDirection(int destination) { return (destination < counts); } // Handles the buttons. // The buttons need debouncing // Instantiate Debounce objects with a 20 millisecond debounce time //Debounce debounceFocusIn = Debounce( 20 , pin_FocusIn ); //Debounce debounceFocusOut = Debounce( 20 , pin_FocusOut ); void HandleButtons() { static MotorMode ButtonState = stopped; // Update the debounced inputs //debounceFocusIn.update ( ); //debounceFocusOut.update ( ); //if (debounceFocusIn.read() == LOW) if (digitalRead(pin_FocusIn) == LOW) { if (ButtonState != moveIn) { SetMode(moveIn); ButtonState = moveIn; } } //else if (debounceFocusOut.read() == LOW) else if (digitalRead(pin_FocusOut) == LOW) { if (ButtonState != moveOut) { SetMode(moveOut); ButtonState = moveOut; } } else if (ButtonState != stopped) { SetMode(stopped); ButtonState = stopped; } // responds to changes in the switch only static boolean stepperState = HIGH; if (digitalRead(pin_FocusSelect) != stepperState) { stepperState = digitalRead(pin_FocusSelect); selectFocuser = stepperState ? 1 : 0; } } // read a decimal digit // it can start with an optional - or + // all characters must be 0 to 9 // it finishes at the first non numeric char int ReadDecimal() { int sign = 1; int chr = ReadSerial(); int num = 0; switch (chr) { case '-': sign = -1; chr = ReadSerial(); break; case '+': sign = 1; chr = ReadSerial(); break; } while (chr >= '0' && chr <= '9') { num = num * 10 + chr - '0'; chr = ReadSerial(); } return num * sign; } // read a number and convert it to boolean // 0 is false !0 is true boolean ReadBoolean() { return ReadDecimal() != 0; } // EEPROM Integer read/write int EEPROM_read_int(int address) { return EEPROM.read(address) << 8 | EEPROM.read(address+1); } void EEPROM_write_int(int address, int value) { if (EEPROM_read_int(address) == value) return; EEPROM.write(address, value >> 8); EEPROM.write(address+1, value & 0xff); } #define MAX_READS 100 void ReadTemperature() { static unsigned long sumReads = 0; static unsigned int countReads = 0; static float averageReads; if (countReads >= MAX_READS) { countReads--; sumReads -= averageReads; } // read, assuming 0 to 1023 = 0 to 5V sumReads += analogRead(TemperaturePort); countReads ++; averageReads = sumReads/countReads; // convert to temperature, 10 mv/K kelvin = averageReads * 500 / 1023; } // read and parse the Alnitak light box command set. void Alnitak() { char cmd = ReadSerial(); int n = ReadDecimal(); switch(cmd) { case 'P': Serial.write("*P19000\r"); break; case 'O': Serial.write("*O19000\r"); break; case 'C': Serial.write("*C19000\r"); break; case 'D': Serial.write("*D19000\r"); // turn light off SoftPWMSet(pin_LightBox, 0); light = false; break; case 'L': Serial.write("*L19000\r"); // turn light on SoftPWMSet(pin_LightBox, brightness); light = true; break; case 'B': // write to output brightness = constrain(n, 0, 255); Serial.print("*B19"); WriteDecimal(brightness); Serial.print('\r'); break; case 'J': Serial.print("*J19"); WriteDecimal(brightness); Serial.print('\r'); break; case 'S': if (light) Serial.write("*S19010\r"); else Serial.write("*S19000\r"); break; case 'V': Serial.write("*V19004\r"); break; } } void WriteDecimal(int value) { int v = value /100; Serial.write(v +'0'); v = value % 100; Serial.write((v/10) +'0'); v = v % 10; Serial.write(v + '0'); }