// This program was written by Carl W Livingston, KC5OTL.
// I freely grant permission to use this program for personal use only.
// No commercial use of this program is allowed unless written authorization
// is granted by the author, Carl W. Livingston, KC5OTL.
// If changes are made to this program, due credit must be given to the
// original author, Carl W. Livingston, KC5OTL.
// While this program may migrate to the global public domain, this program
// was written for my personal use and is released to
www.AVRFreaks.net for
// the personal use of the members of
www.AVRFreaks.net. If this program is
// posted on web-sites other then
www.AVRFreaks.net, full, and due credit MUST be
// given to the Author known as microcarl, who is a
www.AVRFreaks.net member,
// and whose full and legal name is Carl W. Livingston.
/*********************************************************************************
// DISCLAIMER!!!
// The author of this program assumes no liability for damage, monitary loss, or
// programming errors in this program. The use of this program is at the risk
// of the end user.
/*
This stepper motor program is currently set up on an Atmel STK500/STK501 development borad running at 21.855 MHz and driving a GeckoDrive G210 stepper motor drive. See http://www.geckodrive.com/
The GeckoDrive stepper motor controller uses STEP/DIRECTION inputs to control the stepper motor velocity and direction.
The program was written using the ImageCraft ICCAVR V7.xx C complier. Code compression was used during compilation of the program.
This program has been tested on the Mega64. I have also added conditional compilation clauses to use the Mega16 and Mega32 microcontrollers.
Using the ImageCraft ICCAVR V7.00 compiler with code compression, the Mega16 compiles out at about 60% usage of its FLASH memory, the Mega32 at about 40% and, the Mega64 compiles at about 11%.
While this program uses the STEP/DIRECTION output format it can be easily converted to utilize a bi-polar steppr motor with the use of two appropriately sized H-Bridge and a uni-polar stepper motor using four appropriately sized power MOS-FETS. Future releases of this program will be released showing the use of bi-polar and uni-polar stepper motors.
While configuring a stepper motor to move in a forward or reverse direction is somewhat trivial, I found that the acceleration and deceleration aspects was not.
This stepper motor program uses fixed acceleration and deceleration rates.
Future releases will also include adjustable acceleration and deceleration.
If the sum of the fixed acceleration and deceleration counts (T1 + T3) is greater then the total move counts, the move velocity is controlled, along with the fixed acc/dec counts to form a triangle move profile rather then the typical trapazoid move profile.
In addition, if the commanded move velocity is slower then the minimum velocity that will reliably move the stepper motor, no acceleration/deceleration is used.
There are conditional compilation statements, as well, that allow the use of HyperTerminal to display the basic position, step rate and other useful status information. The program line containing "debug" define at the top of the StepCon.c
program (this file) file headder area can be blocked out to turn off the debug code.
The command protocol is as follows:
m xxxxx = set axis move count, where, xxxxx is the number of counts to move.
v xxxxx = set axis velocity count, where, xxxxx is the inverse of axis velocity.
h = home axis at zero position count.
c = cycle start.
s = controlled axis stop.
p = display axis position.
r = report axis status.
The stepper motor is a 1.8 degree per step motor. I.E. 200 steps per motor shaft revolution.
The axis lead screw pitch is 18 turns per inch.
The maxamum physical axis travel is ~13 inches.
Typical command entry would be:
m40000 <RETURN> Move axis ~12 inches
v100 <RETURN> Velocity ~55 inches per minute
c <RETURN> Start axis move.
s <RETURN> Controlled axis stop.
p <RETURN> Reprot current axis position.
r <RETURN> Report current axis status, in hexidecmal.
Axis moves are in step counts. A signed long integer is used for position accumulation.
Axis velocity is in counts and is based on the microcontroller clock frequency.
Timer1 is used along with OCR1A for step and velocity control.
*/
// FILE NAME: StepCon.c
/*************************************************************/
// Standard C library Includes
// Change the following line to iom16v.h or iom32v.h
#include <iom64v.h>
#include <macros.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// StepCon motion controller Includes
// The following two files should reflect changes to support the USART
// differences between the Mega16, Mega32 and Mega64.
// The Mega16 and Mega32 use the generic USART defines.
// The Mega64 USART has USART0 and USART1. For the Mega64, USART1 is used
// to output axis and status information to HyperTerminal.
#include "getchar.c"
#include "putchar.c"
/*************************************************************/
/*********************** DEBUG CONTROL ***********************/
/*************************************************************/
#ifndef DEBUG
// Block out the following line to turn off the debug features.
#define DEBUG
#endif DEBUG
/*************************************************************/
/*************************************************************/
/*************************************************************/
/*************************************************************/
// StepCon motion controller Defines
// Axis status byte flags
#define FIRST_HOME_FLAG 0x01 // Not homed after power up
#define AT_HOME_FLAG 0x02 // Axis Home Flag
#define IN_POSITION_FLAG 0x04 // Axis In Position Flag
// #define Future use 0x08 // Future use
#define MOVE_FLAG 0x10 // Axis Move Enable Flag
#define ACC_FLAG 0x20 // Axis Acceleration Enable
#define DEC_FLAG 0x40 // Axis Deceleration Enable
#define DIR_FLAG 0x80 // Axis Direction FLAG
// PORTC I/O definitions
#define AXIS_NOT_HOMED 0x01 // Not homed after power up
#define AXIS_AT_HOME 0x02 // Axis Home
#define AXIS_IN_POSITION 0x04 // Axis In position
#define AXIS_PLUS_LIMIT 0x08 // (+) Axis limit switch
#define AXIS_MINUS_LIMIT 0x10 // (-) Axis limit switch
#define AXIS_ENABLED 0x20 // Future use
#define AXIS_STEP 0x40 // Axis "STEP" Control Bit
#define AXIS_DIR 0x80 // Axis "DIRECTION" Control Bit
#ifdef DEBUG
/****************************************************************************/
// Macro definitions
// Clear the communications terminal
#define CLEAR_SCREEN (printf("%c[2J", ESC))
// Position cursor on communications terminal. Macro inputs, x = column, y = row
#define PUT_CURSOR(x, y) (printf("%c[%d;%dH", ESC, y, x))
/****************************************************************************/
#endif DEBUG
void InitDevices(void);
void SetVelocity(void);
char *GetData(char *);
char AxisStatus;
char cmd = NULL;
char COMMAND_DATA[10];
unsigned int T1 = 250; // Acceleration time. Default = 250 counts
unsigned int T3 = 250; // Deceleration time. Default = 250 counts
unsigned int CommandVelocity = 100; // Commanded velocity. Default = 100
unsigned int TargetVelocity; // Final move velocity. Processed by command input
unsigned int StartingVelocity = 500; // Starting velocity for move. Default = 500
#ifdef DEBUG
long int TempCount;
#endif DEBUG
long int TargetCount = 0;
long int CurrentCount = 0;
long int TriggerCount;
/****************************************************************************/
// This is the Stepping motor controller main program.
void main(void)
{ // Initialize the microcontroller hardware function blocks.
InitDevices();
// Initialize system variables.
#ifdef __iom16v_h
UDR = NULL;
#endif __iom16v_h
#ifdef __iom32v_h
UDR = NULL;
#endif __iom32v_h
#ifdef __iom64v_h
UDR1 = NULL;
#endif __iom64v_h
cmd = NULL;
AxisStatus = NULL;
PORTC = NULL;
#ifdef DEBUG
CLEAR_SCREEN;
// Set up the communications terminal screen text.
PUT_CURSOR (20, 14);
printf("Feed Rate = ");
PUT_CURSOR(20, 16);
printf("Target Position = ");
PUT_CURSOR(20, 18);
printf("Current Position = ");
PUT_CURSOR(1, 16);
printf("Home: ");
PUT_CURSOR(1, 18);
printf("In Position: ");
#endif DEBUG
SEI(); // Enable the global interrupts.
// Repeat this loop forever!
do
{
#ifdef DEBUG
// Update axis status to the communications terminal.
PUT_CURSOR(40, 14);
TempCount = CommandVelocity;
printf("%6u ", TempCount);
PUT_CURSOR(40, 16);
TempCount = TargetCount;
printf("%6ld ", TempCount);
PUT_CURSOR(40, 18);
TempCount = CurrentCount;
printf("%6ld ", TempCount);
PUT_CURSOR(14, 16);
if (AxisStatus & AT_HOME_FLAG)
printf("Y");
else printf("N");
PUT_CURSOR(14, 18);
if (AxisStatus & IN_POSITION_FLAG)
printf("Y");
else printf("N");
#endif DEBUG
/*
: if (TARGET_COUNT > (T1 + T2)) :
: TARGET_VELOCITY = COMMAND_VELOCITY :
: : : :
: T1 : T2 : T3 :
: : : :
V : : : :
E : : : :
L : :______________________________________________________________: :
O : /: ___:\ :
C : / : DECELERATION WINDOW __/ : \ :
I : / :___ : \ :
T : / : \__ TARGE VELOCITY = COMMAND VELOCITY : \ :
Y : / : : \ :
:/ : : \:
:------:--------------------------------------------------------------:------:
: :
: __ STARTING VELOCITY :
:___/ :
: \__ STARTING COUNT :
: :
: >----------------------- DISTANCE -----------------------> :
: if (abs(TARGET_COUNT) <= (T1 + T2)) :
: TARGET_VELOCITY = STARTING_VELOCITY - (abs(TARGET_COUNT) / 2) :
: :
: : : :
: T1 : T3 : :
: : : :
V : : ____:_________________ COMMAND VELOCITY ______________________ :
E : : / : :
L : :/ _:_ TARGET VELOCITY :
O : /____/ : :
C : /:\ \_:_ MODIFIED DECELERATION WINDOW :
I : / : \ : :
T : / : \ : :
Y : / : \ :___ :
: / : \ : \__ TARGET COUNT :
:/ : \: :
:------:------:--------------------------------------------------------------:
: :
: __ STARTING VELOCITY :
:___/ :
: \__ STARTING COUNT :
: :
: >----------------------- DISTANCE -----------------------> :
*******************************************************************************
* Target Reference Target *
* Count Count Count *
* | | | *
* (-) | (+) (-) | (+) (-) | (+) *
* -------|---N---|-----------------|---0---|-----------------|---N---|--------*
* __| |__ __| |__ __| |__ *
* | | | | | | *
* Forward Reverse Forward Reverse Forward Reverse *
* Decel Decel Decel Decel Decel Decel *
* Trigger Trigger Trigger Trigger Trigger Trigger *
* *
* >----------------- Forward Direction -----------------> *
*******************************************************************************
If the axis is moving forward, the "Forward Deceleration Trigger" value must be
subtracted from the "Target Count", as indicated by the (-).
If the axis is moving in reverse, the "Reverse Deceleration Trigger" value must be added
to the "Target Count", as indicated by the (+).
The result of the above addition or subtraction then becomes the forward or
reverse deceleration trigger point.
The distance between the "Target Count" and the "Forward" or "Reverse" trigger
count is the deceleration window and is where the axis deceleration takes place.
*/
// Check for a character, but don't wait.
#ifdef __iom16v_h
if (UCSRA & (1<<RXC))
cmd = UDR;
#endif __iom16v_h
#ifdef __iom32v_h
if (UCSRA & (1<<RXC))
cmd = UDR;
#endif __iom32v_h
#ifdef __iom64v_h
if (UCSR1A & (1<<RXC1))
cmd = UDR1;
#endif __iom64v_h
switch (cmd)
{
case 'm': // Get the new axis target count.
if (!(AxisStatus & MOVE_FLAG))
{
TargetCount = strtol(GetData(COMMAND_DATA),0,NULL);
if (TargetCount > CurrentCount)
{ // Go forward.
PORTC &= ~AXIS_DIR;
AxisStatus &= ~DIR_FLAG;
}
else
{ // Go reverse.
PORTC |= AXIS_DIR;
AxisStatus |= DIR_FLAG;
}
SetVelocity();
}
cmd = NULL;
break;
case 'p': // Report the current step count.
#ifdef DEBUG
PUT_CURSOR(40, 18);
#endif DEBUG
printf("%6ld ", CurrentCount);
cmd = NULL;
break;
case 'r': // Report the current axis status.
#ifdef DEBUG
PUT_CURSOR(1, 20);
#endif DEBUG
printf("%x ", AxisStatus);
cmd = NULL;
break;
case 'c': // Cycle Start.
if (!(AxisStatus & MOVE_FLAG))
if (TargetCount != CurrentCount)
{
if (CommandVelocity > 500)
OCR1A = CommandVelocity;
else
OCR1A = StartingVelocity;
AxisStatus |= (ACC_FLAG | MOVE_FLAG);
TIMSK |= (1<<OCIE1A); // Enable timer 1, OCR1A interrupt
}
cmd = NULL;
break;
case 's': // Controlled axis Stop.
if (AxisStatus & MOVE_FLAG)
{
if (AxisStatus & DIR_FLAG) // Axis moving reverse?
TargetCount = CurrentCount - T3;
else // The axis is moving forward.
TargetCount = CurrentCount + T3;
AxisStatus |= DEC_FLAG;
AxisStatus &= ~ACC_FLAG;
}
cmd = NULL;
break;
case 'h': // Home the axis.
if (!(AxisStatus & MOVE_FLAG))
{
CurrentCount = 0;
TargetCount = 0;
AxisStatus |= (FIRST_HOME_FLAG | AT_HOME_FLAG | IN_POSITION_FLAG);
}
cmd = NULL;
break;
case 'v': // Set the axes velocity.
if (!(AxisStatus & MOVE_FLAG))
{
CommandVelocity = (unsigned int)atoi(GetData(COMMAND_DATA));
// Limit the "Target Velocity" to a value of 100, "~55 IPM".
if (CommandVelocity < 100)
CommandVelocity = 100;
SetVelocity();
}
cmd = NULL;
break;
}
} while (1); // Repeat this loop forever!
}
void SetVelocity (void)
{
TargetVelocity = CommandVelocity;
if (AxisStatus & AXIS_DIR)
{ // Reverse direction
if (abs(TargetCount - CurrentCount) < (T1 + T3))
{
TargetVelocity = StartingVelocity - (CurrentCount - TargetCount) / 2;
TriggerCount = TargetCount - (TargetCount - CurrentCount) / 2;
}
else TriggerCount = TargetCount + T3;
}
else
{ // Forward direction
if (abs(TargetCount - CurrentCount) < (T1 + T3))
{
TargetVelocity = StartingVelocity - (TargetCount - CurrentCount) / 2;
TriggerCount = TargetCount - (TargetCount - CurrentCount) / 2;
}
else TriggerCount = TargetCount - T3;
}
}
/****************************************************************************/
// Retrieve a character string from the "Host" controller.
// This character string is in ASCII format.
char *GetData(char *d)
{
int n = 0;
char Temp = NULL;
// Clear the character string buffer.
memset(d, NULL, 10);
do
{ // Check for character, but don't wait!
#ifdef __iom16v_h
if (UCSRA & (1<<RXC))
{
Temp = UDR;
#endif __iom16v_h
#ifdef __iom32v_h
if (UCSRA & (1<<RXC))
{
Temp = UDR;
#endif __iom32v_h
#ifdef __iom64v_h
if (UCSR1A & (1<<RXC1))
{
Temp = UDR1;
#endif __iom64v_h
// If the current character is not a "Carrage Return",
// save the current character.
if (Temp != '\r')
d[n++] = Temp;
}
// Keep looping until the entire character string is retrieved.
} while (Temp != '\r');
return(d);
}
/****************************************************************************/
// Timer 1 Output Compare Register 1A, OCR1A intrrupt vector.
#ifdef __iom16v_h
#pragma interrupt_handler SteppingIRQ:7
#endif __iom16v_h
#ifdef __iom32v_h
#pragma interrupt_handler SteppingIRQ:8
#endif __iom32v_h
#ifdef __iom64v_h
#pragma interrupt_handler SteppingIRQ:13
#endif __iom64v_h
// The rate of intrrupt is inversely proportional to the value in OCR1A. That is,
// axis velocity is inversely proportional to current value stored in OCR1A.
// Large values cause the axis to go slow, small values cause the axis to go fast.
void SteppingIRQ(void)
{
PORTC |= AXIS_STEP; // Turn the STEP output on.
if (AxisStatus & DIR_FLAG)
{ // The axis is moving in reverse, decrement axis position counter.
if (--CurrentCount <= TriggerCount)
{
AxisStatus |= DEC_FLAG;
AxisStatus &= ~ACC_FLAG;
}
}
else // The axis is moving forward, increment axis position counter.
if (++CurrentCount >= TriggerCount)
{
AxisStatus |= DEC_FLAG;
AxisStatus &= ~ACC_FLAG;
}
if (CurrentCount == TargetCount)
{ // At target count, disable OCR1A intrrupt
TIMSK &= ~(1<<OCIE1A);
// Clear "MOVE_FLAG", "ACC_FLAG" & "DEC_FLAG".
AxisStatus &= ~(MOVE_FLAG | ACC_FLAG | DEC_FLAG);
// Set "IN_POSITION_FLAG".
AxisStatus |= IN_POSITION_FLAG;
if (CurrentCount == 0)
// Axis is at "Home", set the "HOME_FLAG" bit.
if (AxisStatus & FIRST_HOME_FLAG)
AxisStatus |= AT_HOME_FLAG;
}
else
{
if (CurrentCount != 0)
AxisStatus &= ~AT_HOME_FLAG;
if (CurrentCount != TargetCount)
AxisStatus &= ~IN_POSITION_FLAG;
// Acceleration control.
if (CommandVelocity < 500)
if (AxisStatus & ACC_FLAG)
if (--OCR1A < TargetVelocity)
AxisStatus &= ~ACC_FLAG;
// Deceleration control.
if (CommandVelocity < 500)
if (AxisStatus & DEC_FLAG)
OCR1A ++;
}
PORTC &= ~AXIS_STEP; // Turn the STEP output off.
}