Skip to content

Quadrature encoder example: too many ticks? #416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
holmbuar opened this issue Aug 28, 2023 · 8 comments
Closed

Quadrature encoder example: too many ticks? #416

holmbuar opened this issue Aug 28, 2023 · 8 comments

Comments

@holmbuar
Copy link

I'm getting what I believe is way too many encoder ticks from my DC motors. I'm referring to my RPi forum thread, which understandably has vaned off in attention.

TLDR: I hooked up my DC motor encoders to the Pico, and turned one revolution by hand. It consistently yields 41,500 ticks for one output shaft revolution. Motor data: max 240 RPM, gear ratio 1:34.

https://forums.raspberrypi.com/viewtopic.php?t=355234

@lurch
Copy link
Contributor

lurch commented Aug 28, 2023

ping @pmarques-dev who contributed this code in #126

@pmarques-dev
Copy link
Contributor

Sometimes the encoder counts are specified in terms of "full steps", but the PIO code will count a step on any transition, so it ends up counting 4 steps per count. Note that is doesn't mean that any small noise will produce spurious counts, as a noise in one of the phases will just make the count increment and immediately decrement, as the PIO code implements the proper state machine that can count bidirectionally on every step.

I would leave the max rate parameter at zero, to let the PIO run at full speed to make sure there are no aliasing issues. The only advantage of setting it to a lower value is to save some power, especially on battery powered devices.

From your numbers, 41500 steps per output shaft revolution -> 41500 / 34 = 1220 steps per motor revolution, which would correspond to a 300 counts per turn encoder attached to the motor. If it's a high quality motor, that is not an unusual. The kind of cheap DC motors with gears we use in low cost robotics kits will have 11 counts per turn or similar. Also, at max speed of 240 RPM, that would give around 166.000 steps per second, which the PIO code should be able to handle just fine.

If you still can't get that to work, I would suggest trying a couple of things:

  • use the actual code in the example and check if the printed deltas make sense. Note that the PIO code makes the step count wrap around the 32 bits and the computation of the delta needs to be done carefully to make use of two's complement arithmetic
  • actually look at the signals with an oscilloscope. If the encoder output is open collector, something like a too high pull-up resistor may make those signals not rise quickly enough and be read at some intermediate levels

@holmbuar
Copy link
Author

holmbuar commented Sep 2, 2023

I found the motor info here. The manufacturer claims that the encoder yields 360 counts per revolution, whilst I measured ~305 CPR by hand. Your PIO code information and estimates check out, thanks!

The deltas do work in my controller. I'm not familiar dealing with two's complement arithmetics, is there any "risk" of dividing the transition values by 4, just for the sake of steps-to-counts conversion?

@pmarques-dev
Copy link
Contributor

As long as you take the full delta and then divide by 4, you should be alright. Just don't divide the output of the PIO code by 4 before taking the delta, as that would mess up the computation.

Note that by doing that you're losing some resolution, which is probably ok in your case since you have plenty of that, although you don't really need to. But in general, especially with low cost encoders, you'll want every step possible, since otherwise at low speeds you'll get a larger quantization error in the speed (or position) estimate. I have even submitted a new pull request with a new version that uses not only the steps but the timing of the transitions to get a good speed estimate even at very low "steps per sample" regimes.

In any case, my advice would be to not bother dividing by 4 and instead compute the floating point constant that converts from steps into whatever unit you're going to work with in the end and just multiply the delta by that. For instance, say you want the output of your system to be in rpm: one turn 360 * 4 * 34 = 48960, so if you get a count of 48960 in one minute you have 1 rpm. Since you are measuring once a second, you the count to be 1/60 of that per sample to be at 1 rpm, so 48960 / 60 = 816 steps per sample is 1 rpm.

So if you multiply the delta you get by (1.0f / 816) you get a floating point number in rpm directly from the step delta. The compiler will do that computation at compile time for you. You can even write (1.0f / ((360 * 4 * 34) / 60)) if you want to document how you arrived at the number in the code. Something like:

float speed_rpm = delta * (1.0f / ((360 * 4 * 34) / 60));

That is just one floating point multiplication to get you from the PIO output to the unit you want. Note that this will work for any other unit you want: radians per second, meters per second (e.g. this is a wheel of a robot), etc.

@holmbuar
Copy link
Author

holmbuar commented Sep 3, 2023

Thanks so much for your advice and effort. I want to compute radians per second, 10 times per second, and chose

double angular_velocity = delta_steps * 2 * M_PI *  (1.0 / ((48960)/ 10));

@holmbuar holmbuar closed this as completed Sep 3, 2023
@pmarques-dev
Copy link
Contributor

I would actually write that expression like this:
float angular_velocity = delta_steps * (2 * M_PI * (1.0 / (48960.0 / 10)));
i.e., with the constant part written completely inside parenthesis to make sure the compiler is allowed to compute it at compile time. Without the parenthesis, the compiler will have to multiply delta_steps by 2, then by M_PI and then by a constant, leaving 3 multiplications to be done at runtime. Although mathematically they seem the same, in terms of numerical precision they may produce slightly different results and the compiler is not allowed to optimize in that case.
Also, the (48960 / 10) part is rather risky, because although in this case the reminder is zero, using floating point here would be more safe in general (i.e., change 48960 to 48960.0 to force the compiler to use floating point).
And last, unless you really need the extra precision from the double type, I would stick with "float" type for performance reasons. Note that everything inside the parenthesis will be evaluated at compile time, so it can use doubles to get the best possible constant.

@holmbuar
Copy link
Author

holmbuar commented Sep 10, 2023

Thanks much for those additional compile time and precision details I was not aware of, really appreciate it. The only risk I see to this approach, is to change PID loop time interval, and forget to change the denominator :)

@pmarques-dev
Copy link
Contributor

just define it as a constant and use it both to set the timer and in the expression. Something like:

#define PID_FREQUENCY  10    // control frequency in Hz

....
float angular_velocity = delta_steps * (2 * M_PI * (1.0 / (48960.0 / PID_FREQUENCY)));
...

or even:

const int PID_FREQUENCY = 10;

in case you're not a big fan of pre-processor defines. This is still all resolved at compile time anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants