Alright, well, I sort of figured it out.
I need to run 16 steppers for a prototype machine that a client is working on. Unfortunately, I cannot disclose what it is because that would not be in the client's best interest.
These steppers lock up at speeds greater than 800rpm, but 100-200rpm is the range I would probably want them in for maximum torque. They will be under a lot of load. I am guessing from rough napkin math, that means I need to produce a rising edge every 40 microseconds or so, but definitely not more frequent than that.
I quickly realized that the arduino is not capable of running all these steppers, but worst case I can use one arduino per two-steppers and use 8 arduinos. Then I could use another arduino or teensy or something to communicate simple commands to the other arduinos that are directly controlling the stepper. For now, I just need to get two steppers running on the arduino simultaneously.
Anyways.
I have accomplished that to some degree by using a separate hardware timer for each stepper. However, I still need help. I am a little confused on the math here. Can anyone tell me how fast this timer is ticking up (time between ticks) so that I can more precisely calculate the needed compare register values on the fly?
TCCR5B = (_BV(CS51) | _BV(WGM52));
I am assuming that this code sets a prescaler of 8, and with a 16MHz clock, this timer is ticking every 0.5 microseconds. My interrupt is a state machine that turns a pin on and off, so if I were to interrupt at every tick, it would be 1 microsecond between each rising edge. From this, I am guessing the equation to convert RPMs to a compare register interrupt value would be 600,000,000 / ((Stepper Resolution) * RPMs)? Is this correct?
Here is the full code that lets me spin motors just fine.
byte volatile * const PortConfig[] = {&PORTE, &PORTE, &PORTE, &PORTE, &PORTG, &PORTE, &PORTH, &PORTH, &PORTH, &PORTH, &PORTB, &PORTB, &PORTB, &PORTB, &PORTK, &PORTK, &PORTH, &PORTH, &PORTD, &PORTD, &PORTD, &PORTD, &PORTA, &PORTA, &PORTA, &PORTA, &PORTA, &PORTA, &PORTA, &PORTA, &PORTC, &PORTC, &PORTC, &PORTC, &PORTC, &PORTC, &PORTC, &PORTC, &PORTD, &PORTG, &PORTG, &PORTG, &PORTL, &PORTL, &PORTL, &PORTL, &PORTL, &PORTL, &PORTL, &PORTL, &PORTB, &PORTB, &PORTB, &PORTB, &PORTF, &PORTF, &PORTF, &PORTF, &PORTF, &PORTF, &PORTF, &PORTF, &PORTK, &PORTK, &PORTK, &PORTK, &PORTK, &PORTK, &PORTK, &PORTK};
byte const PinConfig[] = {B00000001, B00000010, B00010000, B00100000, B00100000, B00001000, B00001000, B00010000, B00100000, B01000000, B00010000, B00100000, B01000000, B10000000, B01000000, B10000000, B00000010, B00000001, B00001000, B00000100, B00000010, B00000001, B00000001, B00000010, B00000100, B00001000, B00010000, B00100000, B01000000, B10000000, B10000000, B01000000, B00100000, B00010000, B00001000, B00000100, B00000010, B00000001, B10000000, B00000100, B00000010, B00000001, B10000000, B01000000, B00100000, B00010000, B00001000, B00000100, B00000010, B00000001, B00001000, B00000100, B00000010, B00000001, B00000001, B00000010, B00000100, B00001000, B00010000, B00100000, B01000000, B10000000, B00000001, B00000010, B00000100, B00001000, B00010000, B00100000, B01000000, B10000000};
void PinOn(int pin) {
*PortConfig[pin] |= PinConfig[pin];
}
void PinOff(int pin) {
*PortConfig[pin] &= ~(PinConfig[pin]);
}
class Motor
{
private:
volatile bool _moving = false;
volatile bool _pulsing = false;
volatile unsigned long _tickCnt = 0;
volatile unsigned long _moveTicks = 0;
int _MotorID;
int _moveDir = 1;
unsigned long _resolution = 6400;
unsigned long _moveSpd = 0;
unsigned long _moveAng = 0;
int _pinE, _pinA, _pinB, _pinHLFB;
public:
Motor(int _p1, int _p2, int _p3, int p4, int _res, int _ID);
void Oscillate();
void MoveTo(unsigned long _ang, int _spd, int _dir);
void SetParams(unsigned long _ang, unsigned long _spd, int _dir);
void Tick();
};
void Motor::SetParams(unsigned long _ang, unsigned long _spd, int _dir) {
_moveTicks = ((2 * _ang * _resolution) / 360); //this isn't necessary and can be done in MoveTo()
_moveSpd = 60000000 / (_spd * _resolution); //i put it here for more flexibility down the line
_moveDir = _dir;
}
Motor::Motor(int _p1, int _p2, int _p3, int _p4, int _res, int _ID) {
_MotorID = _ID; //constructor just puts things in the right variables
pinMode(_p1, OUTPUT);
pinMode(_p2, OUTPUT);
pinMode(_p3, OUTPUT);
pinMode(_p4, INPUT_PULLUP);
PinOn(_p1);
PinOn(_p2);
_pinE = _p1;
_pinA = _p2;
_pinHLFB = _p4;
_pinB = _p3;
_resolution = _res;
}
void Motor::Oscillate() {
if (!_moving) {
if (_moveDir > 0) {
MoveTo(_moveAng, _moveSpd, 1);
_moveDir = -1;
} else {
MoveTo(_moveAng, _moveSpd, -1);
_moveDir = 1;
}
Serial.println(OCR5A);
}
}
void Motor::Tick() {
if (_pulsing) {
PinOn(_pinB);
_pulsing = false;
} else {
PinOff(_pinB);
_pulsing = true;
}
if (_tickCnt >= _moveTicks) {
switch (_MotorID) {
case 0:
TIMSK5 &= ~(_BV(OCIE5A)); //turns off compare interrupts when the move is done
break;
case 1:
TIMSK4 &= ~(_BV(OCIE4A));
break;
case 2:
TIMSK3 &= ~(_BV(OCIE3A));
break;
}
_tickCnt = 0;
_moving = false;
}
_tickCnt++;
}
void Motor::MoveTo(unsigned long _angle, int _speed, int _dir) {
_moving = true;
_tickCnt = 0;
if (_dir > 0) {
PinOn(_pinA);
} else {
PinOff(_pinA);
}
switch (_MotorID) { //probably a more elegant way to do this
case 0: //determines which instance of motor called the method
cli(); //then it updates the appropriate compare register and turns on compare interrupt mode
OCR5A = _speed;
TCNT5 = 0;
TIMSK5 |= _BV(OCIE5A);
sei();
break;
case 1:
cli();
OCR4A = _speed;
TIMSK4 |= _BV(OCIE4A);
TCNT4 = 0;
sei();
break;
case 2:
cli();
OCR3A = _speed;
TIMSK3 |= _BV(OCIE3A);
TCNT3 = 0;
sei();
break;
}
}
Motor *MotorA = NULL, *MotorB = NULL, *MotorC = NULL;
void setup() {
cli(); //set up all our timers on CTC mode
TCCR5A = TCCR5B = 0x00;
TCCR5B = (_BV(CS51) | _BV(WGM52));
TCCR4A = TCCR4B = 0x00;
TCCR4B = (_BV(CS41) | _BV(WGM42));
TCCR3A = TCCR3B = 0x00;
TCCR3B = (_BV(CS31) | _BV(WGM32));
sei();
MotorA = new Motor(41, 42, 43, 40, 800, 0); // create some instances of motor
MotorB = new Motor(37, 38, 39, 36, 800, 1);
MotorC = new Motor(33, 34, 35, 32, 800, 2);
Serial.begin(9600);
MotorA->SetParams(36000,1200,1); // a value of 1200 can be used until motor locks?
MotorB->SetParams(60,50,-1);
}
ISR(TIMER5_COMPA_vect) {
MotorA->Tick();
}
ISR(TIMER4_COMPA_vect) {
MotorB->Tick();
}
ISR(TIMER3_COMPA_vect) {
MotorC->Tick();
}
void loop() {
MotorB->Oscillate();
MotorA->Oscillate();
}
From here I can basically put whatever I want in my loop, and I can use the built-in RAS with the steppers for anti-jerk acceleration and not have to code it, and the steppers also have a built in homing function that I can have them run on enable, so I don't need to code that either. I just need to tell them what angles and speeds to oscillate.
However, something is wrong. I am able to put in values as high as 1200 into SetParams() before the stepper automatically locks up. Yet the motor should lock up at 800 rpm, which it does when tested using the software provided by the manufacturer. I am guessing my math is wrong on the timer. I doubt it is the interrupt latency, otherwise the motor would never lock up it would just not ever get faster with higher values. This is not the case because it definitely is getting fast pulses and locking up at a value of 1200.