Speed control of 6 DC motors with encoders

Hello everyone,

I have never used Arduino boards before, but am a bit familiar with electronics and programming, and learn fast. I would need to do a real-time speed control of 6 DC motors at the same time (Pololu - 298:1 Micro Metal Gearmotor HPCB 12V with Extended Motor Shaft) that have encoders (Pololu - Magnetic Encoder Pair Kit for Micro Metal Gearmotors, 12 CPR, 2.7-18V). The maximum rpm of the DC motors is 30000, and the encoders have 12 counts per revolution with 2 quadrature outputs. That's then a total of 12 outputs to monitor for the 6 DC motors. I am thinking about using Arduino or Arduino-like boards to monitor the speed of the DC motors, and control their speed through PWM signals on H bridges. The desired speed would be sent from an external device (a computer for instance) to the selected board through USB for instance.

  • I read that ISR are a good way to detect changes in inputs and thus are relevant for encoders. I would then need 12 inputs that support ISR. The Arduino Uno WiFi Rev.2, Zero, Due, and 101 seem to have enough inputs for that (source: attachInterrupt() - Arduino Reference). However, I came across this reference (GitHub - PaulStoffregen/Encoder: Quadrature Encoder Library for Arduino) which says that a single ISR per encoder or no ISR in certain cases might work, but 2 ISR per encoder look like the optimal solution for accracy.
  • I would also need 2 outputs per DC motor, to drive them using H bridges (that's what I am thinking of at the moment). That's a total of 12 outputs that should support PWM.

Only the Arduino Due has enough PWM and ISR ports for my needs. So that looks like a good choice to me. However, I wonder it this board has enough processing power and a frequency that is high enough to measure the speed of the motors using the encoders and vary the motors speed through PWM. I would appreciate a feedback from people who would have an opinion and advises on this project. I am also open to any suggestion concerning better strategies in terms of hardware and software.

Hi, I'm no expert but the Arduino might be too slow for the task.

Recently I've watched a youtube video of someone wanting to build an "electronic leadscrew" and he was trying to control the speed of a single motor with info from an encoder. He made some good points on choosing a platform: Lathe Electronic Leadscrew Part 1: Proof of Concept - YouTube

cgirerd:
The maximum rpm of the DC motors is 30000, and the encoders have 12 counts per revolution with 2 quadrature outputs. That's then a total of 12 outputs to monitor for the 6 DC motors.

Of those you only have to react to one channel of the encoder; the second channel is just to detect the direction. The speed is measured with one channel only.

So that'd be 360,000 pulses per encoder per second, 2.16 million between six motors. That's a lot. Too much, really, way more than you need for speed control. Reduce that to a simple one pulse per rotation kind of detector. Very simple to do: optical detector, half the shaft reflective, half not. That gives you a nice one pulse per rotation. Probably quite a bit cheaper than a high speed quadrature encoder. If you really need to know the direction: add second such sensor on the axis, with the colouring 90° off.

So that'd bring back the speed to a much more reasonable 30,000 pulses per second, still 180,000 between the sensors. That leaves less than 10 clock cycles per pulse on a 16 MHz Arduino. You're still going to need a faster processor, e.g. a Teensy.

You have over stated the rpm of the motor by a factor of 10 (actual 3,000 r.p.m.) and that is a no load estimate.

What are you driving with these motors?

I realised I was a factor 60 too high (minutes to seconds) myself.

Looking at Pololu - 298:1 Micro Metal Gearmotor HPCB 12V with Extended Motor Shaft it states the motor has a gearbox and the output is just 110 rpm. At 12 pulses per rotation (now that encoder starts to make sense) you get 1,320 pulses per minute, or 22 per second. That's so slow you don't need interrupts. Or if you do (it does give more accurate timing) pin change interrupts can do fine.

For speed control it should be sufficient to use a detector that produces one pulse per revolution. But even with that, 6 motors producing 500 pulses per revolution will be hard work for an Arduino. Keep in mind that the pulses for the different motors are very unlikely to be conveniently spaced relative to the pulses for the other motors. You could get all 6 pulses within the same microsecond. Also keep in mind that detecting the pulses is only part of the speed control process - you need to have enough CPU capacity left over for the PID speed control.

My first thought is to use a separate microprocessor for each motor - an Attiny should be adequate.

This link has code that I extracted from a program I use for speed control for a small DC motor.

...R

Thank you all for your inputs, appreciate it!

wvmarle:
I realised I was a factor 60 too high (minutes to seconds) myself.

Looking at Pololu - 298:1 Micro Metal Gearmotor HPCB 12V with Extended Motor Shaft it states the motor has a gearbox and the output is just 110 rpm. At 12 pulses per rotation (now that encoder starts to make sense) you get 1,320 pulses per minute, or 22 per second. That's so slow you don't need interrupts. Or if you do (it does give more accurate timing) pin change interrupts can do fine.

Actually, the encoders are attached to the motors themselves (there's as extended shaft at their bases), not to the output of the gearboxes, so that's still 110 (motor rpm) x 298 (gearbox ratio) x 12 (pulses per revolution) / 60 (sec) = 6,556 pulses to count per second for a single motor, x 6 = 39,336 pulses to count per econd for the six motors.

Robin2:
For speed control it should be sufficient to use a detector that produces one pulse per revolution. But even with that, 6 motors producing 500 pulses per revolution will be hard work for an Arduino. Keep in mind that the pulses for the different motors are very unlikely to be conveniently spaced relative to the pulses for the other motors. You could get all 6 pulses within the same microsecond. Also keep in mind that detecting the pulses is only part of the speed control process - you need to have enough CPU capacity left over for the PID speed control.

My first thought is to use a separate microprocessor for each motor - an Attiny should be adequate.

This link has code that I extracted from a program I use for speed control for a small DC motor.

...R

That's true, I didn't think about the encoders sending pulses at the same time, thanks for that. So that definitely underlines the need for a dedicated board for each motor. I forgot to say that I would also need to know the angular position of each motor for collision detection between the elements driven by the gears... Plus a PID speed control or similar. I'll take a look at Attiny boards.

You should really consider reducing the number of pulses to one per rotation. That's more than enough for speed control. In your case that'd be about 500 pulses per second, or 3,000 per second for the six motors. That's a much more sensible number.

If you bring it down to 100 pulses per second you will still have more than enough pulses left.

wvmarle:
You should really consider reducing the number of pulses to one per rotation. That's more than enough for speed control. In your case that'd be about 500 pulses per second, or 3,000 per second for the six motors. That's a much more sensible number.

It does not deal with high risk that on some occasions a number of the motors will produce their pulses at what amounts to the same time. That means that pulses will be missed. And it seems to me likely that the close coincidence of pulses is likely to last for several revolutions. Indeed it could last for hundreds of revolutions if the motor speeds are close to each other.

...R

I would be using 3 arduino DUE (~ $15 * 3), one for each group of 2 DC motors and their respective encoders because this board can handle a very high number of pulse edges with its 2 hardware quadrature decoders (see example sketches in the DUE sub forum ---> up to 42000000 edges /s).

The PWM frequency is easy to vary inside the loop() with the appropriate PWM registers (see example sketches in the DUE sub forum --> up to 84MHz).

I would be using the ARM DSP library for PID functions:

https://www.keil.com/pack/doc/CMSIS/DSP/html/group__PID.html

No need to miss pulses, even when they're arriving at the same time.

Six motors, six pins, that's one PORT.

Pin change interrupt on that PORT.

Every time you get the interrupt read the register; check the six bits of interest (OR operation against the previous value of the register); count those that changed. Write code so it can be more than one. The interrupt will fire on both falling and rising edge; so you count pulses double. Shouldn't be an issue.

Then in a separate routine check the speed (number of pulses counted) maybe 10 times a second, and adjust if needed. Something like this should do for the ISR:

ISR() {
  newPinState = PORTC; // pins A0-A5 are PA0-PA5
  changedPins = oldPinState | newPinState;
  oldPinState = newPinState;
  for (uint8_t i = 0; i < 5; i++) {
    encoder[i] += bitRead(changedPins, i);
  }
}

I picked PORTC so you still have all six PWM pins available for the motor controls.

wvmarle:
No need to miss pulses, even when they're arriving at the same time.

Six motors, six pins, that's one PORT.

Pin change interrupt on that PORT.

My first thought was that this is a good idea.

But then it occurred to me that the first pin-change interrupt will prevent the others being detected until the first one is dealt with.

Or. perhaps, more specifically any change to PORTC after the line

newPinState = PORTC;

will be missed if it happens before the ISR completes.

...R

It'll be picked up when another pin changes. There's bound to change one again before the missed one changes back.

E.g. bit 0 changes, and is dealt with and counted.
As the ISR runs bit 1 changes, and does not trigger a new interrupt.
Then bit 2 changes, triggering a new interrupt. By now both bit 1 and bit 2 have changed since the latest read of the PORT register, both are counted.

As long as the duration of the pulses is the same (that's what you're trying to achieve at least with the controls) you don't miss pulses, they're just counted a little later sometimes, but that's OK. If checking the speed every 100 ms there have been some 650 pulses per encoder. One more or less won't make a (noticeable) difference for the controller.

But now I'm not too sure about the second step: the missing of an interrupt. This is as I understand it, hope it's correct: if an interrupt happens while an ISR for another interrupt is executed, it gets queued and will be dealt with the moment the first ISR is completed.

Now what if a new interrupt of the same comes in as the ISR is running already? That is for pin change interrupts totally possible. Basically an interrupt sets a bit in a register, a bit that gets cleared when the interrupt is handled. If that bit is cleared the moment the interrupt vector is called, it should be possible for a new interrupt to come in and be queued, so the ISR is run again instantly. But if that bit is cleared only upon completion of the ISR, the new interrupt is indeed lost.

wvmarle:
Now what if a new interrupt of the same comes in as the ISR is running already? That is for pin change interrupts totally possible. Basically an interrupt sets a bit in a register, a bit that gets cleared when the interrupt is handled. If that bit is cleared the moment the interrupt vector is called, it should be possible for a new interrupt to come in and be queued, so the ISR is run again instantly. But if that bit is cleared only upon completion of the ISR, the new interrupt is indeed lost.

Considering the low cost I would prefer to use a few additional microprocessors so I don't have to worry about all that stuff. YMMV.

...R

Robin2:
Considering the low cost I would prefer to use a few additional microprocessors so I don't have to worry about all that stuff.

That is indeed probably the most practical thing to do.

Doesn't make the question any less interesting.

Thank you very much for the interesting discussion.

wvmarle:
No need to miss pulses, even when they're arriving at the same time.

Six motors, six pins, that's one PORT.

Pin change interrupt on that PORT.

Every time you get the interrupt read the register; check the six bits of interest (OR operation against the previous value of the register); count those that changed. Write code so it can be more than one. The interrupt will fire on both falling and rising edge; so you count pulses double. Shouldn't be an issue.

I don't understand why having a single PORT on a group of pins would make the task more tractable that having a single PORT for each. In the first case, it's a single PORT with ISR triggering 6 times faster than a PORT in the second case, but in the second case there are 6 of them. Looks like the real time requirement of the board would be close, isn't it?

Robin2:
It does not deal with high risk that on some occasions a number of the motors will produce their pulses at what amounts to the same time. That means that pulses will be missed. And it seems to me likely that the close coincidence of pulses is likely to last for several revolutions. Indeed it could last for hundreds of revolutions if the motor speeds are close to each other.

Anyway it's true that reducing the number of pulses per revolution would help in any case by reducing the probability to miss a pulse, but quite a pain to change in the hardware as the encoders are very small and well packed. Would need a bit of work to adapt something on the motor shaft then. And at the end, if there's still a probability to miss pulses, then would be better to have a dedicated board for each motor. More boards to talk to to send the desired speed for the motors, but not an issue, simply more complexity.

cgirerd:
simply more complexity.

Complexity takes different forms.

...R

cgirerd:
I don't understand why having a single PORT on a group of pins would make the task more tractable that having a single PORT for each.

Well, a single PORT for each pin won't work as the ATmega328P (as used on the Uno) has only three of them. PORTB, PORTC and PORTD.

With all pins within a single PORT it makes reading all of them a single instruction operation: just copy a byte from one memory address to another. Likewise it makes looking for changed pins a single instruction operation: a simple logic and operation between two byte values. So within a few clock ticks (at 62.5 ns per tick) you have figured out which of the six pins have changed since the last time you checked.

With connections spread out over different PORTs this wouldn't be as efficient.

wvmarle:
Well, a single PORT for each pin won't work as the ATmega328P (as used on the Uno) has only three of them. PORTB, PORTC and PORTD.

Yesh, but could use an Arduino Due for instance that has all of its digital pins supporting ISR.

wvmarle:
With all pins within a single PORT it makes reading all of them a single instruction operation: just copy a byte from one memory address to another. Likewise it makes looking for changed pins a single instruction operation: a simple logic and operation between two byte values. So within a few clock ticks (at 62.5 ns per tick) you have figured out which of the six pins have changed since the last time you checked.

With connections spread out over different PORTs this wouldn't be as efficient.

Ok, so even if this port would be triggered 6 times as often as if it were 6 different ports, it is still more efficient... Will go with a board for each motor anyway as there are still some pulses that could be missed as discussed previously. I have no knowledge of existing boards but Robin2 advised the use of Attiny boards, so I'll take a look at those ones.

The ATtiny is small as the name says - afaik they're not available on convenient development boards such as the Arduino is. Not too hard to work with once you get a hang of it.

The USBasp programmer is very convenient. You will need to build a PCB for each ATtiny, if only to break out the ISP header.