Go Down

Topic: Asynchronous control of 8 ClearPath SD "steppers" (Read 1 time) previous topic - next topic

jwllorens

May 02, 2018, 02:54 am Last Edit: May 02, 2018, 03:06 am by jwllorens
Hello all.  This post will be a long one, but I have ZERO programming experience, so I need a little guidance.  

I am trying to generate step pulses and direction signals for 8 individual steppers asynchronously.  I need to vary the number of steps before the direction signal is inverted on the fly, and I need to vary the time between step signals (rising edge triggers the step) on the fly.  

This is what I came up with.  Obviously, it is junk, and it doesn't work.  The general idea was to use a timer interrupt at 1 micro intervals to set PORTC and increment a counter, and then do all the work to determine which individual bits should be on or off within the loop().  So when you run it, the interrupt hogs all the processing time (even though I had thought I made the interrupt pretty lean) and it takes forever for the loop() to do its thing.

Code: [Select]
//these arrays and functions are for rapidly changing the state of output pins.  Only compatible with MEGA2560
byte volatile * const DDRConfig[] = {&DDRE,&DDRE,&DDRE,&DDRE,&DDRG,&DDRE,&DDRH,&DDRH,&DDRH,&DDRH,&DDRB,&DDRB,&DDRB,&DDRB,&DDRK,&DDRK,&DDRH,&DDRH,&DDRD,&DDRD,&DDRD,&DDRD,&DDRA,&DDRA,&DDRA,&DDRA,&DDRA,&DDRA,&DDRA,&DDRA,&DDRC,&DDRC,&DDRC,&DDRC,&DDRC,&DDRC,&DDRC,&DDRC,&DDRD,&DDRG,&DDRG,&DDRG,&DDRL,&DDRL,&DDRL,&DDRL,&DDRL,&DDRL,&DDRL,&DDRL,&DDRB,&DDRB,&DDRB,&DDRB,&DDRF,&DDRF,&DDRF,&DDRF,&DDRF,&DDRF,&DDRF,&DDRF,&DDRK,&DDRK,&DDRK,&DDRK,&DDRK,&DDRK,&DDRK,&DDRK};
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]);}


//These variables are used for setup and maintainance during runtime.
byte outputRegister = B00000000; //this will contain a byte which represents what the state should be on all pins on PORTC
int pinB_assignment = 30; //this is just a counter used to assign pins to the 8 motors on program start
volatile unsigned long cnt = 0;
int usecs = 50;
unsigned long newcnt = 0;
unsigned long oldcnt = 0;


//This is the Motor class which will contain information specific to each individual motor.
class Motor
{
  private:
    volatile bool _pulsing = false;
    bool _changeDir = false;
    int _resolution = 6400;
    int _stepRange = 100;
    int _currentStep = 0;
    int _stepInterval = 2;
    int _dir = -1;
    unsigned long _lastcnt = 0;
    int _pinE, _pinA, _pinB, _pinHLFB;
  public:
    Motor(int _p1, int _p2, int _p3, float _res);
    int getVar() {return PinConfig[_pinB];};  //just a method to check things during testing.  was checking that all pins were assigned properly during setup
    void Step();
    void SetMotorAngle(float _range);
    void SetMotorRPM(float _speed);
};

Motor::Motor(int _p1, int _p2, int _p3, float _res){
      pinMode(_p1, OUTPUT);
      pinMode(_p2, OUTPUT);
      pinMode(_p3, INPUT_PULLUP);
      PinOn(_p1);
      PinOn(_p2);
      _pinE = _p1;
      _pinA = _p2;
      _pinHLFB = _p3;
      _resolution = _res;
      _pinB = pinB_assignment;
      pinB_assignment += 1;
}

//this method is to set the number of steps that will occur before the motor reverses direction, inverts _pinA
void Motor::SetMotorAngle(float _angle) {_stepRange = floor(_angle * (_resolution / 360));}

//this method is to set the rate at which the step pulses should occur.  it is convoluted and untested.  will clean up once a working method of generating step pulses is found
void Motor::SetMotorRPM(float _rpm) {_stepInterval = (1000000/((_resolution * (_rpm/60))))/((usecs*16)-1);}

//this method is called to do a step based on several conditions.  if the step pulse is high, turn it off.  otherwise, check to see if it is time to pulse again, if so, check to see if enough steps have been made to change direction, if so, change _dir, if not, do a step.
void Motor::Step() {
  if (_pulsing) {
    outputRegister &= ~(PinConfig[_pinB]);
    _pulsing = false;
  } else {
    if ((cnt - _lastcnt) >= _stepInterval) {
      _lastcnt = cnt;
       if (_changeDir) {
        if (_dir == 1) {
          _dir = -1;
          PinOff(_pinA);
        } else {
          _dir = 1;
          PinOn(_pinA);
        }
        _changeDir = false;
      } else {
        outputRegister |= PinConfig[_pinB];
        _pulsing = true;
        _currentStep += _dir;
        if ((_currentStep == _stepRange) | (_currentStep == 0)) {
          _changeDir = true;
        }
      }
    }
  }
}


//declare some things.
Motor *MotorA = NULL, *MotorB = NULL, *MotorC = NULL, *MotorD = NULL, *MotorE = NULL, *MotorF = NULL, *MotorG = NULL, *MotorH = NULL;


//set up interrupt for compare match timer 5 compare register A, on interrupt, set PORTC to 8 bit global integer "outputRegister" and then increment cnt by 1.
ISR(TIMER5_COMPA_vect){
  PORTC = outputRegister;
  cnt++;
}


//setup here
void setup() {
  // set up timer5 and compare registers, set up DDRC to output.
  cli();
  TCCR5A = 0x00;
  TCCR5B = (_BV(WGM52) | _BV(CS50));     // no prescaler, CTC mode
  OCR5A  = ((usecs*16)-1); / /1MHz = 1us cycle, so "usecs" should = the number of micros before compare match occurs?
  TIMSK5 = _BV(OCIE5A);  //don't understand this, but it needs to be here.  this changes if different compare register is used            
  DDRC = B11111111;  //all pins on Port C should be set to output
  sei();  
  //initialize some motors
  MotorA = new Motor(22,23,69,6400);  //create an instance of motor
  MotorB = new Motor(24,25,68,6400);   //and another
  //MotorC = new Motor(26,27,67,6400);  //the rest are disabled during testing
  //MotorD = new Motor(28,29,66,6400);
  //MotorE = new Motor(38,39,65,6400);
  //MotorF = new Motor(40,41,64,6400);
  //MotorG = new Motor(42,43,63,6400);
  //MotorH = new Motor(44,45,62,6400);
  Serial.begin(9600);//start serial communication
}
void loop() {
    if (cnt > oldcnt) {   //make sure we aren't doing unnecessary work
      oldcnt = cnt;  
      MotorA->Step();  //call Step() as often as possible
      MotorB->Step();
      //MotorC->Step();
      //MotorD->Step();
      //MotorE->Step();
      //MotorF->Step();
      //MotorG->Step();  
      //MotorH->Step();
    }
}


So here are my questions.

1)  Am I saving any time by turning pins on or off via port manipulation using the array that I spent an hour writing out with some Excel help? (the index of DDRconfig[], PortConfig[], and PinConfig[] should match the pin number and return the information relevant to that physical pin, which can then be turned on or off with PinOn() or PinOff()).  I thought I was being pretty sneaky here by loading all the info for every broken-out Mega pin into the arrays and then saving a bunch of time by using this info to set registers directly, but like I said, I don't know what I am doing and this may have been counterproductive.

2) Can I set up 8 PWM pins on the Mega, in two groups of 4 (separate timer for each group) and then vary the frequency on the fly, while also counting the number of pulses?  Would this work?  I have no idea how to count the number of pulses on PWM signals and I don't really understand the PWM stuff.  I assume I would just use a 50% duty cycle (since I don't care about duty cycles) and modify the timer registers on the fly to adjust the frequencies.

3) How can I get an accurate count of micros()?  Using a timer interrupt every microsecond to increment a variable seems like it eats all the processing power of the Arduino.

4) Does anyone have any better ideas on how to control these stepper motors?

Robin2

Hello all.  This post will be a long one, but I have ZERO programming experience, so I need a little guidance. 
The code does not look like code that was created by someone with ZERO experience.

Assuming you really are a beginner let's start at the beginning ...

Post a link to the datasheet for your stepper motors
Tell us what stepper motor drivers you are using

Create a program that makes one motor move in the way that you need.

Have a look at these links
Stepper Motor Basics
Simple Stepper Code

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

jwllorens

I understand how CPUs work better than the average joe and I have done some scripting within a couple of video games back in the day.  I don't think ArmA is really like programming but I guess C# in Space Engineers is the most similar to messing around with Arduinos.  But I am very familiar with binary operations and whatnot so I am not starting from scratch, but I really do have no "experience."  I have never written an actual program, certainly nothing object oriented.  This was my first attempt. 

There is a lot going on there that I do not understand, but I included because I read about it and that was the only way I could get it to work.  For example, why do my pointer arrays that reference the pin output registers need to be declared as volatile? 

Anyways, here is the data for the motors.  Note that they have an integrated controller, so I just need to pump step and direction signals directly to the servo.  The steppers can also do some pretty nice things with RAS on their own, so I can leave that out of my code and just do a simple trapezoidal motion profile, but that comes after I can actually send the pulses fast enough.

This is the motor itself (I have 16 of them) https://www.teknic.com/model-info/CPM-SDHP-3441S-ELN/?model_voltage=75VDC

Here is the user manual (go to page 117 for the relevant information about control signals for this motor) https://www.teknic.com/files/downloads/clearpath_user_manual.pdf

I can spin the motors just fine with any number of methods.  Simply using if (millis - _lastStepTime > _interval) {doStep;} will work alright, but when you get into precisely controlling multiple motors issues with processing time arise.  Is it possible to create a temporary PWM signal that will only last a certain number of pulses?  Perhaps by using interrupts to disable the timer after X number of compare matches?

Robin2

I can spin the motors just fine with any number of methods.  Simply using if (millis - _lastStepTime > _interval) {doStep;} will work alright, but when you get into precisely controlling multiple motors issues with processing time arise.  Is it possible to create a temporary PWM signal that will only last a certain number of pulses?  Perhaps by using interrupts to disable the timer after X number of compare matches?
It would be a big help if you could explain the sort of timing that you need to achieve. Can you provide some examples?

It would also help if you tell us what you are making that will be using 16 stepper motors. Understanding the context can be surprisingly useful.

Another thing to keep in mind is that a 16MHz Arduino may not be fast enough to produce all the step signals required by 16 motors.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

jwllorens

#4
May 03, 2018, 02:27 am Last Edit: May 03, 2018, 02:33 am by jwllorens
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?

Code: [Select]
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. 

Code: [Select]

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.

Robin2

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.
That suggests to me that you are looking for free advice for a commercial project - which does not seem fair.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

jwllorens

So why are you here at all if you are opposed to helping for free?  Or are you just going full on class warrior here?

I am working on this project for free, I will not make a penny off of it and I will receive no credit if it works.  The broader idea behind the project is not even my idea, I simply volunteered to help for fun and the experience.  But obviously, as with many prototyping endeavors, the end-goal is to produce something useful that will, dare I say, perhaps save someone some money. 

If your concern is that I will be making money off of your wisdom, you are wrong.  If your concern is that someone, somewhere might make money off of this project working...  Well, you do you.  Nobody is forcing you to reply, but I am certainly not jeopardizing my relationship with a client, even if this particular work isn't paid, by posting his original ideas on a public internet forum.  Sharing that information isn't even relevant to my questions. 

I have, however, shared all of my code, and that along with any technical replies would prove useful to anyone new to this like myself that wants to spin some stepper motors, for a profit or otherwise.  I only got this far by reading replies to similar questions on forums like this in the first place, so it stands to reason that someone in the future would benefit off of any answers you produce as much or more than I will.

So I guess I will try again.  Am I understanding the prescaler and how to manipulate the timers correctly?  I appreciate any help you offer.     

Robin2

#7
May 03, 2018, 01:12 pm Last Edit: May 03, 2018, 01:20 pm by Robin2
I am working on this project for free,
Fair enough. People do try to get free help here for commercial projects when they should just be paying for professional advice, so get off your high horse.

If you divide the clock by 8 then the Timer clock becomes 2MHz rather than 16Mhz.

I presume you have some of the other WGGM bits set because I think  WGM2 on its own is invalid.

I am always concerned that I don't know how to set the bits in a register using bit names so I usually just use binary - such as TCCR5B = 0b00001010;

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

jwllorens

"I presume you have some of the other WGGM bits set because I think  WGM2 on its own is invalid."

I appreciate this.  I don't have a good grasp of the WGGM bits.  I have poured over the spec sheets about 7 times already and I was barely able to cobble together that stuff that I did.  I am very likely leaving out an important bit (although it is odd that my sketch works, a timer register set incorrectly should result in massive issues)

I ran into something weird though.  I rearranged some code when I was trying to find where I was losing count of a step here and there, and I noticed that a member variable for the motor class was changing spontaneously without any pointer references to it and without a single command to write to it.  What could be causing this?  The variable gets checked every time a compare match interrupt is called, but not written to.  It was also declared as volatile, if that matters.  So why is it not retaining it's value? 

Can't post code at the moment, but I will in a bit.

Robin2

One problem I am having here is that I have no sense of your level of competence with programming and with microprocessors. What you said in your Original Post "I have ZERO programming experience" is hard to believe. For all I know you know more about this stuff than I do,

On the other hand, if you really are new to programming and microprocessors I reckon you are starting with code that is far too complicated for a beginner because you are likely to be missing the experience to debug your code.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

jwllorens

Quote
One problem I am having here is that I have no sense of your level of competence with programming and with microprocessors. What you said in your Original Post "I have ZERO programming experience" is hard to believe. For all I know you know more about this stuff than I do,

On the other hand, if you really are new to programming and microprocessors I reckon you are starting with code that is far too complicated for a beginner because you are likely to be missing the experience to debug your code.

...R
Oh there is no doubt that I am using a lot of techniques here that are beyond my experience level to debug, but another problem with inexperience is that I do not see any alternatives to asynchronously generate pulses at microsecond intervals, vary the timing of those pulses at runtime, and do so with any other methods besides using direct port manipulation to save processing power and multiple timers with compare match interrupts so that I can bang values into the registers for those timers to quickly adjust the timing on the fly and turn on/off interrupt flags.

If there is an approach that I am not considering and don't know about, I certainly want to learn it.

Maybe PWM?  Would it be possible to throw some numbers in a timer's registers to get a PWM frequency of my choosing, then also have a compare match interrupt that would simply increment a variable, and then at a certain value I could flip the PWM output pin to input to shut it off?

I am just really confused by how the timers work in general.  It doesn't look like I can generate PWM and use CTC mode.

Robin2

If there is an approach that I am not considering and don't know about, I certainly want to learn it.
Because you have jumped in at the deep end I don't actually know what you are doing in order to suggest a different approach.

If this was my project (and with own level of experience) I would start getting one motor to move slowly. Then I would get that motor to move at the speed and sequence that I require. With that knowledge I would then sit back and consider how I would extend my knowledge to additional motors..

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Go Up