Many thanks to my friend George. You showed this to me last year. Well, I finally implemented it. I wanted to add a temperature sensor, and allow absolute positioning, so I modified the code. Here is my version:
// Moonlite-compatible stepper controller
// Written by George Carlson June 2014.
// This version uses the Tiny 2.0 a uController based on the Atmel ATMEGA32U4.
// hardware for the remote hand control is not supported.
//
// Many thanks to orly.andico@gmail.com, for the original command parser, others for bits on code picked up here and there on the net
//
// Since the MoonLite focuser only works with positive integers, I center (zero) my system at 32500. The range is from
// 0 to 65535, so 32500 is a good round number for the center.
// If the Current Position, or New Position is set to 0, this code will set the values at 32500. The reason for this
// is the system is designed to be set at center, manually focused, then focused in a +/- fashion by the controller.
//
// Modifications by Robert Brayton teensyfocus@cinenosin.com
// RSB 24NOV2019 Beautified code; Fixed various bugs; Added absolute addressing (EEPROM); Added DS18B20 temp sensor
/********************************************************************/
// First we include the libraries
#include <OneWire.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
/********************************************************************/
// Temperature sensor DS18B20 pinouts
#define ONE_WIRE_BUS 12 // Data wire for temp sensor
// Pin 10 supplies ground
// Pin 11 supplies Vcc
// Note a 4.7k pull up resistor is required between pins 11 and 12
/********************************************************************/
// Setup a oneWire instance to communicate with any OneWire devices
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
/********************************************************************/
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
/********************************************************************/
#define RUNLED 13 // Amber LED lights whenever motor is active, 11 for the Teensy
#define HOME 32500 // Home position for stepper
#define MAXCOMMAND 32 // Input buffer size
#define SAVED_STEPPER_POSITION 0 // EEPROM address for saved stepper position
// Position counters
bool isRunning = false;
unsigned int Current;
unsigned int Target = HOME;
long DistanceToGo = 0;
// Motor timeout to write current position to EEPROM and shut off motor pins
long millisLastMove = 0;
// Motor speed
int minmotorSpeed = 2;
int maxmotorSpeed = 20;
int motorSpeed = 2;
// Motor connections
int motorPin1 = 2; // Blue - 28BYJ48 pin 1
int motorPin2 = 3; // Pink - 28BYJ48 pin 2
int motorPin3 = 4; // Yellow - 28BYJ48 pin 3
int motorPin4 = 5; // Orange - 28BYJ48 pin 4
// Red - 28BYJ48 pin 5 (VCC)
// lookup table for motor phase control
int StepTable[8] = {0b01001, 0b00001, 0b00011, 0b00010, 0b00110, 0b00100, 0b01100, 0b01000};
unsigned int motorPhase = 0;
void forwardstep()
{
Current++;
for ( int x = 0; x < 4; x++ ) // The 28BYJ48 stepper is geared and and I use a 1:10 Feathertouch focuser
// So I need more sub-steps per major step
{
motorPhase++;
motorPhase &= 0x07;
setOutput(motorPhase);
for (int i = 0; i < motorSpeed >> 1; i++)
delay(1);
}
}
void backwardstep()
{
Current--;
for ( int x = 0; x < 4; x++ )
{
motorPhase--;
motorPhase &= 0x07;
setOutput(motorPhase);
for (int i = 0; i < motorSpeed >> 1; i++)
delay(1);
}
}
void setOutput(int out)
{
digitalWrite(motorPin1, bitRead(StepTable[out], 0));
digitalWrite(motorPin2, bitRead(StepTable[out], 1));
digitalWrite(motorPin3, bitRead(StepTable[out], 2));
digitalWrite(motorPin4, bitRead(StepTable[out], 3));
}
long hexstr2long(char *line)
{
long ret = 0;
ret = strtol(line, NULL, 16);
return (ret);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// start of program
void setup()
{
Serial.begin(9600);
//setup the motor pins as outputs
pinMode(motorPin1, OUTPUT);
pinMode(motorPin2, OUTPUT);
pinMode(motorPin3, OUTPUT);
pinMode(motorPin4, OUTPUT);
// Motor running LED
pinMode(RUNLED, OUTPUT); /* yellow LED */
// Temperature sensor pins
pinMode( 10, OUTPUT );
pinMode( 11, OUTPUT );
digitalWrite( 10, 0 ); // Sensor ground
digitalWrite( 11, 1 ); // Sensor Vcc - sensor uses 1mA, pin is able to source 40mA
sensors.begin();
// Fetch current stepper position from EEPROM
EEPROM.get( SAVED_STEPPER_POSITION, Current );
if ( Current == (unsigned int) -1 ) // All 1's indicate first use
Current = HOME;
}
// Forever Loop
void loop()
{
char inChar;
char line[MAXCOMMAND] = "";
bool eoc = false; // End of command detected
unsigned int idx = 0; // Index of next character in input buffer
int currentTemp = 0;
// Check for a new commend, read the command until the terminating # character
while ( Serial.available() && !eoc )
{
inChar = Serial.read();
if ( inChar == '#' )
eoc = true;
else if ( isAlphaNumeric ( inChar ) )
{
line[idx++] = toupper( inChar );
if (idx >= MAXCOMMAND) // Input buffer overrun
idx = MAXCOMMAND - 1;
}
}
// Process command upon end of command received
if ( eoc )
{
// Parse the command and parameter
char tempString[16];
unsigned int hashCmd;
char param[MAXCOMMAND];
unsigned long parameterValue = 0;
memset(param, 0, MAXCOMMAND);
int len = strlen(line);
hashCmd = (byte(line[0]) | (byte(line[1]) << 8)); /* combine the two command characters into an unsigned int */
// Check for parameter following the command
if ( len > 2 )
{
strncpy(param, line + 2, len - 2);
parameterValue = hexstr2long(param);
}
// the stand-alone program sends :C# :GB# on startup
// :C# is to start a temperature conversion, doesn't require any response (we don't use it)
switch ( hashCmd )
{
// GP command Get current position
case ('P'<<8 | 'G'):
sprintf( tempString, "%04X", Current );
Serial.print( tempString );
Serial.print( "#" );
break;
// GT command Get Temperature
case ('T'<<8 | 'G'):
//tempTemp = GetTemp();
if ( !isRunning )
{
sensors.requestTemperatures(); // Send the command to get temperature reading
// delay( 25 );
currentTemp = sensors.getTempCByIndex(0);
}
sprintf( tempString, "%04X", 0xFFFF & ( currentTemp * 2 ) );
Serial.print( tempString );
Serial.print( "#" );
break;
// GI command 01 if motor running, 00 if not
case ('I'<<8 | 'G'):
Serial.print ( isRunning ? "01#" : "00#" );
break;
case ('B'<<8 | 'G'):
// GB command Get current backlight value, always 00
Serial.print( "00#" );
break;
case ('V'<<8 | 'G'):
// GV command Get software version, always 10
Serial.print( "10#" );
break;
case ('N'<<8 | 'G'):
// GN command Get new (target) position
sprintf( tempString, "%04X", Target );
Serial.print( tempString );
Serial.print( "#" );
break;
case ('C'<<8 | 'G'):
// GC command Get temperature coefficient, always 2
Serial.print( "02#" );
break;
case ('D'<<8 | 'G'):
// GD command Get motor speed
sprintf( tempString, "%02X", motorSpeed );
Serial.print( tempString );
Serial.print( "#" );
break;
case ('H'<<8 | 'G'):
// GH command Get half step mode, always 00
Serial.print( "00#" );
break;
case ('D'<<8 | 'S'):
// SD command Set motor speed
motorSpeed = parameterValue;
if (motorSpeed < minmotorSpeed)
motorSpeed = minmotorSpeed;
if (motorSpeed > maxmotorSpeed)
motorSpeed = maxmotorSpeed;
break;
case ('P'<<8 | 'S'):
// SP command Set current position
// RSB - Should this set both Current and Target, thus resetting Current without moving the stepper??
Current = ( parameterValue == 0 ? HOME : parameterValue );
isRunning = true;
digitalWrite( RUNLED, HIGH );
millisLastMove = millis(); /* reset idle timer */
break;
case ('N'<<8 | 'S'):
// SN command Set (move to) new position
Target = ( parameterValue == 0 ? HOME : parameterValue );
isRunning = true;
digitalWrite( RUNLED, HIGH );
millisLastMove = millis(); /* reset idle timer */
break;
case ('H'<<8 | 'P'):
// PH command Find motor home
Current = HOME;
isRunning = true;
digitalWrite( RUNLED, HIGH );
millisLastMove = millis(); /* reset idle timer */
break;
case ('G'<<8 | 'F'):
// FG command Start motor command
isRunning = true;
digitalWrite( RUNLED, HIGH );
millisLastMove = millis(); /* reset idle timer */
break;
case ('Q'<<8 | 'F'):
// FQ command Stop motor command
isRunning = false;
digitalWrite( RUNLED, LOW );
break;
} // End of command parsing
// Get ready for the next command
memset( line, 0, MAXCOMMAND );
idx = 0;
eoc = false;
parameterValue = 0;
} // end process command
// Compute remaining distance and move the motor
DistanceToGo = Target - Current; /* compute remaining distance to go */
if (DistanceToGo == 0)
{
// Motion is complete
isRunning = false;
if ( ( millis() - millisLastMove ) > 5000 ) // Turn off motor after 5 second pause
{
digitalWrite(motorPin1, 0);
digitalWrite(motorPin2, 0);
digitalWrite(motorPin3, 0);
digitalWrite(motorPin4, 0);
digitalWrite(RUNLED, LOW);
// Store current position in EEPROM
EEPROM.put( SAVED_STEPPER_POSITION, Current );
}
}
if ( isRunning )
{
if ( DistanceToGo > 0 )
forwardstep();
if ( DistanceToGo < 0 )
backwardstep();
}
} // end forever loop