Is it possible to run a resource-intensive encoder and stepper motor off a single Arduino?

Hello! I am having trouble getting an encoder and stepper motor working together smoothly (for cartpole, so the encoder measures the pole angle, not the motor angle).

The stepper motor uses the FastAccelStepper library, and takes time to get up to speed. With the speeds I'm hoping for it to reach, it can't just jump to the desired speed without stalling.

The encoder function is constantly triggered by two interrupt pins on CHANGE. The interrupt function run is as short as I could get it... it uses an encoder state table, and then increments a counter depending on a clockwise or counterclockwise rotation.

However, when the stepper is spinning at high speed, and the encoder is spinning, sometimes the stepper will stall. I believe that once it misses a step, it starts to stall, and then cannot jump back into the high-velocity without starting over at velocity = 0 and doing the acceleration process again. So the stepper motor isn't just missing steps, but completely stalling during the encoder's interrupt.

My next best idea is to add a second Arduino, that handles the encoder and sends its position over UART. Before I do that, is there anything I am missing that will allow for this to work? Thank you!

#include "Stepper.h"
#include "Rotary.h"

#define aChannel 2
#define bChannel 3

FastAccelStepperController stepper = FastAccelStepperController();
Rotary rotary1 = Rotary(aChannel, bChannel);
TMC2209Stepper uartDriver = TMC2209Stepper(SW_RX, SW_TX, R_SENSE, DRIVER_ADDRESS);

float acceleration;
float encoderPosition = 0; // volatile?

void setup() {
  stepper.setup();
  uartDriver.begin();
  uartDriver.microsteps(16);
  uartDriver.en_spreadCycle(1);

  attachInterrupt(digitalPinToInterrupt(aChannel), rotateEncoder, CHANGE); // shouldn't these interrupts be attached to 2 and 3
  attachInterrupt(digitalPinToInterrupt(bChannel), rotateEncoder, CHANGE);
}

void loop() {
  stepper.update(encoderPosition * 1000, 0, 0); // will eventually implement PID or something here.
}

void rotateEncoder() {
  unsigned char result = rotary1.process();
  if (result == DIR_CW) {
    encoderPosition++;
  } else if (result == DIR_CCW) {
    encoderPosition--;
  }
}

and the rotary function...

unsigned char Rotary::process() {
    unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1); // | is bitwise inclusive OR. << shifts bits left. So 1 and 1 becomes 11.
    state = ttable[state & 0xf][pinstate];                                 // & is bitwise AND. & 0xf is same as & 1111. So it is masking "state" to the lowest 4 bits.
    return state & 0x30;                                                   // 0x30 is 00110000. So masks all except 5th and 6th bits.
}

and lastly the motor update function...

void FastAccelStepperController::update(float requested_acceleration, float requested_velocity, float requested_position) {
    stepper->moveByAcceleration(requested_acceleration);
}

what are you trying to do? what drives the encoder?

1 Like

The encoder is driven by the pole angle (like in cartpole). The position of the encoder will drive the stepper motor. Eventually, that line of code will be replaced with an update from a PID controller.

Even then though, I'm thinking I will still have to deal with moving the stepper and reading the encoder at the same time.

i think you may be interested in Self Balancing Robot

2 Likes

Interesting! Thank you for the link... looks like he was able to accomplish it with a single Arduino. I wonder if an accelerometer is less resource intensive than an encoder... I will study that and respond here soon

I think he is using DC motors instead of stepper motors which I'm realizing might be easier on the Arduino since I wouldn't have to keep calling the STEP pin to move fast. Makes me wonder if I should switch to a DC motor for my project instead of a stepper... thank you @gcjr !

What is the encoder resolution, and what is the maximum motor RPM? That is what will tell you the limits of the Arduino. An AVR-based Arduino is not fast enough to handle an interrupt driven high-resolution encoder at high RPM. Handling even many thousands of interrupts/second requires a faster processor if you're expecting to also do other work. High-resolution encoders generally require either a VERY fast processor, or dedicated hardware to read the encoders.

1 Like

The encoder is 600 pulses per revolution and the stepper motor is 200 steps *16 microsteps per revolution, and it is spinning pretty fast... I'd guess at least 600rpm. If I switch step calls over to something like TimerOne, would that free up the thread for the encoder? Said another way, would the timer interrupt run on the same thread as the main thread? Or is there some special little area for the timer to do its own thing.

Nevermind, I now know that it does run on the same thread

So you're asking the Arduino to field ~12,000 interrupts/second. That is just not likely to work reliably if you are using an AVR-based Arduino. Of course, you don't say WHICH Arduino you are using...

And, with steppers, if you lose a single step, whether due to excessive acceleration, jittery step timing, or excessive load, it is very likely the motor will stall, and the ONLY way to recover is to stop the motor, re-home it, and start over again. That is life with steppers. That said, a properly designed, properly-operated stepper system will NEVER lose steps. Lost steps are a result of incorrect operation. Full stop.

2 Likes

Sorry, it's worse than that. You are fielding interrupts on BOTH edges of BOTH phases of the encoder. So you are asking the Arduino to field 24,000 interrupts/second. That is REALLY not going to work. That is only 666 CPU clocks between interrupts.

2 Likes

Thank you Ray... that is good to know. I didn't really have a good sense of many interrupts a second was appropriate. I will start by adding in a second Arduino to handle the encoder, so the first Arduino can stay focused on the stepper.

if what you're doing is essentially a self-balancing robot using PID (e.g. Segway) how do you translate a linear output from PID that is typical the DC motor voltage into steps. The motor voltage results in a torque, not a change in position

Sorry for the late reply on this... if I understand your question correctly, I am using the FastAccelStepper Library to control the stepper instead of just calling HIGH/LOW on the step pin directly. The library allows you to set the acceleration of the stepper motor with the moveByAcceleration() function. So I am taking the linear output of the PID controller and inputting it into moveByAcceleration.