1952 lines
42 KiB
C++
1952 lines
42 KiB
C++
/**********************************************************************
|
|
* Author: tstern
|
|
*
|
|
Copyright (C) 2018 MisfitTech, All rights reserved.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
Written by Trampas Stern for MisfitTech.
|
|
|
|
Misfit Tech invests time and resources providing this open source code,
|
|
please support MisfitTech and open-source hardware by purchasing
|
|
products from MisfitTech, www.misifittech.net!
|
|
*********************************************************************/
|
|
#include "stepper_controller.h"
|
|
|
|
#include "nonvolatile.h" //for programmable parameters
|
|
#include <Wire.h>
|
|
#include <inttypes.h>
|
|
#include "steppin.h"
|
|
|
|
#pragma GCC push_options
|
|
#pragma GCC optimize ("-Ofast")
|
|
|
|
#define WAIT_TC16_REGS_SYNC(x) while(x->COUNT16.STATUS.bit.SYNCBUSY);
|
|
|
|
volatile bool TC5_ISR_Enabled=false;
|
|
|
|
void setupTCInterrupts() {
|
|
|
|
// Enable GCLK for TC4 and TC5 (timer counter input clock)
|
|
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5));
|
|
while (GCLK->STATUS.bit.SYNCBUSY);
|
|
|
|
TC5->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE; // Disable TCx
|
|
WAIT_TC16_REGS_SYNC(TC5) // wait for sync
|
|
|
|
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16; // Set Timer counter Mode to 16 bits
|
|
WAIT_TC16_REGS_SYNC(TC5)
|
|
|
|
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as normal Normal Frq
|
|
WAIT_TC16_REGS_SYNC(TC5)
|
|
|
|
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1; // Set perscaler
|
|
WAIT_TC16_REGS_SYNC(TC5)
|
|
|
|
|
|
TC5->COUNT16.CC[0].reg = F_CPU/NZS_CONTROL_LOOP_HZ;
|
|
WAIT_TC16_REGS_SYNC(TC5)
|
|
|
|
|
|
TC5->COUNT16.INTENSET.reg = 0; // disable all interrupts
|
|
TC5->COUNT16.INTENSET.bit.OVF = 1; // enable overfollow
|
|
// TC5->COUNT16.INTENSET.bit.MC0 = 1; // enable compare match to CC0
|
|
|
|
|
|
NVIC_SetPriority(TC5_IRQn, 2);
|
|
|
|
|
|
// Enable InterruptVector
|
|
NVIC_EnableIRQ(TC5_IRQn);
|
|
|
|
|
|
// Enable TC
|
|
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
|
|
WAIT_TC16_REGS_SYNC(TC5)
|
|
|
|
}
|
|
|
|
static void enableTCInterrupts() {
|
|
|
|
TC5_ISR_Enabled=true;
|
|
NVIC_EnableIRQ(TC5_IRQn);
|
|
TC5->COUNT16.INTENSET.bit.OVF = 1;
|
|
// TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; //Enable TC5
|
|
// WAIT_TC16_REGS_SYNC(TC5) //wait for sync
|
|
}
|
|
|
|
static void disableTCInterrupts() {
|
|
|
|
TC5_ISR_Enabled=false;
|
|
//NVIC_DisableIRQ(TC5_IRQn);
|
|
TC5->COUNT16.INTENCLR.bit.OVF = 1;
|
|
}
|
|
|
|
static bool enterCriticalSection()
|
|
{
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
return state;
|
|
}
|
|
|
|
static void exitCriticalSection(bool prevState)
|
|
{
|
|
if (prevState)
|
|
{
|
|
enableTCInterrupts();
|
|
} //else do nothing
|
|
}
|
|
|
|
|
|
void StepperCtrl::updateParamsFromNVM(void)
|
|
{
|
|
bool state=enterCriticalSection();
|
|
|
|
pPID.Kd=NVM->pPID.Kd*CTRL_PID_SCALING;
|
|
pPID.Ki=NVM->pPID.Ki*CTRL_PID_SCALING;
|
|
pPID.Kp=NVM->pPID.Kp*CTRL_PID_SCALING;
|
|
|
|
vPID.Kd=NVM->vPID.Kd*CTRL_PID_SCALING;
|
|
vPID.Ki=NVM->vPID.Ki*CTRL_PID_SCALING;
|
|
vPID.Kp=NVM->vPID.Kp*CTRL_PID_SCALING;
|
|
|
|
sPID.Kd=NVM->sPID.Kd*CTRL_PID_SCALING;
|
|
sPID.Ki=NVM->sPID.Ki*CTRL_PID_SCALING;
|
|
sPID.Kp=NVM->sPID.Kp*CTRL_PID_SCALING;
|
|
|
|
if (NVM->SystemParams.parametersVaild)
|
|
{
|
|
memcpy((void *)&systemParams, (void *)&NVM->SystemParams, sizeof(systemParams));
|
|
LOG("Home pin %d",systemParams.homePin);
|
|
|
|
}else
|
|
{
|
|
ERROR("This should never happen but just in case");
|
|
systemParams.microsteps=16;
|
|
#if defined(CTRL_POS_PID_AS_DEFAULT)
|
|
systemParams.controllerMode=CTRL_POS_PID;
|
|
#else
|
|
systemParams.controllerMode=CTRL_SIMPLE;
|
|
#endif
|
|
systemParams.dirPinRotation=CW_ROTATION; //default to clockwise rotation when dir is high
|
|
systemParams.errorLimit=(int32_t)ANGLE_FROM_DEGREES(1.8);
|
|
systemParams.errorPinMode=ERROR_PIN_MODE_ENABLE; //default to enable pin
|
|
systemParams.errorLogic=false;
|
|
systemParams.homeAngleDelay=ANGLE_FROM_DEGREES(10);
|
|
systemParams.homePin=-1; //no homing pin configured
|
|
}
|
|
|
|
//default the error pin to input, if it is an error pin the
|
|
// handler for this will change the pin to be an output.
|
|
// for bidirection error it has to handle input/output it's self as well.
|
|
// This is not the cleanest way to handle this...
|
|
// TODO implement this cleaner?
|
|
pinMode(PIN_ERROR, INPUT_PULLUP); //we have input pin
|
|
|
|
if (NVM->motorParams.parametersVaild)
|
|
{
|
|
memcpy((void *)&motorParams, (void *)&NVM->motorParams, sizeof(motorParams));
|
|
} else
|
|
{
|
|
//MotorParams_t Params;
|
|
motorParams.fullStepsPerRotation=200;
|
|
motorParams.currentHoldMa=500;
|
|
motorParams.currentMa=800;
|
|
motorParams.homeHoldMa=200;
|
|
motorParams.homeMa=800;
|
|
motorParams.motorWiring=true;
|
|
//memcpy((void *)&Params, (void *)&motorParams, sizeof(motorParams));
|
|
//nvmWriteMotorParms(Params);
|
|
}
|
|
|
|
stepperDriver.setRotationDirection(motorParams.motorWiring);
|
|
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
|
|
void StepperCtrl::motorReset(void)
|
|
{
|
|
//when we reset the motor we want to also sync the motor
|
|
// phase. Therefore we move forward a few full steps then back
|
|
// to sync motor phasing, leaving the motor at "phase 0"
|
|
bool state=enterCriticalSection();
|
|
|
|
// stepperDriver.move(0,motorParams.currentMa);
|
|
// delay(100);
|
|
// stepperDriver.move(A4954_NUM_MICROSTEPS,motorParams.currentMa);
|
|
// delay(100);
|
|
// stepperDriver.move(A4954_NUM_MICROSTEPS*2,motorParams.currentMa);
|
|
// delay(100);
|
|
// stepperDriver.move(A4954_NUM_MICROSTEPS*3,motorParams.currentMa);
|
|
// delay(100);
|
|
// stepperDriver.move(A4954_NUM_MICROSTEPS*2,motorParams.currentMa);
|
|
// delay(100);
|
|
// stepperDriver.move(A4954_NUM_MICROSTEPS,motorParams.currentMa);
|
|
// delay(100);
|
|
stepperDriver.move(0,motorParams.currentMa);
|
|
delay(1000);
|
|
|
|
setLocationFromEncoder(); //measure new starting point
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
void StepperCtrl::setLocationFromEncoder(void)
|
|
{
|
|
numSteps=0;
|
|
currentLocation=0;
|
|
|
|
if (calTable.calValid())
|
|
{
|
|
int32_t n,x;
|
|
int32_t calIndex;
|
|
Angle a;
|
|
|
|
//set our angles based on previous cal data
|
|
|
|
x=sampleMeanEncoder(200);
|
|
a=calTable.fastReverseLookup(x);
|
|
|
|
//our cal table starts at angle zero, so lets set starting based on this and stepsize
|
|
LOG("start angle %d, encoder %d", (uint16_t)a,x);
|
|
|
|
// we were rounding to nearest full step, but this should not be needed TBS 10/12/2017
|
|
// //TODO we need to handle 0.9 degree motor
|
|
// if (CALIBRATION_TABLE_SIZE == motorParams.fullStepsPerRotation)
|
|
// {
|
|
// n=(int32_t)ANGLE_STEPS/CALIBRATION_TABLE_SIZE;
|
|
//
|
|
// calIndex=((int32_t)((uint16_t)a+n/2)*CALIBRATION_TABLE_SIZE)/ANGLE_STEPS; //find calibration index
|
|
// if (calIndex>CALIBRATION_TABLE_SIZE)
|
|
// {
|
|
// calIndex-=CALIBRATION_TABLE_SIZE;
|
|
// }
|
|
// a=(uint16_t)((calIndex*ANGLE_STEPS)/CALIBRATION_TABLE_SIZE);
|
|
// }
|
|
|
|
|
|
x=(int32_t)((((float)(uint16_t)a)*360.0/(float)ANGLE_STEPS)*1000);
|
|
LOG("start angle after rounding %d %d.%03d", (uint16_t)a,x/1000,x%1000);
|
|
|
|
//we need to set our numSteps
|
|
numSteps=DIVIDE_WITH_ROUND( ((int32_t)a *motorParams.fullStepsPerRotation*systemParams.microsteps),ANGLE_STEPS);
|
|
currentLocation=(uint16_t)a;
|
|
}
|
|
zeroAngleOffset=getCurrentLocation(); //zero the angle shown on LCD
|
|
}
|
|
|
|
void StepperCtrl::acceptPositionAndStealthSwitchMode(feedbackCtrl_t m)
|
|
{
|
|
bool state=enterCriticalSection();
|
|
currentLocationIsDesiredLocation();
|
|
systemParams.controllerMode=m;
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
void StepperCtrl::stealthSwitchMode(feedbackCtrl_t m)
|
|
{
|
|
bool state=enterCriticalSection();
|
|
systemParams.controllerMode=m;
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
int64_t StepperCtrl::getZeroAngleOffset(void)
|
|
{
|
|
int64_t x;
|
|
bool state=enterCriticalSection();
|
|
|
|
x=zeroAngleOffset;
|
|
|
|
exitCriticalSection(state);
|
|
return x;
|
|
}
|
|
|
|
void StepperCtrl::setAngle(int64_t angle)
|
|
{
|
|
bool state=enterCriticalSection();
|
|
|
|
zeroAngleOffset=getCurrentLocation()-angle;
|
|
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
void StepperCtrl::setZero(void)
|
|
{
|
|
//we want to set the starting angle to zero.
|
|
bool state=enterCriticalSection();
|
|
|
|
zeroAngleOffset=getCurrentLocation();
|
|
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
void StepperCtrl::encoderDiagnostics(char *ptrStr)
|
|
{
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
|
|
encoder.diagnostics(ptrStr);
|
|
|
|
if (state) enableTCInterrupts();
|
|
}
|
|
|
|
|
|
//TODO This function does two things, set rotation direction
|
|
// and measures step size, it should be two functions.
|
|
//return is anlge in degreesx100 ie 360.0 is returned as 36000
|
|
float StepperCtrl::measureStepSize(void)
|
|
{
|
|
int32_t angle1,angle2,x,i;
|
|
bool feedback=enableFeedback;
|
|
int32_t microsteps=systemParams.microsteps;
|
|
|
|
systemParams.microsteps=1;
|
|
enableFeedback=false;
|
|
motorParams.motorWiring=true; //assume we are forward wiring to start with
|
|
stepperDriver.setRotationDirection(motorParams.motorWiring);
|
|
/////////////////////////////////////////
|
|
//// Measure the full step size /////
|
|
/// Note we assume machine can take one step without issue///
|
|
|
|
LOG("reset motor");
|
|
motorReset(); //this puts stepper motor at stepAngle of zero
|
|
|
|
LOG("sample encoder");
|
|
|
|
angle1=sampleMeanEncoder(200);
|
|
|
|
LOG("move");
|
|
stepperDriver.move(A4954_NUM_MICROSTEPS/2,motorParams.currentMa); //move one half step 'forward'
|
|
delay(100);
|
|
stepperDriver.move(A4954_NUM_MICROSTEPS,motorParams.currentMa); //move one half step 'forward'
|
|
delay(500);
|
|
LOG("sample encoder");
|
|
angle2=sampleMeanEncoder(200);
|
|
|
|
LOG("Angles %d %d",angle1,angle2);
|
|
if ((abs(angle2-angle1))>(ANGLE_STEPS/2))
|
|
{
|
|
//we crossed the wrap around
|
|
if (angle1>angle2)
|
|
{
|
|
angle1=angle1+(int32_t)ANGLE_STEPS;
|
|
}else
|
|
{
|
|
angle2=angle2+(int32_t)ANGLE_STEPS;
|
|
}
|
|
}
|
|
LOG("Angles %d %d",angle1,angle2);
|
|
|
|
//when we are increase the steps in the stepperDriver.move() command
|
|
// we want the encoder increasing. This ensures motor is moving clock wise
|
|
// when encoder is increasing.
|
|
// if (angle2>angle1)
|
|
// {
|
|
// motorParams.motorWiring=true;
|
|
// stepperDriver.setRotationDirection(true);
|
|
// LOG("Forward rotating");
|
|
// }else
|
|
// {
|
|
// //the motor is wired backwards so correct in stepperDriver
|
|
// motorParams.motorWiring=false;
|
|
// stepperDriver.setRotationDirection(false);
|
|
// LOG("Reverse rotating");
|
|
// }
|
|
x=((int64_t)(angle2-angle1)*36000)/(int32_t)ANGLE_STEPS;
|
|
// if x is ~180 we have a 1.8 degree step motor, if it is ~90 we have 0.9 degree step
|
|
LOG("%angle delta %d %d (%d %d)",x,abs(angle2-angle1),angle1,angle2 );
|
|
|
|
//move back
|
|
stepperDriver.move(-A4954_NUM_MICROSTEPS/2,motorParams.currentMa); //move one half step 'forward'
|
|
delay(100);
|
|
stepperDriver.move(-A4954_NUM_MICROSTEPS,motorParams.currentMa); //move one half step 'forward'
|
|
|
|
systemParams.microsteps=microsteps;
|
|
enableFeedback=feedback;
|
|
|
|
return ((float)x)/100.0;
|
|
}
|
|
|
|
|
|
int32_t StepperCtrl::measureError(void)
|
|
{
|
|
//LOG("current %d desired %d %d",(int32_t) currentLocation, (int32_t)getDesiredLocation(), numSteps);
|
|
|
|
return ((int32_t)currentLocation-(int32_t)getDesiredLocation());
|
|
}
|
|
|
|
/*
|
|
bool StepperCtrl::changeMicrostep(uint16_t microSteps)
|
|
{
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
systemParams.microsteps=microSteps;
|
|
motorReset();
|
|
if (state) enableTCInterrupts();
|
|
return true;
|
|
}
|
|
*/
|
|
Angle StepperCtrl::maxCalibrationError(void)
|
|
{
|
|
//Angle startingAngle;
|
|
bool done=false;
|
|
int32_t mean;
|
|
int32_t maxError=0, j;
|
|
int16_t dist;
|
|
uint16_t angle=0;
|
|
bool feedback=enableFeedback;
|
|
uint16_t microSteps=systemParams.microsteps;
|
|
int32_t steps;
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
|
|
|
|
if (false == calTable.calValid())
|
|
{
|
|
return ANGLE_MAX;
|
|
}
|
|
|
|
enableFeedback=false;
|
|
j=0;
|
|
LOG("Running calibration test");
|
|
|
|
systemParams.microsteps=1;
|
|
motorReset();
|
|
steps=0;
|
|
|
|
while(!done)
|
|
{
|
|
Angle cal;
|
|
Angle act, desiredAngle;
|
|
|
|
//todo we should measure mean and wait until stable.
|
|
delay(200);
|
|
mean=sampleMeanEncoder(200);
|
|
desiredAngle=(uint16_t)(getDesiredLocation() & 0x0FFFFLL);
|
|
|
|
cal=calTable.getCal(desiredAngle);
|
|
dist=Angle(mean)-cal;
|
|
act=calTable.fastReverseLookup(cal);
|
|
|
|
LOG("actual %d, cal %d",mean,(uint16_t)cal);
|
|
LOG("desired %d",(uint16_t)desiredAngle);
|
|
LOG("numSteps %d", numSteps);
|
|
|
|
LOG("cal error for step %d is %d",j,dist);
|
|
LOG("mean %d, cal %d",mean, (uint16_t)cal);
|
|
|
|
updateStep(0,1);
|
|
|
|
// move one half step at a time, a full step move could cause a move backwards depending on how current ramps down
|
|
steps+=A4954_NUM_MICROSTEPS/2;
|
|
stepperDriver.move(steps,motorParams.currentMa);
|
|
|
|
delay(50);
|
|
steps+=A4954_NUM_MICROSTEPS/2;
|
|
stepperDriver.move(steps,motorParams.currentMa);
|
|
|
|
|
|
if (400==motorParams.fullStepsPerRotation)
|
|
{
|
|
delay(100);
|
|
updateStep(0,1);
|
|
// move one half step at a time, a full step move could cause a move backwards depending on how current ramps down
|
|
steps+=A4954_NUM_MICROSTEPS/2;
|
|
stepperDriver.move(steps,motorParams.currentMa);
|
|
|
|
delay(100);
|
|
steps+=A4954_NUM_MICROSTEPS/2;
|
|
stepperDriver.move(steps,motorParams.currentMa);
|
|
}
|
|
//delay(400);
|
|
|
|
if (abs(dist)>maxError)
|
|
{
|
|
maxError=abs(dist);
|
|
}
|
|
|
|
j++;
|
|
if (j>=(1*CALIBRATION_TABLE_SIZE+3))
|
|
{
|
|
done=true;
|
|
}
|
|
|
|
|
|
}
|
|
systemParams.microsteps=microSteps;
|
|
motorReset();
|
|
enableFeedback=feedback;
|
|
if (state) enableTCInterrupts();
|
|
LOG("max error is %d cnts", maxError);
|
|
return Angle((uint16_t)maxError);
|
|
}
|
|
|
|
|
|
//The encoder needs to be calibrated to the motor.
|
|
// we will assume full step detents are correct,
|
|
// ex 1.8 degree motor will have 200 steps for 360 degrees.
|
|
// We also need to calibrate the phasing of the motor
|
|
// to the A4954. This requires that the A4954 "step angle" of
|
|
// zero is the first entry in the calibration table.
|
|
bool StepperCtrl::calibrateEncoder(void)
|
|
{
|
|
int32_t x,i,j;
|
|
uint32_t angle=0;
|
|
int32_t steps;
|
|
bool done=false;
|
|
|
|
int32_t mean;
|
|
uint16_t microSteps=systemParams.microsteps;
|
|
bool feedback=enableFeedback;
|
|
bool state=TC5_ISR_Enabled;
|
|
|
|
disableTCInterrupts();
|
|
|
|
enableFeedback=false;
|
|
systemParams.microsteps=1;
|
|
LOG("reset motor");
|
|
motorReset();
|
|
LOG("Starting calibration");
|
|
delay(200);
|
|
steps=0;
|
|
j=0;
|
|
while(!done)
|
|
{
|
|
int ii,N;
|
|
Angle cal,desiredAngle;
|
|
desiredAngle=(uint16_t)(getDesiredLocation() & 0x0FFFFLL);
|
|
cal=calTable.getCal(desiredAngle);
|
|
delay(200);
|
|
mean=sampleMeanEncoder(200);
|
|
|
|
LOG("Previous cal distance %d, %d, mean %d, cal %d",j, cal-Angle((uint16_t)mean), mean, (uint16_t)cal);
|
|
|
|
calTable.updateTableValue(j,mean);
|
|
|
|
updateStep(0,1);
|
|
|
|
N=2;
|
|
// move one half step at a time, a full step move could cause a move backwards depending on how current ramps down
|
|
for (ii=0; ii<N; ii++)
|
|
{
|
|
steps+=A4954_NUM_MICROSTEPS/N;
|
|
stepperDriver.move(steps,motorParams.currentMa);
|
|
|
|
delay(50);
|
|
}
|
|
//steps+=A4954_NUM_MICROSTEPS/2;
|
|
//stepperDriver.move(steps,motorParams.currentMa);
|
|
|
|
|
|
|
|
if (400==motorParams.fullStepsPerRotation)
|
|
{
|
|
delay(100);
|
|
updateStep(0,1);
|
|
// move one half step at a time, a full step move could cause a move backwards depending on how current ramps down
|
|
steps+=A4954_NUM_MICROSTEPS/2;
|
|
stepperDriver.move(steps,motorParams.currentMa);
|
|
|
|
delay(100);
|
|
steps+=A4954_NUM_MICROSTEPS/2;
|
|
stepperDriver.move(steps,motorParams.currentMa);
|
|
|
|
}
|
|
|
|
j++;
|
|
if (j>=CALIBRATION_TABLE_SIZE)
|
|
{
|
|
done=true;
|
|
}
|
|
|
|
|
|
}
|
|
//calTable.printCalTable();
|
|
//calTable.smoothTable();
|
|
//calTable.printCalTable();
|
|
calTable.saveToFlash(); //saves the calibration to flash
|
|
calTable.printCalTable();
|
|
|
|
systemParams.microsteps=microSteps;
|
|
motorReset();
|
|
enableFeedback=feedback;
|
|
if (state) enableTCInterrupts();
|
|
return done;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
stepCtrlError_t StepperCtrl::begin(void)
|
|
{
|
|
int i;
|
|
float x;
|
|
|
|
|
|
enableFeedback=false;
|
|
velocity=0;
|
|
currentLocation=0;
|
|
numSteps=0;
|
|
|
|
//we have to update from NVM before moving motor
|
|
updateParamsFromNVM(); //update the local cache from the NVM
|
|
|
|
LOG("start up encoder");
|
|
if (false == encoder.begin(PIN_AS5047D_CS))
|
|
{
|
|
return STEPCTRL_NO_ENCODER;
|
|
}
|
|
|
|
LOG("cal table init");
|
|
calTable.init();
|
|
|
|
startUpEncoder=(uint16_t)getEncoderAngle();
|
|
WARNING("start up encoder %d",startUpEncoder);
|
|
|
|
LOG("start stepper driver");
|
|
stepperDriver.begin();
|
|
|
|
#ifdef NEMA17_SMART_STEPPER_3_21_2017
|
|
if (NVM->motorParams.parametersVaild)
|
|
{
|
|
//lets read the motor voltage
|
|
if (GetMotorVoltage()<5)
|
|
{
|
|
//if we have less than 5 volts the motor is not powered
|
|
uint32_t x;
|
|
x=(uint32_t)(GetMotorVoltage()*1000.0);
|
|
ERROR("Motor voltage is %" PRId32 "mV",x); //default printf does not support floating point numbers
|
|
ERROR("Motor may not have power");
|
|
return STEPCTRL_NO_POWER;
|
|
}
|
|
bool state=enterCriticalSection();
|
|
setLocationFromEncoder(); //measure new starting point
|
|
exitCriticalSection(state);
|
|
|
|
}else
|
|
{
|
|
LOG("measuring step size");
|
|
x=measureStepSize();
|
|
if (abs(x)<0.5)
|
|
{
|
|
ERROR("Motor may not have power");
|
|
return STEPCTRL_NO_POWER;
|
|
}
|
|
}
|
|
|
|
#else
|
|
LOG("measuring step size");
|
|
x=measureStepSize();
|
|
if (abs(x)<0.5)
|
|
{
|
|
ERROR("Motor may not have power");
|
|
return STEPCTRL_NO_POWER;
|
|
}
|
|
#endif
|
|
|
|
|
|
LOG("Checking the motor parameters");
|
|
//todo we might want to move this up a level to the NZS
|
|
// especially since it has default values
|
|
if (false == NVM->motorParams.parametersVaild)
|
|
{
|
|
MotorParams_t params;
|
|
WARNING("NVM motor parameters are not set, we will update");
|
|
|
|
//power could have just been applied and step size read wrong
|
|
// if we are more than 200 steps/rotation which is most common
|
|
// lets read again just to be sure.
|
|
if (abs(x)<1.5)
|
|
{
|
|
//run step test a second time to be sure
|
|
x=measureStepSize();
|
|
}
|
|
|
|
if (x>0)
|
|
{
|
|
motorParams.motorWiring=true;
|
|
} else
|
|
{
|
|
motorParams.motorWiring=false;
|
|
}
|
|
if (abs(x)<=1.2)
|
|
{
|
|
motorParams.fullStepsPerRotation=400;
|
|
}else
|
|
{
|
|
motorParams.fullStepsPerRotation=200;
|
|
}
|
|
|
|
memcpy((void *)¶ms, (void *)&motorParams,sizeof(motorParams));
|
|
nvmWriteMotorParms(params);
|
|
}
|
|
|
|
LOG("Motor params are now good");
|
|
LOG("fullSteps %d", motorParams.fullStepsPerRotation);
|
|
LOG("motorWiring %d", motorParams.motorWiring);
|
|
LOG("currentMa %d", motorParams.currentMa);
|
|
LOG("holdCurrentMa %d", motorParams.currentHoldMa);
|
|
LOG("homeMa %d", motorParams.homeMa);
|
|
LOG("homeHoldMa %d", motorParams.homeHoldMa);
|
|
|
|
|
|
updateParamsFromNVM(); //update the local cache from the NVM
|
|
|
|
|
|
if (false == calTable.calValid())
|
|
{
|
|
return STEPCTRL_NO_CAL;
|
|
}
|
|
|
|
|
|
enableFeedback=true;
|
|
setupTCInterrupts();
|
|
enableTCInterrupts();
|
|
return STEPCTRL_NO_ERROR;
|
|
|
|
}
|
|
|
|
Angle StepperCtrl::sampleAngle(void)
|
|
{
|
|
uint16_t angle;
|
|
int32_t x,y;
|
|
|
|
#ifdef NZS_AS5047_PIPELINE
|
|
//read encoder twice such that we get the latest sample as the pipeline is always once sample behind
|
|
|
|
|
|
y=encoder.readEncoderAnglePipeLineRead(); //convert the 14 bit encoder value to a 16 bit number
|
|
x=encoder.readEncoderAnglePipeLineRead();
|
|
|
|
|
|
angle=((uint32_t)(x)*4); //convert the 14 bit encoder value to a 16 bit number
|
|
#else
|
|
angle=((uint32_t)encoder.readEncoderAngle())<<2; //convert the 14 bit encoder value to a 16 bit number
|
|
#endif
|
|
return Angle(angle);
|
|
}
|
|
|
|
//when sampling the mean of encoder if we are on roll over
|
|
// edge we can have an issue so we have this function
|
|
// to do the mean correctly
|
|
Angle StepperCtrl::sampleMeanEncoder(int32_t numSamples)
|
|
{
|
|
|
|
int32_t i,last,x=0;
|
|
int64_t mean=0;
|
|
int32_t min,max;
|
|
|
|
mean=0;
|
|
for (i=0; i<(numSamples); i++)
|
|
{
|
|
int32_t d;
|
|
last=x;
|
|
x=(((int32_t)encoder.readEncoderAngle())*4);
|
|
if (encoder.getError())
|
|
{
|
|
#ifndef MECHADUINO_HARDWARE
|
|
SerialUSB.println("AS5047 Error");
|
|
#else
|
|
Serial5.print("AS5047 Error");
|
|
#endif
|
|
delay(1000);
|
|
return 0;
|
|
}
|
|
if(i==0)
|
|
{
|
|
last=x;
|
|
min=x;
|
|
max=x;
|
|
}
|
|
//LOG("i %d,min %d, max %d, last %d, x %d", i, min, max, last, x);
|
|
if (abs(last-x)>65536/2)
|
|
{
|
|
if (last>x)
|
|
{
|
|
x=x+65536;
|
|
} else
|
|
{
|
|
x=x-65536;
|
|
}
|
|
}
|
|
|
|
|
|
if (x>max)
|
|
{
|
|
max=x;
|
|
}
|
|
if (x<min)
|
|
{
|
|
min=x;
|
|
}
|
|
|
|
mean=mean+(x);
|
|
}
|
|
|
|
LOG("min %d, max %d, mean %d", min, max, (int32_t)(mean/numSamples));
|
|
return Angle(mean/numSamples);
|
|
}
|
|
|
|
void StepperCtrl::feedback(bool enable)
|
|
{
|
|
disableTCInterrupts();
|
|
motorReset();
|
|
enableFeedback=enable;
|
|
if (enable == true)
|
|
{
|
|
enableTCInterrupts();
|
|
}
|
|
}
|
|
|
|
|
|
void StepperCtrl::updateSteps(int64_t steps)
|
|
{
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
numSteps+=steps;
|
|
if (state) enableTCInterrupts();
|
|
}
|
|
|
|
void StepperCtrl::updateStep(int dir, uint16_t steps)
|
|
{
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
if (dir)
|
|
{
|
|
numSteps-=steps;
|
|
}else
|
|
{
|
|
numSteps+=steps;
|
|
}
|
|
if (state) enableTCInterrupts();
|
|
}
|
|
|
|
void StepperCtrl::requestStep(int dir, uint16_t steps)
|
|
{
|
|
bool state;
|
|
state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
|
|
if (dir)
|
|
{
|
|
numSteps-=steps;
|
|
}else
|
|
{
|
|
numSteps+=steps;
|
|
}
|
|
|
|
if (false == enableFeedback)
|
|
{
|
|
moveToAngle(getDesiredLocation(),motorParams.currentMa);
|
|
}
|
|
if (state) enableTCInterrupts();
|
|
}
|
|
|
|
|
|
void StepperCtrl::move(int dir, uint16_t steps)
|
|
{
|
|
int64_t ret;
|
|
int32_t n;
|
|
|
|
|
|
|
|
updateStep(dir,steps);
|
|
|
|
if (false == enableFeedback)
|
|
{
|
|
n=systemParams.microsteps;
|
|
ret=((int64_t)numSteps * A4954_NUM_MICROSTEPS+(n/2))/n;
|
|
n=A4954_NUM_MICROSTEPS*motorParams.fullStepsPerRotation;
|
|
while(ret>n)
|
|
{
|
|
ret-=n;
|
|
}
|
|
while(ret<-n)
|
|
{
|
|
ret+=n;
|
|
}
|
|
n=(int32_t)(ret);
|
|
LOG("s is %d %d",n,steps);
|
|
stepperDriver.move(n,motorParams.currentMa);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t StepperCtrl::getDesiredLocation(void)
|
|
{
|
|
int64_t ret;
|
|
int32_t n;
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
n=motorParams.fullStepsPerRotation * systemParams.microsteps;
|
|
ret=((int64_t)numSteps * (int64_t)ANGLE_STEPS+(n/2))/n ;
|
|
if (state) enableTCInterrupts();
|
|
return ret;
|
|
}
|
|
|
|
|
|
//int32_t StepperCtrl::getSteps(void)
|
|
//{
|
|
// int32_t ret;
|
|
// bool state=enterCriticalSection();
|
|
// ret=numSteps;
|
|
// exitCriticalSection(state);
|
|
// return ret;
|
|
//}
|
|
|
|
void StepperCtrl::moveToAbsAngle(int32_t a)
|
|
{
|
|
|
|
int64_t ret;
|
|
int32_t n;
|
|
|
|
|
|
n=motorParams.fullStepsPerRotation * systemParams.microsteps;
|
|
|
|
ret=(((int64_t)a+zeroAngleOffset)*n+ANGLE_STEPS/2)/(int32_t)ANGLE_STEPS;
|
|
bool state=enterCriticalSection();
|
|
numSteps=ret;
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
void StepperCtrl::moveToAngle(int32_t a, uint32_t ma)
|
|
{
|
|
//we need to convert 'Angle' to A4954 steps
|
|
a=a % ANGLE_STEPS; //we only interested in the current angle
|
|
|
|
|
|
a=DIVIDE_WITH_ROUND( (a*motorParams.fullStepsPerRotation*A4954_NUM_MICROSTEPS), ANGLE_STEPS);
|
|
|
|
//LOG("move %d %d",a,ma);
|
|
stepperDriver.move(a,ma);
|
|
|
|
}
|
|
|
|
Angle StepperCtrl::getEncoderAngle(void)
|
|
{
|
|
Angle a;
|
|
bool state=enterCriticalSection();
|
|
a=calTable.fastReverseLookup(sampleAngle());
|
|
exitCriticalSection(state);
|
|
return a;
|
|
}
|
|
|
|
int64_t StepperCtrl::getCurrentLocation(void)
|
|
{
|
|
Angle a;
|
|
int32_t x;
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
a=calTable.fastReverseLookup(sampleAngle());
|
|
x=(int32_t)a - (int32_t)((currentLocation) & ANGLE_MAX);
|
|
|
|
if (x>((int32_t)ANGLE_STEPS/2))
|
|
{
|
|
currentLocation -= ANGLE_STEPS;
|
|
}
|
|
if (x<-((int32_t)ANGLE_STEPS/2))
|
|
{
|
|
currentLocation += ANGLE_STEPS;
|
|
}
|
|
currentLocation=(currentLocation & 0xFFFFFFFFFFFF0000UL) | (uint16_t)a;
|
|
if (state) enableTCInterrupts();
|
|
return currentLocation;
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t StepperCtrl::getCurrentAngle(void)
|
|
{
|
|
int64_t x;
|
|
x=getCurrentLocation()-zeroAngleOffset;
|
|
return x;
|
|
}
|
|
|
|
|
|
int64_t StepperCtrl::getDesiredAngle(void)
|
|
{
|
|
int64_t x;
|
|
x=getDesiredLocation()-zeroAngleOffset;
|
|
return x;
|
|
}
|
|
|
|
void StepperCtrl::setVelocity(int64_t vel)
|
|
{
|
|
bool state=enterCriticalSection();
|
|
|
|
velocity=vel;
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
void StepperCtrl::setTorque(int8_t tor)
|
|
{
|
|
bool state=enterCriticalSection();
|
|
|
|
torque=tor;
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
int8_t StepperCtrl::getTorque(void)
|
|
{
|
|
int8_t tor;
|
|
bool state=enterCriticalSection();
|
|
|
|
tor=torque;
|
|
exitCriticalSection(state);
|
|
return tor;
|
|
}
|
|
|
|
int64_t StepperCtrl::getVelocity(void)
|
|
{
|
|
int64_t vel;
|
|
bool state=enterCriticalSection();
|
|
|
|
vel=velocity;
|
|
exitCriticalSection(state);
|
|
return vel;
|
|
}
|
|
|
|
bool StepperCtrl::torqueLoop(int64_t currentLoc, Control_t *ptrCtrl)
|
|
{
|
|
int32_t U, ma;
|
|
int64_t u, y;
|
|
|
|
int32_t fullStep=ANGLE_STEPS/motorParams.fullStepsPerRotation;
|
|
|
|
y=currentLoc;
|
|
ma=motorParams.currentMa;
|
|
u=torque*ma/128; // Make ma the maximum torque at 128
|
|
// If dir pin changes meaning then torque also changes direction
|
|
if (CW_ROTATION == systemParams.dirPinRotation)
|
|
{
|
|
u=-u;
|
|
}
|
|
U=abs(u);
|
|
if (U>ma)
|
|
U=ma;
|
|
if (u>0)
|
|
y=y+fullStep;
|
|
else
|
|
y=y-fullStep;
|
|
ptrCtrl->ma=U;
|
|
ptrCtrl->angle=(int32_t)y;
|
|
moveToAngle(y,U);
|
|
}
|
|
|
|
void StepperCtrl::PrintData(void)
|
|
{
|
|
char s[128];
|
|
bool state=enterCriticalSection();
|
|
sprintf(s, "%u,%u,%u", (uint32_t)numSteps,(uint32_t)getDesiredAngle(),(uint32_t)getCurrentAngle());
|
|
#ifndef MECHADUINO_HARDWARE
|
|
SerialUSB.println(s);
|
|
#endif
|
|
exitCriticalSection(state);
|
|
}
|
|
//this is the velocity PID feedback loop
|
|
bool StepperCtrl::vpidFeedback(int64_t desiredLoc, int64_t currentLoc, Control_t *ptrCtrl)
|
|
{
|
|
int32_t fullStep=ANGLE_STEPS/motorParams.fullStepsPerRotation;
|
|
static int64_t lastY=getCurrentLocation();
|
|
static int32_t lastError=0;
|
|
static int64_t Iterm=0;
|
|
int64_t y,z;
|
|
int64_t v,dy;
|
|
int64_t u;
|
|
|
|
//get the current location
|
|
y =currentLoc;
|
|
|
|
v=y-lastY;
|
|
|
|
//add in phase prediction
|
|
y=y+calculatePhasePrediction(currentLoc);
|
|
z=y;
|
|
|
|
lastY=y;
|
|
|
|
v=v*NZS_CONTROL_LOOP_HZ;
|
|
|
|
|
|
if (enableFeedback) //if ((micros()-lastCall)>(updateRate/10))
|
|
{
|
|
int64_t error,U;
|
|
error = velocity-v;
|
|
|
|
Iterm += (vPID.Ki * error);
|
|
if (Iterm>(16*4096*CTRL_PID_SCALING *(int64_t)motorParams.currentMa))
|
|
{
|
|
Iterm=(16*4096*CTRL_PID_SCALING *(int64_t)motorParams.currentMa);
|
|
}
|
|
if (Iterm<-(16*4096*CTRL_PID_SCALING *(int64_t)motorParams.currentMa))
|
|
{
|
|
Iterm=-(16*4096*CTRL_PID_SCALING*(int64_t)motorParams.currentMa);
|
|
}
|
|
|
|
u=((vPID.Kp * error) + Iterm - (vPID.Kd *(lastError-error)));
|
|
U=abs(u)/CTRL_PID_SCALING/1024; //scale the error to make PID params close to 1.0;//scale the error to make PID params close to 1.0 by dividing by 1024
|
|
|
|
if (U>motorParams.currentMa)
|
|
{
|
|
U=motorParams.currentMa;
|
|
}
|
|
|
|
//when error is positive we need to move reverse direction
|
|
if (u>0)
|
|
{
|
|
z=z+(fullStep);
|
|
}else
|
|
{
|
|
z=z-(fullStep);
|
|
|
|
}
|
|
|
|
ptrCtrl->ma=U;
|
|
ptrCtrl->angle=(int32_t)z;
|
|
moveToAngle(z,U);
|
|
loopError=error;
|
|
lastError=error;
|
|
} else
|
|
{
|
|
lastError=0;
|
|
Iterm=0;
|
|
}
|
|
|
|
if (abs(lastError)>(systemParams.errorLimit))
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
//Since we are doing fixed point math our
|
|
// threshold needs to be large.
|
|
// We need a large threshold when we have fast update
|
|
// rate as well. But for most part it is random
|
|
bool StepperCtrl::pidFeedback(int64_t desiredLoc, int64_t currentLoc, Control_t *ptrCtrl)
|
|
{
|
|
static int count=0;
|
|
|
|
static int32_t maxError=0;
|
|
static int32_t lastError=0;
|
|
static int32_t Iterm=0;
|
|
int32_t ma;
|
|
int64_t y;
|
|
|
|
int32_t fullStep=ANGLE_STEPS/motorParams.fullStepsPerRotation;
|
|
int32_t dy;
|
|
|
|
y=currentLoc;
|
|
|
|
//add in phase prediction
|
|
y=y+calculatePhasePrediction(currentLoc);
|
|
|
|
if (enableFeedback) //if ((micros()-lastCall)>(updateRate/10))
|
|
{
|
|
int64_t error,u;
|
|
int32_t U,x;
|
|
|
|
//error is in units of degrees when 360 degrees == 65536
|
|
error=(desiredLoc-y); //error is currentPos-desiredPos
|
|
|
|
Iterm+=(pPID.Ki * error);
|
|
|
|
if (systemParams.homePin>0 && digitalRead(systemParams.homePin)==0)
|
|
{
|
|
ma=motorParams.homeMa;
|
|
} else
|
|
{
|
|
ma=motorParams.currentMa;
|
|
}
|
|
|
|
//Over the long term we do not want error
|
|
// to be much more than our threshold
|
|
if (Iterm> (ma*CTRL_PID_SCALING) )
|
|
{
|
|
Iterm=(ma*CTRL_PID_SCALING) ;
|
|
}
|
|
if (Iterm<-(ma*CTRL_PID_SCALING) )
|
|
{
|
|
Iterm=-(ma*CTRL_PID_SCALING) ;
|
|
}
|
|
|
|
//u=((pPID.Kp * error) + Iterm - (pPID.Kd *(lastError-error)));
|
|
if(abs(error) > 90304) // 540 degrees
|
|
{
|
|
u=(int64_t)((float)ma*(float)CTRL_PID_SCALING*90304.0/(float)error);
|
|
}
|
|
else
|
|
{
|
|
u=((pPID.Kp * error) + Iterm - (pPID.Kd *(lastError-error)));
|
|
}
|
|
|
|
U=abs(u)/CTRL_PID_SCALING;
|
|
if (U>ma)
|
|
{
|
|
U=ma;
|
|
}
|
|
|
|
|
|
//when error is positive we need to move reverse direction
|
|
if (u>0)
|
|
{
|
|
y=y+fullStep;
|
|
}else
|
|
{
|
|
y=y-fullStep;
|
|
|
|
}
|
|
|
|
ptrCtrl->ma=U;
|
|
ptrCtrl->angle=(int32_t)y;
|
|
moveToAngle(y,U);
|
|
loopError=error;
|
|
lastError=error;
|
|
|
|
}else
|
|
{
|
|
lastError=0;
|
|
Iterm=0;
|
|
}
|
|
|
|
if (abs(lastError)>(systemParams.errorLimit))
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
// the phase prediction tries to predict error from sensor based
|
|
// on current location and previous location.
|
|
// TODO our error can help in the phase prediction.
|
|
// if the error
|
|
int64_t StepperCtrl::calculatePhasePrediction(int64_t currentLoc)
|
|
{
|
|
static int64_t lastLoc=0;
|
|
static int32_t mean=0;
|
|
int32_t dx,x;
|
|
|
|
#ifndef ENABLE_PHASE_PREDICTION
|
|
return 0;
|
|
#endif
|
|
|
|
//what was our change in the location
|
|
dx=currentLoc-lastLoc; //max value is typically less than 327(1.8 degrees) or 163(0.9 degree)
|
|
|
|
//if the motor direction changes, zero phase prediction
|
|
if (SIGN(dx) != SIGN(mean))
|
|
{
|
|
//last thing we want is phase prediction during direction change.
|
|
mean=0;
|
|
} else
|
|
{
|
|
if (abs(dx)>abs(mean))
|
|
{
|
|
//increase mean really slowly, 2048 ~ 1/3 second with 6khz processing loop
|
|
// in fixed point since the dx is so small we need to scale it up to average
|
|
// dx has be be greater than 8 to change the mean...
|
|
// this limits the acceleration of motor above max processing speed (6k*1.8)=1800RPM
|
|
// however I doubt the motor can accelerate that fast with any load...
|
|
// The average helps prevent external impulse error from causing prediction to cause issues.
|
|
mean=DIVIDE_WITH_ROUND(2047*mean + dx*128, 2048);
|
|
}else
|
|
{
|
|
//decrease fast
|
|
//do not add more phase prediction than the difference in last two samples.
|
|
mean=dx*128;
|
|
}
|
|
}
|
|
lastLoc=currentLoc;
|
|
|
|
x= mean/128; //scale back to normal
|
|
return x;
|
|
}
|
|
|
|
|
|
bool StepperCtrl::determineError(int64_t currentLoc,int64_t error)
|
|
{
|
|
static int64_t lastLocation=0;
|
|
static int64_t lastError=0;
|
|
static int64_t lastVelocity=0;
|
|
|
|
int64_t velocity;
|
|
|
|
//since this is called on periodic timer the velocity
|
|
// is propotional to the change in location
|
|
// one rotation per second is velocity of 10, assumming 6khz update rate
|
|
// one rotation per minute is 10/60 velocity units
|
|
// since this is less than 1 we will scale the velo
|
|
velocity=(currentLoc-lastLocation);
|
|
|
|
if (velocity>0 && lastVelocity>0)
|
|
{
|
|
|
|
}
|
|
|
|
|
|
lastVelocity=velocity;
|
|
lastError=error;
|
|
lastLocation=currentLoc;
|
|
}
|
|
|
|
//this was written to do the PID loop not modeling a DC servo
|
|
// but rather using features of stepper motor.
|
|
bool StepperCtrl::simpleFeedback(int64_t desiredLoc, int64_t currentLoc, Control_t *ptrCtrl)
|
|
{
|
|
static uint32_t t0=0;
|
|
static uint32_t calls=0;
|
|
bool ret=false;
|
|
|
|
static int32_t maxError=0;
|
|
static int32_t lastError=0;
|
|
static int32_t i=0;
|
|
static int32_t iTerm=0;
|
|
//static int64_t lastY=getCurrentLocation();
|
|
static int32_t velocity=0;
|
|
static int32_t errorCount=0;
|
|
|
|
static bool lastProbeState=false;
|
|
static int64_t probeStartAngle=0;
|
|
static int32_t maxMa=0;
|
|
|
|
|
|
static int64_t filteredError=0;
|
|
static int32_t probeCount=0;
|
|
|
|
int32_t fullStep=ANGLE_STEPS/motorParams.fullStepsPerRotation;
|
|
|
|
int32_t ma=0;
|
|
|
|
int64_t y;
|
|
|
|
|
|
|
|
//estimate our current location based on the encoder
|
|
y=currentLoc;
|
|
|
|
//add in phase prediction
|
|
y=y+calculatePhasePrediction(currentLoc);
|
|
|
|
|
|
//we can limit the velocity by controlling the amount we move per call to this function
|
|
// this only works for velocity greater than 100rpm
|
|
/* if (velocity!=0)
|
|
{
|
|
fullStep=velocity/NZS_CONTROL_LOOP_HZ;
|
|
}
|
|
if (fullStep==0)
|
|
{
|
|
fullStep=1; //this RPM of (1*NZS_CONTROL_LOOP_HZ)/60 ie at 6Khz it is 100RPM
|
|
}
|
|
*/
|
|
if (enableFeedback)
|
|
{
|
|
int64_t error;
|
|
int32_t u;
|
|
|
|
int32_t x;
|
|
int32_t kp;
|
|
|
|
//error is in units of degrees when 360 degrees == 65536
|
|
error=(desiredLoc-y);//measureError(); //error is currentPos-desiredPos
|
|
|
|
|
|
//data[i]=(int16_t)error;
|
|
//i++;
|
|
if (i>=N_DATA)
|
|
{
|
|
i=0;
|
|
}
|
|
|
|
kp=sPID.Kp;
|
|
if (1)//(abs(error)<(fullStep))
|
|
{
|
|
iTerm+=(sPID.Ki*error);
|
|
x=iTerm/CTRL_PID_SCALING;
|
|
}else
|
|
{
|
|
kp=(CTRL_PID_SCALING*9)/10;
|
|
x=0;
|
|
iTerm=0;
|
|
}
|
|
|
|
if (x>fullStep)
|
|
{
|
|
x=fullStep;
|
|
iTerm=fullStep;
|
|
}
|
|
if (x<-fullStep)
|
|
{
|
|
x=-fullStep;
|
|
iTerm=-fullStep;
|
|
}
|
|
|
|
|
|
//u=(kp * error)/CTRL_PID_SCALING+x+(sPID.Kd *(error-lastError))/CTRL_PID_SCALING;
|
|
|
|
// Lower effort if we're very far off
|
|
if(abs(error) > 90304) // 540 degrees
|
|
{
|
|
u=(int64_t)((float)fullStep*90304.0/(float)error);
|
|
}
|
|
else
|
|
{
|
|
u=(kp * error)/CTRL_PID_SCALING+x+(sPID.Kd *(error-lastError))/CTRL_PID_SCALING;
|
|
}
|
|
|
|
//limit error to full step
|
|
if (u>fullStep)
|
|
{
|
|
u=fullStep;
|
|
}
|
|
if (u<-fullStep)
|
|
{
|
|
u=-fullStep;
|
|
}
|
|
|
|
ma=(abs(u)*(motorParams.currentMa-motorParams.currentHoldMa))/ fullStep + motorParams.currentHoldMa;
|
|
if (ma>motorParams.currentMa)
|
|
{
|
|
ma=motorParams.currentMa;
|
|
}
|
|
//maxMa=motorParams.currentMa;
|
|
|
|
if (systemParams.homePin>=0)
|
|
{
|
|
|
|
if (digitalRead(systemParams.homePin)==0)
|
|
{
|
|
if (lastProbeState==false)
|
|
{
|
|
//record our current angle for homing
|
|
probeStartAngle=desiredLoc;
|
|
probeCount=0;
|
|
maxMa=0;
|
|
}
|
|
lastProbeState=true;
|
|
probeCount++;
|
|
//we will lower current after whe have moved some amount
|
|
|
|
if (probeCount > NZS_CONTROL_LOOP_HZ && probeCount <(2* NZS_CONTROL_LOOP_HZ))
|
|
{
|
|
maxMa+=ma;
|
|
if (abs(error)>maxError)
|
|
{
|
|
maxError=abs(error);
|
|
}
|
|
|
|
}
|
|
if (probeCount>(2*NZS_CONTROL_LOOP_HZ))
|
|
{
|
|
// ma=(abs(u)*(maxMa))/ fullStep;// + motorParams.homeHoldMa;
|
|
// if (ma>motorParams.homeMa)
|
|
// {
|
|
// ma=motorParams.homeMa;
|
|
// }
|
|
|
|
//if (ma>maxMa/NZS_CONTROL_LOOP_HZ)
|
|
{
|
|
ma=((maxMa/NZS_CONTROL_LOOP_HZ)*9)/10;
|
|
}
|
|
|
|
}
|
|
|
|
} else
|
|
{
|
|
lastProbeState=false;
|
|
}
|
|
}else
|
|
{
|
|
maxError=0;
|
|
probeCount=0;
|
|
//maxMa=0;
|
|
}
|
|
|
|
|
|
y=y+u;
|
|
ptrCtrl->ma=ma;
|
|
ptrCtrl->angle=(int32_t)y;
|
|
moveToAngle(y,ma); //35us
|
|
|
|
lastError=error;
|
|
loopError=error;
|
|
//stepperDriver.limitCurrent(99);
|
|
}
|
|
|
|
//filteredError=(filteredError*15+lastError)/16;
|
|
|
|
if (probeCount>(2*NZS_CONTROL_LOOP_HZ))
|
|
{
|
|
if (abs(lastError) > maxError )
|
|
{
|
|
|
|
errorCount++;
|
|
if (errorCount>(10))
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (abs(lastError) > systemParams.errorLimit)
|
|
{
|
|
|
|
errorCount++;
|
|
if (errorCount>(NZS_CONTROL_LOOP_HZ/128)) // error needs to exist for some time period
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (errorCount>0)
|
|
{
|
|
errorCount--;
|
|
}
|
|
|
|
//errorCount=0;
|
|
stepperDriver.limitCurrent(99); //reduce noise on low error
|
|
return 0;
|
|
|
|
}
|
|
|
|
void StepperCtrl::currentLocationIsDesiredLocation(){
|
|
bool state=enterCriticalSection();
|
|
moveToAbsAngle(getCurrentAngle());
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
void StepperCtrl::enable(bool enable)
|
|
{
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
bool feedback=enableFeedback;
|
|
|
|
stepperDriver.enable(enable); //enable or disable the stepper driver as needed
|
|
|
|
|
|
if (enabled==true && enable==false)
|
|
{
|
|
feedback = false;
|
|
}
|
|
if (enabled==false && enable==true) //if we are enabling previous disabled motor
|
|
{
|
|
feedback = true;
|
|
setLocationFromEncoder();
|
|
}
|
|
|
|
enabled=enable;
|
|
enableFeedback=feedback;
|
|
if (state) enableTCInterrupts();
|
|
}
|
|
|
|
/*
|
|
void StepperCtrl::testRinging(void)
|
|
{
|
|
uint16_t c;
|
|
int32_t steps;
|
|
int32_t microSteps=systemParams.microsteps;
|
|
bool feedback=enableFeedback;
|
|
|
|
enableFeedback=false;
|
|
systemParams.microsteps=1;
|
|
motorReset();
|
|
for (c=2000; c>10; c=c-10)
|
|
{
|
|
SerialUSB.print("Current ");
|
|
SerialUSB.println(c);
|
|
steps+=A4954_NUM_MICROSTEPS;
|
|
stepperDriver.move(steps,NVM->SystemParams.currentMa);
|
|
currentLimit=false;
|
|
measure();
|
|
}
|
|
systemParams.microsteps=microSteps;
|
|
motorReset();
|
|
enableFeedback=feedback;
|
|
}
|
|
*/
|
|
|
|
//returns -1 if no data, else returns number of data points remaining.
|
|
int32_t StepperCtrl::getLocation(Location_t *ptrLoc)
|
|
{
|
|
bool state=enterCriticalSection();
|
|
int32_t n;
|
|
//check for empty
|
|
if (locReadIndx==locWriteIndx)
|
|
{
|
|
//empty data
|
|
exitCriticalSection(state);
|
|
return -1;
|
|
}
|
|
|
|
//else read data
|
|
memcpy(ptrLoc,(void *)&locs[locReadIndx], sizeof(Location_t));
|
|
|
|
//update the read index
|
|
locReadIndx=(locReadIndx+1)%MAX_NUM_LOCATIONS;
|
|
|
|
//calculate number of locations left
|
|
n=((locWriteIndx+MAX_NUM_LOCATIONS)-locReadIndx)%MAX_NUM_LOCATIONS;
|
|
|
|
|
|
exitCriticalSection(state);
|
|
return n;
|
|
}
|
|
|
|
void StepperCtrl::updateLocTable(int64_t desiredLoc, int64_t currentLoc, Control_t *ptrCtrl)
|
|
{
|
|
bool state=enterCriticalSection();
|
|
int32_t next;
|
|
|
|
// set the next write location
|
|
next=(locWriteIndx+1)%MAX_NUM_LOCATIONS;
|
|
|
|
if (next==locReadIndx)
|
|
{
|
|
//we are full, exit
|
|
exitCriticalSection(state);
|
|
//RED_LED(true); //turn Red LED on to indciate buffer full
|
|
return;
|
|
}
|
|
|
|
//use ticks for the moment so we can tell if we miss data on the print.
|
|
locs[locWriteIndx].microSecs=(int32_t)micros();
|
|
locs[locWriteIndx].desiredLoc=(int32_t)(desiredLoc-zeroAngleOffset);
|
|
locs[locWriteIndx].actualLoc=(int32_t)(currentLoc-zeroAngleOffset);
|
|
locs[locWriteIndx].angle=(ptrCtrl->angle-zeroAngleOffset);
|
|
locs[locWriteIndx].ma=ptrCtrl->ma;
|
|
locWriteIndx=next;
|
|
|
|
|
|
exitCriticalSection(state);
|
|
}
|
|
|
|
|
|
bool StepperCtrl::processFeedback(void)
|
|
{
|
|
bool ret;
|
|
int32_t us,j;
|
|
Control_t ctrl;
|
|
int64_t desiredLoc;
|
|
int64_t currentLoc;
|
|
int32_t steps;
|
|
static int64_t mean=0;;
|
|
|
|
us=micros();
|
|
|
|
#ifdef USE_TC_STEP
|
|
static int64_t lastSteps;
|
|
int64_t x;
|
|
x=getSteps()-lastSteps;
|
|
updateSteps(x);
|
|
lastSteps+=x;
|
|
#endif
|
|
|
|
// steps=getSteps();
|
|
// if (steps>0)
|
|
// {
|
|
// requestStep(1, (uint16_t)steps);
|
|
// }else
|
|
// {
|
|
// requestStep(0, (uint16_t)(-steps));
|
|
// }
|
|
|
|
desiredLoc=getDesiredLocation();
|
|
|
|
currentLoc=getCurrentLocation();
|
|
mean=(31*mean+currentLoc+16)/32;
|
|
|
|
#ifdef A5995_DRIVER //the A5995 is has more driver noise
|
|
if (abs(currentLoc-mean)<ANGLE_FROM_DEGREES(0.9))
|
|
#else
|
|
if (abs(currentLoc-mean)<ANGLE_FROM_DEGREES(0.3))
|
|
#endif
|
|
{
|
|
currentLoc=mean;
|
|
}
|
|
|
|
|
|
|
|
switch (systemParams.controllerMode)
|
|
{
|
|
#if defined(CTRL_POS_PID_AS_DEFAULT)
|
|
default:
|
|
#endif
|
|
case CTRL_POS_PID:
|
|
{
|
|
ret=pidFeedback(desiredLoc, currentLoc, &ctrl);
|
|
break;
|
|
}
|
|
#if !defined(CTRL_POS_PID_AS_DEFAULT)
|
|
default:
|
|
#endif
|
|
case CTRL_SIMPLE:
|
|
{
|
|
ret=simpleFeedback(desiredLoc, currentLoc,&ctrl);
|
|
break;
|
|
}
|
|
case CTRL_TORQUE:
|
|
{
|
|
ret=torqueLoop(currentLoc, &ctrl);
|
|
break;
|
|
}
|
|
case CTRL_POS_VELOCITY_PID:
|
|
{
|
|
ret=vpidFeedback(desiredLoc, currentLoc,&ctrl);
|
|
break;
|
|
}
|
|
//TODO if disable feedback and someone switches mode
|
|
// they will have to turn feedback back on.
|
|
case CTRL_OFF:
|
|
{
|
|
enableFeedback=false;
|
|
break;
|
|
}
|
|
case CTRL_OPEN:
|
|
{
|
|
enableFeedback=false;
|
|
break;
|
|
}
|
|
}
|
|
ticks++;
|
|
updateLocTable(desiredLoc, currentLoc,&ctrl);
|
|
loopTimeus=micros()-us;
|
|
return ret;
|
|
}
|
|
|
|
|
|
//auto tuning of PID parameters based on documentation here:
|
|
// http://brettbeauregard.com/blog/2012/01/arduino-pid-autotune-library
|
|
// http://www.controleng.com/search/search-single-display/relay-method-automates-pid-loop-tuning/4a5774decc.html
|
|
void StepperCtrl::PID_Autotune(void)
|
|
{
|
|
int32_t noiseMin, noiseMax, error;
|
|
int32_t eMin, eMax;
|
|
int64_t mean;
|
|
int32_t startAngle, thres;
|
|
int32_t i,j;
|
|
int32_t t0,t1;
|
|
int32_t times[100];
|
|
int32_t angle;
|
|
|
|
//save previous state;
|
|
uint16_t microSteps=systemParams.microsteps;
|
|
bool feedback=enableFeedback;
|
|
feedbackCtrl_t prevCtrl=systemParams.controllerMode;
|
|
|
|
//disable interrupts and feedback controller
|
|
bool state=TC5_ISR_Enabled;
|
|
disableTCInterrupts();
|
|
systemParams.controllerMode=CTRL_POS_PID;
|
|
enableFeedback=false;
|
|
motorReset();
|
|
//nvmWritePID(1,0,0,2);
|
|
//set the number of microsteps to 1
|
|
//systemParams.microsteps=1;
|
|
for (i=0; i<10; i++)
|
|
{
|
|
angle=getCurrentLocation();
|
|
}
|
|
// pKp=NVM->PIDparams.Kp;
|
|
// pKi=NVM->PIDparams.Ki;
|
|
// pKd=NVM->PIDparams.Kd;
|
|
// threshold=NVM->PIDparams.Threshold;
|
|
|
|
//enableTCInterrupts();
|
|
moveToAngle(angle,motorParams.currentMa);
|
|
|
|
|
|
//moveToAngle(angle,NVM->SystemParams.currentMa);
|
|
/*
|
|
//next lets measure our noise on the encoder
|
|
noiseMin=(int32_t)ANGLE_MAX;
|
|
noiseMax=-(int32_t)ANGLE_MAX;
|
|
mean=0;
|
|
j=1000000UL/NZS_CONTROL_LOOP_HZ;
|
|
prevAngle=sampleAngle();
|
|
for (i=0; i<(NZS_CONTROL_LOOP_HZ/2); i++)
|
|
{
|
|
Angle a;
|
|
a=sampleAngle();
|
|
error=(int32_t)(prevAngle-a);
|
|
|
|
if (error<noiseMin)
|
|
{
|
|
noiseMin=error;
|
|
}
|
|
|
|
if (error>noiseMax)
|
|
{
|
|
noiseMax=error;
|
|
}
|
|
mean=mean+(int32_t)a;
|
|
delayMicroseconds(j);
|
|
|
|
}
|
|
mean=mean/i;
|
|
while (mean>ANGLE_MAX)
|
|
{
|
|
mean=mean-ANGLE_STEPS;
|
|
}
|
|
while (mean<0)
|
|
{
|
|
mean=mean+ANGLE_STEPS;
|
|
}
|
|
//mean is the average of the encoder.
|
|
*/
|
|
|
|
|
|
|
|
stepperDriver.move(0,motorParams.currentMa);
|
|
delay(1000);
|
|
|
|
//now we need to do the relay control
|
|
for (i=0; i<10; i++)
|
|
{
|
|
startAngle=getCurrentLocation();
|
|
LOG("Start %d", (int32_t)startAngle);
|
|
}
|
|
thres=startAngle + (int32_t)((ANGLE_STEPS/motorParams.fullStepsPerRotation)*10/9);
|
|
LOG("Thres %d, start %d",(int32_t)thres,(int32_t)startAngle);
|
|
eMin=(int32_t)ANGLE_MAX;
|
|
eMax=-(int32_t)ANGLE_MAX;
|
|
int32_t reset=0;
|
|
int32_t force=(motorParams.currentMa);
|
|
|
|
for (i=0; i<100; i++)
|
|
{
|
|
int32_t error;
|
|
if (reset)
|
|
{
|
|
motorReset();
|
|
stepperDriver.move(0,motorParams.currentMa);
|
|
delay(1000);
|
|
startAngle=getCurrentLocation();
|
|
LOG("Start %d", (int32_t)startAngle);
|
|
force=force-100;
|
|
|
|
eMin=(int32_t)ANGLE_MAX;
|
|
eMax=-(int32_t)ANGLE_MAX;
|
|
|
|
if (force<100)
|
|
{
|
|
i=100;
|
|
break;
|
|
}
|
|
LOG("force set to %d",force);
|
|
i=0;
|
|
}
|
|
reset=0;
|
|
|
|
stepperDriver.move(A4954_NUM_MICROSTEPS,force);
|
|
//moveToAngle(startAngle+(ANGLE_STEPS/motorParams.fullStepsPerRotation),force);
|
|
//stepperDriver.move(A4954_NUM_MICROSTEPS,NVM->SystemParams.currentMa);
|
|
t0=micros();
|
|
|
|
error=0;
|
|
while(error<=((ANGLE_STEPS/motorParams.fullStepsPerRotation)/2+40))
|
|
{
|
|
int32_t y;
|
|
y=getCurrentLocation();
|
|
error=y-startAngle;
|
|
//LOG("Error1 %d",error);
|
|
if (error<eMin)
|
|
{
|
|
eMin=error;
|
|
}
|
|
if (error>eMax)
|
|
{
|
|
eMax=error;
|
|
}
|
|
if (abs(error)>ANGLE_STEPS/motorParams.fullStepsPerRotation*2)
|
|
{
|
|
LOG("large Error1 %d, %d, %d",error, y, startAngle);
|
|
|
|
reset=1;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
stepperDriver.move(0,force);
|
|
|
|
//stepperDriver.move(0,NVM->SystemParams.currentMa);
|
|
t1=micros();
|
|
|
|
error=(ANGLE_STEPS/motorParams.fullStepsPerRotation);
|
|
while(error>=((ANGLE_STEPS/motorParams.fullStepsPerRotation)/2-40))
|
|
{
|
|
error=getCurrentLocation()-startAngle;
|
|
//LOG("Error2 %d",error);
|
|
if (error<eMin)
|
|
{
|
|
eMin=error;
|
|
}
|
|
if (error>eMax)
|
|
{
|
|
eMax=error;
|
|
}
|
|
if (abs(error)>ANGLE_STEPS/motorParams.fullStepsPerRotation*2)
|
|
{
|
|
LOG("large Error2 %d",error);
|
|
reset=1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
times[i]=t1-t0;
|
|
|
|
}
|
|
for (i=0; i<100; i++)
|
|
{
|
|
LOG("Time %d %d",i,times[i]);
|
|
}
|
|
LOG("errorMin=%d",eMin);
|
|
LOG("errorMax=%d",eMax);
|
|
|
|
motorReset();
|
|
systemParams.controllerMode=prevCtrl;
|
|
systemParams.microsteps=microSteps;
|
|
enableFeedback=feedback;
|
|
if (state) enableTCInterrupts();
|
|
|
|
}
|
|
|
|
//void StepperCtrl::printData(void)
|
|
//{
|
|
// bool state=TC5_ISR_Enabled;
|
|
// disableTCInterrupts();
|
|
// int32_t i;
|
|
// for(i=0; i<N_DATA; i++)
|
|
// {
|
|
// LOG ("%d\n",data[i]);
|
|
// }
|
|
//
|
|
// if (state) enableTCInterrupts();
|
|
//
|
|
//}
|
|
|
|
#pragma GCC pop_options
|