High-speed motor control with ninja-like precision

Newb here, so go easy on me...

The project I'm trying to implement involves driving a dc motor at rather high frequency (~230 rotations/second) for a specified number of complete rotations. It's important that the motor comes to rest at the same position where it began, because I'll subsequently trigger a DSLR camera to snap a photo of the object being rotated, and repeat this process multiple times.

I'm using this Pololu motor (gearbox removed): Pololu - 9.7:1 Metal Gearmotor 25Dx60L mm HP 6V with 48 CPR Encoder (No End Cap), which has an integrated 48 CPR quadrature encoder. I'm controlling the motor with an Arduino Uno, via the Pololu Simple Motor Controller.

As a starting point, I've written a sketch that runs the motor for a specified number of encoder changes (equal to 20 complete rotations) then stops:

#include <SoftwareSerial.h>
#define rxPin 10
#define txPin 13
SoftwareSerial smcSerial = SoftwareSerial(rxPin, txPin);

#define encoder0PinA 2
volatile unsigned int encPos = 0;
 
int pinIRLED = 9;
int pinIRLED_G = 8;
int pinENC = 11;
int pinENC_G = 10;

void exitSafeStart()
{
  smcSerial.write(0x83);
}
 
void setup()
{ 
  // Set pins for motor encoder power
  pinMode(pinENC, OUTPUT);
  pinMode(pinENC_G, OUTPUT);
  digitalWrite(pinENC, HIGH);
  digitalWrite(pinENC_G, LOW);
  
  pinMode(encoder0PinA, INPUT);    
  attachInterrupt(0, EncoderA, CHANGE); // encoder pin on interrupt 0 (pin 2)

  smcSerial.begin(19200);
  Serial.begin(57600);
 
  // the Simple Motor Controller must be running for at least 1 ms
  // before we try to send serial data, so we delay here for 5 ms
  delay(5);
  smcSerial.write(0xAA);  // send baud-indicator byte
  exitSafeStart();  // clear the safe-start violation and let the motor run
}

void setMotorSpeed(int speed) // speed should be a number from -3200 to 3200
{
  smcSerial.write(0x85);  // motor forward command
  smcSerial.write(speed & 0x1F);
  smcSerial.write(speed >> 5);
}

void EncoderA()
{
  encPos = encPos + 1; // Count encoder A changes
}

int speedo = 3100; // Motor speed 
int rotations = 20; // Number of complete motor rotations
int multiplier = 24; // Number of encoder changes per rotation
int pulses = rotations*multiplier;
int N = 5; // cycle repeat

int count = 1;
void loop() 
{
  if (count <= N) 
  {
    encPos = 0;
    while (encPos < pulses)
    {
      setMotorSpeed(speedo);  // CHARGE! 
    }
        
    setMotorSpeed(0); // stop motor
    delay(100); // Allow motor to come to rest
    
    Serial.print(pulses); // # of changes I specified
    Serial.print(":");
    Serial.print(encPos); // # of changes measured
    Serial.println();
    
    count++; // iterate to next cycle
  }
  else 
  {
     setMotorSpeed(0); // stop motor after last cycle
  }
    
}

Of course, the motor can't stop on a dime, so it overshoots the number of encoder changes I've specified in my while loop. In a bit of wishful thinking, I thought that if the overshoot was at least consistent from trial to trial, I could just subtract that from my "pulses" variable, and have the motor come to rest at exactly 20 complete rotations. Not so much. The motor rotates for another 89 to 94 encoder changes at this frequency - so not consistent. Plus, I'm not even totally convinced that the interrupts are catching every encoder change at this frequency (any thoughts on this?)

So I'm not sure where to go from here. Is this even the right approach for this type of project? Should I be working with a PID controller instead? I have no experience with a PID controller, so am unfamiliar with their limitations and tuning challenges.

Many thanks for any thoughts!

Do you really have to use a DC motor rather than a stepper motor?

If you are stuck with the DC motor, I'd suggest using a feedback system to control the position of the motor. A standard PID control algorithm would seem ideal for that, with the 'error' signal being the distance between the motor's current position and the desired position. The PID would enable you to control the acceleration and speed to move the motor to the desired position. You would need to ensure that the maximum speed was slow enough that you could reliably read the encoder, and of course you need to have a reliable way to read the encoder - without that, you're stuffed.

Thanks for your suggestions, Peter. No, I'm not stuck with a DC motor, so a stepper motor is a possibility. I originally went with a DC motor because I knew it would give me the speed I needed. My understanding is that stepper motors are quite a bit slower - do you think I can gear a stepper motor to spin something as fast as 230 rotations per second? Also, if anyone has any suggestions of a particular stepper motor that might work for my application, that would be much appreciated!

Also, if I do stick with the DC motor and PID controller route, do you happen to know what the time resolution is with interrupts on the Arduino? I'm wondering at what speed I should start worrying about missing encoder change events.

Thanks again for your help.

Edit: The object I'm spinning is extremely small and lightweight.

pastafarian:
do you think I can gear a stepper motor to spin something as fast as 230 rotations per second?

Edit: The object I'm spinning is extremely small and lightweight.

I think that's rather high for a stepper motor, but if you aren't going to need much torque perhaps you could gear it up. What sort of angular resolution do you need?

PeterH:
What sort of angular resolution do you need?

I think within a range of 5 deg. or so would be fine.

230 Hz is about 14000 rpm ! What objects are you planning on spinning that fast?

That's like turbo injector speeds. Have you built a safety cage around it for when it flies off or disintegrates?

ardnut:
230 Hz is about 14000 rpm ! What objects are you planning on spinning that fast?

A hamster wheel. My hamster is fast.

Actually, I need to spin insect wings at their natural rotational velocity.

OK, I guess the rotational energy won't be that big then !

just make the support can't come off and everything is perfectly balanced.

Any off-axis weight will cause some nasty vibrations. You may want to glue the sample directly to the motor spindle. :wink:

pastafarian:
I think within a range of 5 deg. or so would be fine.

That means you could probably get away with gearing the stepper up by about 5:1, which brings your top speed into the sort of range that you might achieve with a stepper - although it's likely to be marginal.

When you bring your sample to a stop does it always need to be in exactly the same orientation (i.e. from run to run) or does it need to stop at an arbitrary position?

PeterH:
When you bring your sample to a stop does it always need to be in exactly the same orientation (i.e. from run to run) or does it need to stop at an arbitrary position?

It needs to stop at the same position every time, so that it's always oriented the same way relative to the camera view.

pastafarian:
It's important that the motor comes to rest at the same position where it began, because I'll subsequently trigger a DSLR camera to snap a photo of the object being rotated, and repeat this process multiple times.

The camera is going to snap a photo while the object is being rotated, or when it stops? I'm not clear on the exact connection between the motor stopping, and a photo being taken. If it's what I think it is, then you don't really need an encoder or stepper, but just a trigger of some sort. Regardless...

pastafarian:

PeterH:
What sort of angular resolution do you need?

I think within a range of 5 deg. or so would be fine.

That could be obtained with your dc motor setup. In fact, if utilizing a deceleration ramp is acceptable, you could well under that.

jraskell:

pastafarian:
It's important that the motor comes to rest at the same position where it began, because I'll subsequently trigger a DSLR camera to snap a photo of the object being rotated, and repeat this process multiple times.

The camera is going to snap a photo while the object is being rotated, or when it stops? I'm not clear on the exact connection between the motor stopping, and a photo being taken. If it's what I think it is, then you don't really need an encoder or stepper, but just a trigger of some sort. Regardless...

Sorry for the unclear description: The motor will rotate a wing for a specified X number of turns, then stop rotating. Camera will snap photo of wing (while motor is stopped). After photo, motor will start spinning again...and this process will repeat multiple times. The motor needs to stop at the same position after every spin, so that the wing is oriented in the same position (orthogonally to the camera view) for every photo.

jraskell:
If it's what I think it is, then you don't really need an encoder or stepper, but just a trigger of some sort.

Could you please elaborate? I already have the motor with the encoder, so I'm inclined to use the encoder output unless it's insufficient for this application, or has a shortcoming that I'm unaware of. For example, is there a chance I'm missing encoder change events at this rotation speed? (That would be a big shortcoming!) One channel of the encoder should be putting out 12 pulses per revolution of the motor shaft, or 24 edges. If I'm spinning at 230 Hz, that equates to an interrupt trigger every 181 microseconds (if I'm triggering on a CHANGE). Does anyone know whether this rate is within the limits of the arduino? I could always reduce resolution by triggering only on a rising or falling edge.

jraskell:
That could be obtained with your dc motor setup. In fact, if utilizing a deceleration ramp is acceptable, you could well under that.

Great! Can you point me to a good reference for coding a deceleration ramp?

Thanks.

pastafarian:
It needs to stop at the same position every time, so that it's always oriented the same way relative to the camera view.

That makes the problem much easier - all you really need is a small bias tending to stop the motor at a given position.

Do you actually care how many times it turns, or only the approximate number of turns and the final position?

PeterH:
That makes the problem much easier - all you really need is a small bias tending to stop the motor at a given position.

Do you actually care how many times it turns, or only the approximate number of turns and the final position?

Ideally I'd like the motor to spin the exact number of turns I specify. However, if there's an easy control solution that comes with a little slop in the number of turns, I'm willing to be within ±2 turns of what I specify.

I'd love to hear more about incorporating a bias - sounds cool!

Hi,

There is a very cheap and simple way of ensuring that the sample stops in exactly the same place every time if that will help. But the last turn or two will need to be slowish.

Would this be of any help?.

Mark

holmes4:
Hi,

There is a very cheap and simple way of ensuring that the sample stops in exactly the same place every time if that will help. But the last turn or two will need to be slowish.

Would this be of any help?.

Mark

Hi Mark. Yes, that would definitely be helpful. I'm willing to have the last couple of turns be slower. Thanks for any suggestions you may have.

Make a card disk with a radius 2 or 3 times the length of your sample. Cut a small hole near the edge of the disk.

Fit the disk to the motor spindle so that it can't slip.

Now place a LED behind the disk so that it will be seen through the hole in the disk as the disk turns.

A Photo resistor(PR) is placed in front of the disk so that light from the LED will fall on the PR when the hole in the disk passes and the light is blocked the rest of the time.

Connect the PR via a voltage divider to an analog pin.

The value from the analog pin will be low when the light from the LED can pass through the disk and high when it can't.

Take the last few turns slowly and when get a low reading stop!.

You may find you need to fit some kind of "sunhat" to the PR to cut down on the effects of ambient light.

The PR also gives you a rev counter.

Mark

Great! Can you point me to a good reference for coding a deceleration ramp?

I'm not aware of any reference off hand. The basic gist is that you arbitrarily slow down the motor as you approach your target point. There are a variety of ways to accomplish this. A first pass crude approach would be to pick a couple points prior to your target stopping point and reduce the motor speed first to 2/3 speed, then to 1/3 speed. The end goal is to have the motor come to a more controlled stop as it approaches it's target reducing overshoot and inaccuracies in where you stop. By adjusting the points at which you reduce motor speed and the amount of speed reduction you can fine tune your specific setup for optimal performance. You can also increase the number of deceleration points if you want to provide a smoother rate of deceleration, but that should have little affect on your end goal of stopping at a specific location. That goal could very well be accomplished with a single deceleration point.

pastafarian:
I'd love to hear more about incorporating a bias - sounds cool!

No, it's not at all cool and nowhere near as elegant as the other solutions being suggested. Basically you arrange the motor so that it stops in a consistent position. Probably, being a DC motor it will already have several positions that it likes to settle in due to the position of the magnets, and which one it falls into is just a game of roulette. If you bias the motor, for example by putting an eccentric cam with a sprung follower, you can arrange for it to always stop in the same place.

Thanks for all the help, folks. I'm getting closer...At the moment, my encoder pulse counter ("encPos" in my code above) appears to be giving me a HIGHER value than it should for a given number of motor rotations, even accounting for inertial overshoot. While this gives me hope that I'm not missing encoder transitions at this high rotation frequency (something I was originally worried about), I'm not sure what to make of it. Is it possible that I'm picking up "bounce" in my signal, causing the interrupt to trigger more frequently than it should?? I've seen references of bounce coming from hardware switches, although I'm unclear on whether it can happen in quadrature encoder signals as well?

In any event, If I can sort out the encoder signal to give me an accurate reading, then I'll use it to implement a crude deceleration ramp (thanks, jraskell.) If I can't tame the encoder, then I'll revert to holmes4's photoresistor method, which sounds very promising. I'll come back to update with progress...

Again, many thanks for the assistance!