3D Printer – Running Motors

An FDM 3D printer uses a nozzle that is heated to the liquid point of the “printing” material and moves that nozzle in specific patterns. These patterns are governed by command from a printing program. The commands are called G Code commands, and became the first standards in controlling CNC machinery, IIRC (If I Remember Correctly).

To print a 3D object, you go from (Sketchup, AutoCad, SolidWorks, etc) to an OBJ (object) or an STL (STereo Lithography) file to a GCode File. There are other formats that can be used, but the STL is most common, and the OBJ can be useful if your drawing program hiccups when creating an STL, or, vice-versa!

The GCode file is generated by a “Slicer” (Slic3r, etc) from an OBJ or STL into a set of GCode Commands.

The most common GCode Commands used are G0, G1, M104, M109, M140, M116. There are a few others, but as I look at printing data streams, that is what I see.

G0, G1 Commands And Effects

A G0 or G1 command are usually treated the same by printer firmware. (This is software which is usually called firmware because it is frozen in ROM, Flash, or EEPROM. Reading up on those terms using Google is an exercise for the reader.)

The side effects of a G0 or G1 command are varied, and can lead to headaches. All printer motors used in cheap 3D printers are stepper motors. Send a pulse to the motor, and it moves its’ axis a certain distance, measured in degrees on a circle.

Each “step” generates a certain amount of force (torque). Stepper motors have great starting torque, but generally lose torque the faster they go. However, inertia (the characteristic of an object in motion; it continues in motion unless stopped) does help this issue to some degree.

Regardless, stepper motors have an upper limit to the speed they can run before their torque becomes too small to be of much use. That speed is actually quite high.

For example, NEMA 17 style motors, the kind in most 3D printers, have a maximum RPM of 4688. They typically have 200 steps per revolution, which works out 1.8 degrees per step. (200 steps * 1.8 degrees/step = 360 degrees.) For older printers, the maximum usable RPM is closer to 900. A speed of 4688 is higher than you usually want to move on a 3D printing platform due to vibration causing issues with the fluid coming out of the nozzle, in addition to the fact the loss of torque won’t let you reach that speed.

So high speed torque loss is less of an issue than you might expect. Since we are running a fraction of the maximum RPM for the motor, the torque loss can be safely ignored in a well designed system. However, using stepper motors to deposit Heated Fluid also leads to some other interesting side effects.

For instance, you don’t want the printer to jerk the head left (using X motor) 1 millimeter, stop, and jerk the head forward one millimeter (using Y motor), stop, then extrude 0.75 mm of plastic (using the Extruder motor). No. Not good. It needs to be a dance with multiple motions simultaneously occurring.

Generally, you want the X motor to step, the Y Motor to step, and Extruder motor to step, each running their associated tasks simultaneously. That is at the heart of the problem of controlling a 3D printer.

If you are only doing software control of the stepping pulses to the motors, then if you have a slow computer, disaster awaits. If your computer is fast enough (as in the case of the Arduino, and most other modern micro-controllers), then the variation between the actual stepper motor pulses is only microseconds. Due to the relatively slow movement of physical objects (such as motor shafts), this is fast enough, and does not generally affect print quality by itself.

PSOC 5 System Strength

If you are a part time perfectionist like me (some things in my life are very sloppy), then having the pulses go out at the exact time they should is very important. Trying to solve this problem caused me all kinds of mental issues, especially in an RTOS, until I developed a good solution.

Note that I want to do this code without using any GPL software, so I could not use Marlin, RepRap, or other code. I am making it GPL for the world, but I also want to be able to license it as I desire in the future. In addition, the code I have seen is not pretty.

So, I kept thinking. On a previous project that moved motors around, I had used counters with compare registers. Those registers could be updated on the fly without affecting counter operation. Also, using pulse stretchers, I could send a pulse out to the stepper driver exactly the size needed, at exactly the correct time, with no jitter. Bingo! Only one problem left. How quickly can the processor do calculations?

If you are running a printer with an RTOS, you have to understand one fact: the code you want to execute may not run in exactly the same time domain as it ran last time. It will execute within a few tens of microseconds, but I never liked that uncertainty when trying to put out signals at microsecond precision.

Even so, the RTOS design paradigm is too useful for me to want to give it up. It really allows you to focus on the job at hand, with distractions minimized. The overall system design (which runs first) can be tweaked, assuming you code decently. Also, I did not want to hamstring the RTOS code with high overhead (long running) interrupt service routines.

Hardware to the Rescue

The PSOC 5 LP has 24 UDB’s for your use. These Universal Digital Blocks are called different names (such as cells) in Xilinx or Altera, but they provide fairly much the same services. In the PSOC 5, the UDB system actually contains miniature ALU’s that can run interesting code. Cypress (now Infineon) published articles about it several years back.

So, there are enough resources in these UDB’s to create several timers. Also several registers. Also several logic based pulse stretchers. I created a stepping system each for the X, Y, Z, and Extruder motors. (Along with PWM’s, etc.)

Hardware Assisted Stepper Motor Pulse Timing

The motors need pulses sent out at exact times an exact number of microseconds apart. The longest timeframe is much less than 65.535 milliseconds, which is what a 1 mhz clock into a 16 bit counter gives us.

The software sets a time for the next pulse to occur. When the timer reaches the compare register value, a pulse goes out through a hardware based pulse stretcher (to deliver the pulse in the time domain the stepper motor driver requires (in this case, greater than 2 microseconds). This timer pulse also generates an ISR which runs code to update the timer hardware.

The ISR is used to load the next pre-calculated time delay into the comparison register. This allows a jitter-free next pulse time output. Afterwards, a post is made to a task to calculate the next pulse timeframe.

The calculator task receives the post (as a pointer to the motor structure) and calculates the time delay to load into the compare register at the next compare pulse.

Since pulses happen a fairly large distance apart from the point of view of an 80 MHZ ARM processor, the calculations can be complex and time consuming without affecting pulse delivery. In addition, the RTOS has enough left over cycles to handle the other tasks required of it.

Following is the schematic of the pulse driver. The signal line “clk_step” is a low speed clock that generates a wide enough pulse to be seen by the stepper driver IC.

The Pulse Driver Schematic

Pulse Drive For the X stepper motor

How It Works

Software loads the Timer_X_Steps Comparison register, and enables the timer. The 1 MHZ count input provides a 1 million per second count rate. The 24 MHZ clock input allows the 1 MHZ clock to come from any source, and even possibly have its rising edge be delayed relative to the 24 MHZ clock without causing jitter.

When the compare register triggers, the pulse goes out, and the ISR is generated. The current compare register is added to the delay time in microseconds, naturally masked off to 16 bits, and placed back into the compare register. This creates a “ring” compare system which produces pulses exactly at the time desired, regardless of how long the processor takes to compute the new value.

Well, almost. Obviously, if the processor takes hundreds and hundreds and hundreds of microseconds to compute the new value you very well could have issues in putting pulses out at the right time.

In the tests I have run, this does not happen. This is due in part to the very high priority given to the compute task. Its job is limited to computes; it does very little else. This design allows it to run similar to an ISR (without blocking out other ISR functions).

I’ve not noticed slowdowns when it is executing (by observing the blinking LED that is handled by the lowest priority task in the system), so it does not appear to be critically affecting timing.

Here is the code for this system. First, X Steps ISR

//-------------------------------------------------------------------
// The X axis motor timer expired, generating an interrupt.
// Process the next move.  The StepperCalculatorTask.c will cause the timer to
// fire again if needed.
//-------------------------------------------------------------------
// allow the compiler to do a pre-reference to speed up ISR
static const  MotorDescriptionType *mdPtrX=&Motor[MOTOR_X_INDEX]; 
//-----
CY_ISR(MOTOR_X_Steps_isr){
    BaseType_t xHigherPriorityTaskWoken;
    int16_t step =mdPtrX->ramp_NextStepPeriod_InUS_int16;
   
    step += Timer_X_Steps_ReadCompare();
    
   /* No tasks have yet been unblocked. */
    xHigherPriorityTaskWoken = pdFALSE;     
     
    // update timer so next step is at us mark.
    Timer_X_Steps_WriteCompare(step);
    
    // to find where this message goes, do a global search for StepperMotorCalculatorQueue
    // it is in StepperCalculatorTask.c at the time of this writing.
    xQueueSendFromISR(StepperMotorCalculatorQueue,&mdPtrX,&xHigherPriorityTaskWoken);
    
    /* a context switch should be performed if xHigherPriorityTaskWoken is 
        equal to pdTRUE.
    */
    portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
    

}

The mdPtrX is pre-calculated by the compile to provide a reference to the in-memory motor structure which is mirroring the current motor’s state. The step is loaded with the number of microseconds to the next pulse out (and ISR). It is then added to the Compare Register in the timer, and placed back into the timer. This is done without upsetting the timer’s activity, so all is good.

Then, the Calculator Task Queue is posted to. The motor involved is given as the queue entry. That saves time during calculation (no lookup of data required). Some time is lost in the post, but not enough to cause timing issues, based on testing.

Leaving the ISR as soon as possible allows the system to respond to the other motor ISR’s in case they are synchronized with this one. The calculator task queue may be loaded up with up to four motors simultaneously, but that has not caused any issues during testing.

Calculator Task

The calculator task code follows:

QueueHandle_t       StepperMotorCalculatorQueue;
//-----------------------------------------------------------------
// Do the calculation for the next move.  Receive a MotorDescription structure
//------------------------------------------------------------------
static portTASK_FUNCTION( vStepperCalculatorTask, pvParameters )
{
   // int16_t i;
   // char c=0;
    static TaskMonitor_t *TaskMonitorPtr = &TaskMonitorArray[STEPPER_CALCULATOR_TASK];
    MotorDescriptionType *mdPtr;
    
    (void) pvParameters;
    
    // add in some padding to queue size for race conditions where all motors trigger.
    StepperMotorCalculatorQueue = xQueueCreate( motorNumberOfMotors, 
                                            sizeof( MotorDescriptionType * ) );
       

	while( StepperMotorCalculatorQueue == 0 )   	{
    		// Queue was not created and must not be used.
            // panic here.
            DebugPrintString("Stepper Motor Calculator Queue Not Created\n\r");
            vTaskDelay(pdMS_TO_TICKS(1000));// warn user the system is dead
    	}     

    vTaskDelay(pdMS_TO_TICKS(50));// wait for other tasks to initialize

    GCodePrintString(STEPPER_MOTOR_CALCULATOR_TASK_STARTED_STRING CRLF);

    for(;;){
        TaskMonitorPtr->runCounter++;// crude profile indicator.
        
        //----------------------------------------------------------
        // The ISR posts here, and we calculate the ramp_NextStepPeriod_InUS 
        // Even if no posts to the queue occur, wake up once a second to let the
        // TaskMonitor code know task is still alive and running. (Through incrementing
        // the runCounter above.)
        //----------------------------------------------------------
        if ( QReceive( StepperMotorCalculatorQueue, &mdPtr, 1000,TaskMonitorPtr) == pdPASS ){
            // We received a post, the system scheduled as it was able, use these
            // spare cycles to determine the next move.
            // during debug and other issues, if at move_no state, don't trigger the timer again
            if (mdPtr->moveState==MOVE_NO)
                continue;
            // calculate the period between the next pulse set, set up timer to fire
           StepperMotor_Calculate_And_Set_NextStepPeriod_InUS(mdPtr);
        }// if QReceive
    }// for(;;)
}//portTASK_FUNCTION

The function called by the calculator task resides in the StepperMotor.c file (which was downgraded from a class to a c file with a function pointing to the instance of the motor’s data. Note that in the function the word “this” is used. That usage allows for easier transition back to a class, should that be desired in the future.

Code to Calculate Next Pulse

This functionality was created by Stanley Reifel in 2014, and published under the MIT license. It essentially uses a ramp to determine how close together the pulses should come. Stan’s code was based on the following:

 // This stepper motor driver is based on Aryeh Elderman's paper "Real Time Stepper  
 // Motor Linear Ramping Just By Addition and Multiplication".  See: 
 //                          www.hwml.com/LeibRamp.pdf

During my down porting from C++ to C, and testing the result, I found some issues with the published code, and corrected them. I honestly can’t remember the problems, they weren’t major. They also could have come from the work necessary to blend it into a hardware controlled pulse output system. Regardless, here is the calculation code:

/* ----------------------------------------------------------------
// Called From Calculator Task:  Another pulse has gone out, it is time to 
// calculate the next pulse time after the current timer expires.
// moveState is already set by the time we are in this routine
   -------------------------------------------------------------- */
void StepperMotor_Calculate_And_Set_NextStepPeriod_InUS(MotorDescriptionType *this)
 { 
   long distanceToTarget_InSteps;
   int16_t MotorIndex = this->motorIndex;
   int16_t homeEndStopResult= getEndStopStatus(
                  this->IndexOfHomeSwitchPinInFunctionArray);
    
   

    // If at the last pulse, since the last timer has expired,
    // we are no longer moving, set that state in the variable
    // disable the move timer for this motor.
    if (this->moveState == MOVE_LAST){
        completeTheMove(this);       
        return;
    }

    
   /*
   // update the current position and speed.  A pulse just went out.    
   // the timer/counter has already had the period from the previous register
   // load go out.  We calculate the next period, and stuff it into the register.
   */
    int16_t adder = this->direction_Scaler;
        
   this->currentPosition_InSteps += adder;// + 1 or -1 

    
    // if HomeSwitch i.e. MIN exists, then use it as a trigger
    if (homeEndStopResult >= 0) {
        // in Configuration.h (or in your printer definition file)
        if (ALWAYS_STOP_IF_MIN_ENDSTOP_TRIGGERED ){
            // if direction of travel is POSITIVE, then ignore MIN 
            // endstop.  If is NEGATIVE, then if MIN endstop triggered,
            // then we are finished.
            if (this->direction_Scaler < 0 ) {
                if (gethomeSwitchTriggerState(this)){// we are triggered
                    completeTheMove(this);       
                }
                return;                
            }
        }
    

        // if we are going to zero and hit endstop, stop the printer,
        // or if we are indeed homing....
        if ( this->homingAxisNow){
            if (gethomeSwitchTriggerState(this)){// we are triggered
                completeTheMove(this);       
                this->homingAxisNow=pdFALSE;
                this->desiredSpeed_InStepsPerSecond = 
                        this->originalDesiredSpeed_InStepsPerSecond;
            }
            return;                
        }
    }
    
    // if no Home End Stop exists, then use 0 from current position as the guide
    if (homeEndStopResult == -1 ) {
        if (this->homingAxisNow ) {
            // just in case we triggered due to code error and were already at home.
            if (this->currentPosition_InSteps <= 0) {
                completeTheMove(this);
                this->homingAxisNow=pdFALSE;
                this->desiredSpeed_InStepsPerSecond = 
                       this->originalDesiredSpeed_InStepsPerSecond;

            }
        }
    }

    
   //
   // check if move has reached its final target position,
   // disable pulse out, since one more timeout for the timer/Counter will occur
   // set the move state to MOVE_LAST, as this was the last move pulse.
finished_true:
    if (this->currentPosition_InSteps == this->targetPosition_InSteps){
        disableMotorPulseOut(MotorIndex);
        this->moveState = MOVE_LAST;
        return;
    }
    
   //
   // determine the distance from the current position to the target
   //
   distanceToTarget_InSteps = this->targetPosition_InSteps - this->currentPosition_InSteps;
   if (distanceToTarget_InSteps < 0) 
     distanceToTarget_InSteps = -distanceToTarget_InSteps;
 
    this->accelerationSign=1;
   //
   // test if it is time to start decelerating, if so change from accelerating to 
   // decelerating
   //
   if (distanceToTarget_InSteps <= this->decelerationDistance_InSteps)
        this->accelerationSign = -1;

   //
   // compute the period for the next step
   // StepPeriodInUS = LastStepPeriodInUS * 
   //   (1 - AccelerationInStepsPerUSPerUS * LastStepPeriodInUS^2)
   //
    if ( this->accelerationSign>0){
        // decrease amount of time per step
       this->ramp_NextStepPeriod_InUS = this->ramp_NextStepPeriod_InUS * 
         (1.0 - this->acceleration_InStepsPerUSPerUS * this->ramp_NextStepPeriod_InUS * 
         this->ramp_NextStepPeriod_InUS);
    } else {
        // increase amount of time per step
       this->ramp_NextStepPeriod_InUS = this->ramp_NextStepPeriod_InUS * 
         (1.0 + this->acceleration_InStepsPerUSPerUS * this->ramp_NextStepPeriod_InUS * 
         this->ramp_NextStepPeriod_InUS);     
    }
    
    // wsm don't slow down slower than initial step rate. (which is slowest)
    // so, in essence, clip the low side
    if (this->accelerationSign < 0){
         if (this->ramp_NextStepPeriod_InUS  > this->motor_InitialStepPeriod_InUS) 
            this->ramp_NextStepPeriod_InUS  = this->motor_InitialStepPeriod_InUS;
    }

    
    //
    // clip the speed so that it does not accelerate beyond the desired velocity
    //
    if (this->ramp_NextStepPeriod_InUS < this->desiredStepPeriod_InUS)
        this->ramp_NextStepPeriod_InUS = this->desiredStepPeriod_InUS;

    // convert to integer to speed up ISR's
    this->ramp_NextStepPeriod_InUS_int16 = this->ramp_NextStepPeriod_InUS;
    
   
    return;
 }

The Motor Status Structure

None of the above makes much sense without the structure being used. (Except for the fact that during the down port, I expanded some of the variable names to be more descriptive of what those variables are doing in the system).

So, following is the structure and functions and enums that contains the current motor information. In addition, I “de-classed” the functions and listed them in the header with some of the original descriptive text.

A note about enums. Enums can hold your hand while writing and compiling code if you use them properly. It is too bad they removed enums from C++. Oh, well. I use enums to make the compiler and editor let me know if I am using a wrong value in a parameter. Nuff said. Following is the motor structure and function headers that were more or less in the original class:

enum stepperMotorIndexes {
    motorXindex=0
    ,motorYindex
    ,motorZindex
    // THE E0, and E1 indexes are not affected by a global F command under G1
    ,motorE0index
    ,motorNumberOfMotors
};

enum MotorDirectionEnum {
    directionBack=0,
    directionForward=1
};

#define MOTOR_X_INDEX   motorXindex
#define MOTOR_Y_INDEX   motorYindex
#define MOTOR_Z_INDEX   motorZindex
#define MOTOR_E0_INDEX  motorE0index
#define NUMBER_OF_STEPPERS_MANAGED motorNumberOfMotors 
#define NUMBER_OF_AXES_MOTORS      motorZindex 
 
// move state values.  
enum moveStatesEnum {
    MOVE_NO         =     0   // already at position, or move will not occur
   ,MOVE_FIRST      =     1   // set everything up, no pulse out yet
   ,MOVE_ONGOING    =     2   // pulses go out, compute pulse for next isr  
   ,MOVE_LAST       =     3   // this state is the last pulse output.
};
    
#define NUMBER_OF_PULSE_EVENT_COMPILATIONS 16   /* plenty of room*/
  

// the structure is *almost* in alphabetical order.
// keep the identifying information at the top of the structure
// to make it easier to debug.
typedef struct MotorDescriptionType_struct {
    char       motorDescription;        // X, Y, Z, E
    char       motorIndex;             //0, 1, 2, ...

    enum moveStatesEnum  moveState; // if MOVE_FIRST, move is primed to start.

    int16_t     accelerationSign;   // positive if accelerating, negative if not.
    double      acceleration_InStepsPerSecondPerSecond;
    double      acceleration_InStepsPerUSPerUS;
    
    int32_t     cpuClockFrequency;    
    int32_t     currentPosition_InSteps;
    int16_t     currentPulse;


    int32_t     decelerationDistance_InSteps;
    double      desiredSpeed_InStepsPerSecond;
    double      desiredStepPeriod_InUS;
    // this is out of alphabetical order to make using debugger easier
    double      originalDesiredSpeed_InStepsPerSecond; // used as recovery


    int16_t     direction_Scaler;

    int16_t     emergencyStop;                      // will shake the 3d printer due to instant halt

    int16_t     homeSwitchPinIsInverted;            // if non-zero, home switch is opposite sense
    int16_t     homingAxisNow;                      // if non-zero, we are homing the axis.
    
    enum PinDefineEnum
                IndexOfHomeSwitchPinInFunctionArray;// if non-zero, is index of home switch
    
    enum PinDefineEnum
                IndexOfEnablePinInFunctionArray; // the value of the enable pin for direct write

    int16_t     imoClockFrequency;
    
    double      motor_InitialStepPeriod_InUS;

    double      ramp_NextStepPeriod_InUS;       
    int16_t     ramp_NextStepPeriod_InUS_int16;  // is faster to use in ISR when is an int.

    int16_t     reverseMotor;                   // when non-zero, reverse scalar
    double      stepsPerRevolution;
    double      stepsPerMillimeter;

    int32_t     targetPosition_InSteps;

}MotorDescriptionType;

extern MotorDescriptionType Motor[NUMBER_OF_STEPPERS_MANAGED];       // x,y,z,e0
//------------FUNCTIONS--------------------------------------------------------
/* // -------------------------------------------------------------------------
   // Start the specified timer/counters simultaneously
*/ // -------------------------------------------------------------------------
void enableMotorTimersSimultaneously(int16_t EnableBitMask) ;

extern int16_t gethomeSwitchTriggerState(MotorDescriptionType *this);

    // return -1 (no switch) 0 unswitched 1 triggered
extern int16_t  getEndStopStatus(enum PinDefineEnum pinNumber);  

/* // --------------------------------------------------------------------------
   // constructor for the motor pseudo class
*/ // --------------------------------------------------------------------------
void StepperMotor_init( MotorDescriptionType *this,int16_t MotorIndex,
                        int16_t enablePinIndex,char name,
                        enum PinDefineEnum homeSwitchIndex, int16_t invertHomeSwitch);


// ---------------------------------------------------------------------------------
// Called to calculate the time between the next pulse set.  With an RTOS, is
// called from a high priority task due to the ISR from the last timer/counter
// period reload.
// ---------------------------------------------------------------------------------
void StepperMotor_Calculate_And_Set_NextStepPeriod_InUS(MotorDescriptionType *this);

// ---------------------------------------------------------------------------------
// return non-zero if any motor is in the process of moving.  Uses the 
// move state.
// ---------------------------------------------------------------------------------
int16 StepperMotor_isAnyMotorMoving();

// ---------------------------------------------------------------------------------
// set the number of steps the motor has per millimeter
// ---------------------------------------------------------------------------------
void StepperMotor_setStepsPerMillimeter(MotorDescriptionType *this,
                        double motorStepPerMillimeter);

// ---------------------------------------------------------------------------------
// get the current position of the motor in millimeter, this functions is updated
// while the motor moves
//  Exit:  a signed motor position in millimeter returned
// ---------------------------------------------------------------------------------
double StepperMotor_getCurrentPositionInMillimeters(MotorDescriptionType *this);

// ---------------------------------------------------------------------------------
// set current position of the motor in millimeter, this does not move the motor
// ---------------------------------------------------------------------------------
void StepperMotor_setCurrentPositionInMillimeters(MotorDescriptionType *this,
                        double currentPositionInMillimeter);
//
// set the maximum speed, units in millimeters/second, this is the maximum speed  
// reached while accelerating
// Note: this can only be called when the motor is stopped
//  Enter:  speedInMillimetersPerSecond = speed to accelerate up to, units in 
//          millimeters/second
//
void StepperMotor_setMaximumSpeedInMillimetersPerSecond(MotorDescriptionType *this,
                        double speedInMillimetersPerSecond);
//
// set the rate of acceleration, units in millimeters/second/second
// Note: this can only be called when the motor is stopped
//  Enter:  accelerationInMillimetersPerSecondPerSecond = rate of acceleration,  
//          units in millimeters/second/second
//
void StepperMotor_setAcceleration_InMillimetersPerSecondPerSecond(MotorDescriptionType *this,
                    double accelerationInMillimetersPerSecondPerSecond);

//
// move relative to the current position, units are in millimeters, this function  
// does not return until the move is complete
//  Enter:  distanceToMoveInMillimeters = signed distance to move relative to the  
//          current position in millimeters
void StepperMotor_moveRelativeInMillimeters(MotorDescriptionType *this,
                                            double distanceToMoveInMillimeters
);
//
// setup a move relative to the current position, units are in millimeters, no   
// motion occurs until processMove() is called
// Note: this can only be called when the motor is stopped
//  Enter:  distanceToMoveInMillimeters = signed distance to move relative to the  
//            currentposition in millimeters
int16_t StepperMotor_InitializeRelativeMoveInMillimeters(MotorDescriptionType *this
                                            ,double distanceToMoveInMillimeters
);

//
// setup a move, units are in millimeters, no motion occurs until processMove() is 
// called.  Note: this can only be called when the motor is stopped
//  Enter:  absolutePositionToMoveToInMillimeters = signed absolute position to move  
//          to in units of millimeters
int16_t StepperMotor_InitializeMoveInMillimeters(MotorDescriptionType *this
                        ,double absolutePositionToMoveToInMillimeters
);


//
// Get the current velocity of the motor in millimeters/second.  This functions is 
// updated while it accelerates up and down in speed.  This is not the desired  
// speed, but the speed the motor should be MOVE_ONGOING at the time the function is   
// called.  This is a signed value and is negative when motor is MOVE_ONGOING backwards.
// Note: This speed will be incorrect if the desired velocity is set faster than
// this library can generate steps, or if the load on the motor is too great for
// the amount of torque that it can generate.
//  Exit:  velocity speed in millimeters per second returned, signed
//
double StepperMotor_getCurrentVelocityInMillimetersPerSecond(MotorDescriptionType *this);

//
// set the number of steps the motor has per revolution
//
void StepperMotor_setStepsPerRevolution(MotorDescriptionType *this,
                        double motorStepPerRevolution);


//
// get the current position of the motor in revolutions, this functions is updated
// while the motor moves
//  Exit:  a signed motor position in revolutions returned
//
double StepperMotor_getCurrentPositionInRevolutions(MotorDescriptionType *this);

//
// set current position of the motor in revolutions, this does not move the motor
//
void StepperMotor_setCurrentPositionInRevolutions(MotorDescriptionType *this,
                    double currentPositionInRevolutions);

//
// set the maximum speed, units in revolutions/second, this is the maximum speed  
// reached while accelerating.  Note: this can only be called when the motor is 
// stopped
//  Enter:  speedInRevolutionsPerSecond = speed to accelerate up to, units in 
//            revolutions/second
//
void StepperMotor_setMaximumSpeedInRevolutionsPerSecond(MotorDescriptionType *this,
                    double speedInRevolutionsPerSecond);

//
// set the rate of acceleration, units in revolutions/second/second
// Note: this can only be called when the motor is stopped
//  Enter:  accelerationInRevolutionsPerSecondPerSecond = rate of acceleration,  
//            units inrevolutions/second/second
//
void StepperMotor_setAcceleration_InRevolutionsPerSecondPerSecond(MotorDescriptionType *this,
                   double accelerationInRevolutionsPerSecondPerSecond);


// move relative to the current position, units are in revolutions, this function  
// does not return until the move is complete
//  Enter:  distanceToMoveInRevolutions = signed distance to move relative to the  
//          current position in revolutions
//
void StepperMotor_moveRelativeInRevolutions(MotorDescriptionType *this,
                            double distanceToMoveInRevolutions);



//
// setup a move relative to the current position, units are in revolutions, no   
// motion occurs until processMove() is called.  Note: this can only be called 
// when the motor is stopped
//  Enter:  distanceToMoveInRevolutions = signed distance to move relative to the  
//          current position in revolutions
void StepperMotor_setupRelativeMoveInRevolutions(MotorDescriptionType *this
                                        ,double distanceToMoveInRevolutions
);

//
// setup a move, units are in revolutions, no motion occurs until processMove() is 
// called.  Note: this can only be called when the motor is stopped
//  Enter:  absolutePositionToMoveToInRevolutions = signed absolute position to  
//          move to inunits of revolutions
void StepperMotor_setupMoveInRevolutions(MotorDescriptionType *this
                                ,double absolutePositionToMoveToInRevolutions
);


//
// Get the current velocity of the motor in revolutions/second.  This functions is 
// updated while it accelerates up and down in speed.  This is not the desired  
// speed, but the speed the motor should be MOVE_ONGOING at the time the function is   
// called.  This is a signed value and is negative when motor is MOVE_ONGOING backwards.
// Note: This speed will be incorrect if the desired velocity is set faster than
// this library can generate steps, or if the load on the motor is too great for
// the amount of torque that it can generate.
//  Exit:  velocity speed in revolutions per second returned, signed
//
double StepperMotor_getCurrentVelocityInRevolutionsPerSecond(MotorDescriptionType *this);


 // ---------------------------------------------------------------------------------
// set the current position of the motor in steps, this does not move the motor
// Note: This function should only be called when the motor is stopped
//    Enter:  currentPositionInSteps = the new position of the motor in steps
 // ---------------------------------------------------------------------------------
void StepperMotor_setCurrentPositionInSteps(MotorDescriptionType *this,long currentPositionInSteps);



 // ---------------------------------------------------------------------------------
// get the current position of the motor in steps, this functions is updated
// while the motor moves
//  Exit:  a signed motor position in steps returned
 // ---------------------------------------------------------------------------------
long StepperMotor_getCurrentPositionInSteps(MotorDescriptionType *this);

//------------------------------------------------------------------------------
// setup a "Stop" to begin the process of decelerating from the current velocity to  
// zero, decelerating requires calls to processMove() until the move is complete
// Note: This function can be used to stop a motion initiated in units of steps or 
// revolutions
// -----------------------------------------------------------------------------
void StepperMotor_setupStop(MotorDescriptionType *this);

//
// set the maximum speed, units in steps/second, this is the maximum speed reached  
// while accelerating
// Note: this can only be called when the motor is stopped
//  Enter:  speedInStepsPerSecond = speed to accelerate up to, units in steps/second
//
void StepperMotor_setMaximumSpeedInStepsPerSecond(MotorDescriptionType *this,
                    double speedInStepsPerSecond);
//
// set the rate of acceleration, units in steps/second/second
// Note: this can only be called when the motor is stopped
//  Enter:  accelerationInStepsPerSecondPerSecond = rate of acceleration, units in 
//          steps/second/second
//
void StepperMotor_setAcceleration_InStepsPerSecondPerSecond(MotorDescriptionType *this,
                   double accelerationInStepsPerSecondPerSecond);


//
// home the motor by MOVE_ONGOING until the homing sensor is activated, then set the 
// position to zero with units in steps
//  Enter:  directionTowardHome = 1 to move in a positive direction, -1 to move in 
//             a negative directions 
//          speedInStepsPerSecond = speed to accelerate up to while MOVE_ONGOING toward 
//             home, units in steps/second
//          maxDistanceToMoveInSteps = unsigned maximum distance to move toward 
//             home before giving up
//          homeSwitchPin = pin number of the home switch, switch should be 
//             configured to go low when at home
//  Exit:   true returned if successful, else false
//
bool StepperMotor_moveToHomeInSteps(MotorDescriptionType *this,long directionTowardHome, 
double speedInStepsPerSecond, long maxDistanceToMoveInSteps);


//
// move relative to the current position, units are in steps, this function does 
// not return until the move is complete
//  Enter:  distanceToMoveInSteps = signed distance to move relative to the current 
//            position in steps
//
void StepperMotor_moveRelativeInSteps(MotorDescriptionType *this,long distanceToMoveInSteps);
//
// setup a move relative to the current position, units are in steps, no motion  
// occurs until processMove() is called.  Note: this can only be called when the 
// motor is stopped
//  Enter:  distanceToMoveInSteps = signed distance to move relative to the current  
//          position in steps
int16_t StepperMotor_setupRelativeMoveInSteps(MotorDescriptionType *this
                                            ,long distanceToMoveInSteps
);

//-------------------------------------------------------------------------------
// setup a move, units are in steps, no motion occurs until processMove() is called
// Note: this can only be called when the motor is stopped
//  Enter:  absolutePositionToMoveToInSteps = signed absolute position to move to in 
//          units of steps
// return TRUE if motor will not move (already at target)
// return FALSE if motor has started MOVE_ONGOING.
//-------------------------------------------------------------------------------
enum moveStatesEnum StepperMotor_InitializeMotorMoveInSteps(MotorDescriptionType *this,
                                                long absolutePositionToMoveToInSteps,
                                                int16_t ignorehomeSwitchPin
);


//
// Get the current velocity of the motor in steps/second.  This functions is updated
// while it accelerates up and down in speed.  This is not the desired speed, but  
// the speed the motor should be MOVE_ONGOING at the time the function is called.  This  
// is a signed value and is negative when the motor is MOVE_ONGOING backwards.
// Note: This speed will be incorrect if the desired velocity is set faster than
// this library can generate steps, or if the load on the motor is too great for
// the amount of torque that it can generate.
//  Exit:  velocity speed in steps per second returned, signed
//
double StepperMotor_getCurrentVelocityInStepsPerSecond(MotorDescriptionType *this);


//
// check if the motor has competed its move to the target position
//  Exit:  true returned if the stepper is at the target position
//
bool StepperMotor_QueryMotionComplete(MotorDescriptionType *this);



Next Time

Next time I hope to give some photos and results of a dry-run using a very old printer carcass. If that goes well (moving without running filament through), then I will upload the SOCINO adapter card for the CY8CKit059.

At some point in the future, the entire project will be dumped into github, available for the whole world. If you want the intermediate project, contact me directly.

Enjoy!

One More Thing

The SOCino board adapts a Cy8CKit-059 board to a RAMPS 1.4x board. The RAMPS board uses the Pololu A4988 driver boards. You can find out about them here: https://www.pololu.com/product/1182.

Easy adjustment of these driver boards has always been a mystery to me. I finally did some deep dive research and found adjustment to be very simple.

All you have to do is power up the PSOC and the RAMPS board system.* Then connect a voltmeter to a tiny Phillips screwdriver (I use jumper clips) and to ground. Set the voltmeter to measure DC volts. If you touch the screwdriver to the POT on the Pololu, you measure the setting on the board in volts.

Dial the voltage into around 0.49 volts. You can go higher if your motors don’t run well, or lower the setting if the motors are getting too hot (and if lowering still allows proper operation).

That’s it!

* 5 volts and 12volts. The 5 can come from the 12 (supplied by the RAMPS board) if you put a voltage regulator in and solder bridge JP1 on the SOCino board. You can also supply 5v from a USB extender to the finger. If you are driving a disply, also plug in the micro-usb connector on the other side. (I have found that both are needed if you are also powering a RepRap SmartController display unit. The current requirements are enough to drop the PSOC voltage unless supplied by 2 USB connectors.)

Add a Comment

Your email address will not be published. Required fields are marked *