How to effectively process this speed sensor as feedback for a PI controller? - Page 1
Forum section: electronics / general

author: XaviPacheco
author_rank: Regular Contributor
timestamp: November 19, 2018, 08:31:25 pm
content: My speed sensor gives me 6 pulses per revolution. I plan to measure (and control) a speed range between 200 and 4000 RPM. The way I'm measuring the speed is this:A microcontroller pin is configured as Input Capture Mode. Then it's going to trigger an interrupt every rising edge of the signal and calculate the period, so the frequency. My speed will be:RPM = (Wheel_Frequency*60)/6;Now, the speed measurements are right because I also used a digital tachometer to confirm them. The issue is that the microcontroller speed readings are not always consistent. That is, let's say the speed should be 300 RPM, then the microcontroller readings range from 280 to 320. In other words, it does not keep at 300 RPM. These tests are being done under No load.The PI controller needs a good feedback. So, what I was thinking is to average a certain number of readings and use it for the controller.The PI controller will be executed at a fixed loop time, let's say every 10 ms. But here is the point where I wanted to reach, so you could give me a hand.1- How do I choose a convenient update rate for the PI controller according to my "low feedback rate" for low RPMs? The PI routine reads the speed, so the PI update rate would be the same as "the speed sampling rate".2- Should I do the averaging inside the Input capture interrupt?My wires are kind of crossed, any input that help clarify my mind in appreciated.

author: IanB
author_rank: Super Contributor
timestamp: November 19, 2018, 09:03:19 pm
content: I think what you are seeing is jitter. You cannot measure the interval between two pulses accurately enough to get a consistent speed measurement.Definitely you need to do some averaging or smoothing. I would suggest you try to come up with some kind of rolling average over, say, 10 or more pulses. A simpler way would be to use a first order filter, where instead of saying "new speed = calculated value" you would say "new speed = 0.1 x calculated value + 0.9 x old speed". Your 0.1 is your filter factor that you can adjust by trial and error until you get a good balance between lack of jitter and fast enough response.Your update rate on the PI controller needs to be fast relative to how fast the speed can change. Work out the time constant of the open loop by doing a step change on the motor input and then make your PI sampling interval faster than this (maybe 5x faster, perhaps).

author: XaviPacheco
author_rank: Regular Contributor
timestamp: November 19, 2018, 09:09:33 pm
content: Quote from: IanB on November 19, 2018, 09:03:19 pmWhat do you think of using a circular buffer for the averaging?

author: IanB
author_rank: Super Contributor
timestamp: November 19, 2018, 10:21:27 pm
content: Quote from: XaviPacheco on November 19, 2018, 09:09:33 pmA circular buffer would enable a rolling average calculation, but my feeling is that an input smoothing filter might give better results and would be simpler to implement.The practical result of using a rolling average is that instead of measuring the speed over an interval of one pulse you would be measuring the speed over an interval of N pulses. This wouldn't eliminate the jitter, just reduce it. For instance you were getting measurements between 280 and 320 rpm over one pulse interval. If you measured over 10 pulse intervals I think you would get measurements between 298 and 302 (the spread would be reduced from 40 to 4).A good thing about the first order filter alternative is that it does a weighted average over all prior history and not just the last N intervals. This gives it a very long "tail" which has a strong stabilizing effect on the measurement. In practice, noisy measurements are nearly always passed through some kind of smoothing filter before subsequent processing.Maybe you could try both and see which one looks better in practice?

author: XaviPacheco
author_rank: Regular Contributor
timestamp: November 19, 2018, 10:44:27 pm
content: I haven't worked with embedded filters before. I would like to try it as you suggest. If you have a pseudocode or reference I can read, I will appreciate it.

author: floobydust
author_rank: Super Contributor
timestamp: November 19, 2018, 10:52:16 pm
content: Look at the mechanical system too.I find a few kinds of jitter with a hall sensor and sheave having six spokes as an example.Spacing (casting) between spokes is not perfect so input captures wobble i.e 500, 490, 510, 500, 495, 505 usec. within one revolution. I would window-average a multiple of one revolution (N #spokes) to get rid of that, to cancel it out.Like a car engine, over one crankshaft revolution the speed pulses up and down according to each cylinder's compression/firing stroke. The flywheel helps but with a high resolution i.e. 60 ppr sensor, you can see the crank velocity has ripple which is important if you are doing precise ignition timing.If you have a gearbox or imperfect (warped) belt, there can also be speed wobble between a few revolutions. It depends on the mechanical load.

author: IanB
author_rank: Super Contributor
timestamp: November 19, 2018, 11:27:48 pm
content: Quote from: XaviPacheco on November 19, 2018, 10:44:27 pmThe first order filter is really simple to implement. At its simplest it could be like this:Maintain a value called "filtered speed". Each measurement interval, compute the instantaneous speed as you are doing now--call it "speed". Then you do a speed update like this:(new filtered speed) <== (filter) x (speed) + (1 - filter) x (last filtered speed)Here "filter" is your filter factor between 0 and 1. Set filter to 0 and the filtered speed will never change. set filter to 1 and there will be no filtering. Set filter to some value between 0 and 1 to get filtering. Try filter = 0.1 as a starting point and adjust to taste.You can relate the filter constant to the time constant of the filter by using an equation like this:(filter) = (dt) / (dt + tc)where "dt" is your sample interval and "tc" is the time constant of the filter.

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: November 20, 2018, 12:23:00 am
content: Try an exponential filter such as this one:Code:/** ExponentialFilter()** Apply an exponential filter to the input.** y(k) = a * y(k-1) + (1.0 - a) * x(k)** Where a is a smoothing constant that ranges from 0 to 1. Larger values of a result in heavier filtering.* When a = 1, the new input is ignored. When a = 0, no filtering is done--the input is passed through to the output.**/floatExponentialFilter(float newVal, float smoothFactor, float previousVal){float y;y = (smoothFactor * previousVal) + ((1.0f - smoothFactor) * newVal);return(y);}

author: XaviPacheco
author_rank: Regular Contributor
timestamp: November 20, 2018, 01:11:44 am
content: Thank you. I will try them and report back.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 20, 2018, 04:02:05 pm
content: Jitter is,a) mechanical; youarereading the speed correctly! It just varies quickly. Even electric motors don't run perfectly smoothly. Try adding a heavy flywheel.b) electrical; edges are slow, and there is insufficient hysteresis - i.e., a significant portion of time, the signal is somewhere around 50% voltage - combine this with noise, and the actual trigger point varies randomly around the true trigger point.c) digital; you don't have enough resolution in your MCU timer.So, the choices are:1) Average the jittery signal,2) Measure over a longer time interval to begin with,3) Don't overreact to what the measurement says. If the speed error is real, it's still there with the next cycle, and the next, and the next, if you react carefully.Funny thing - all three ways to deal with it are almost or completely equivalent.You may realize that averaging the calculated speed over the last few pulses is exactly equivalent to just completely skipping the middle pulses and calculating over a longer physical angular distance travelled. The longer the time and travel you measure, the better the accuracy. It's completely equivalent to sum up and average adjacent readings to average out the errors, or just measure the longer distance to begin with. Whichever is easier to implement.You may also realize that averaging the measured process value isn't too different from just using smaller PID coefficients to make the loop respond slower.If your PID loop is unstable because of such small jitter in the speed value, the chances are, you should just retune it to be stable.But, OTOH, if your speed measurement currently haswaymore bandwidth than you actually need for the control, then filtering it first is wise - i.e., don't feed the PID with high-frequency data it doesn't need.Efficient and simple implementation of a cumulative moving average filter is like this:static uint32_t speed_flt;speed_flt = ((measured_speed<<8 ) + 255*speed_flt)>>8; // veeeery slowly changing... or:speed_flt = ((measured_speed<<8 ) + 15*speed_flt)>>4; // fairly quick, or:speed_flt = ((measured_speed<<8 ) + 3*speed_flt)>>2; // just a little bit of filteringThen use as (speed_flt>>8 ) to shift back to the original scale.The memory and CPU footprint for such a filter is ridiculously small, and you can easily filter things in minute or hour timescale if you need to. Worst case, you need to go to 64 bit integers and it's still fast and small.

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 20, 2018, 04:34:39 pm
content: True. Even the motor will act as a low-pass filter to the PWM control, so any additional filtering of the inputs may not be necessary. A well tuned PID will probably do the trick.Would be interesting and useful as a validating tool to use an external tachometer so you can validate the actual behavior of your control loop.

author: XaviPacheco
author_rank: Regular Contributor
timestamp: November 20, 2018, 04:46:01 pm
content: I implemented the exponential running average filter and the results are better, no perfect though. I just started to tune the PI controller. With Kp=1 and Ki=0 the overshoot/oscillations is excessive. I will go around 0.1 and 0.6 for Kp to see the reaction. Then I will adjust Ki.Also, my pid loop is running inside a 20ms interrupt. Still not sure about this amount of time.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 20, 2018, 05:44:36 pm
content: If you are new to PID controllers - heck, it took quite some years for me to actually understand the literature, and the math which always seemed too complex even though it's actually simple - I recommend you start with a super simple I controller.Why I controller? I think it looks most intuitive. We have this mental state - the position of "gas pedal". If we are going too slowly, we push it further - if we go too fast, we pull it a bit back. An I controller can be implemented in a typical C oneliner, without the PID terminology, without the programmer even knowing they are making an I controller:PWM += speed_setpoint - measured_speed;That's an I controller!Now, to adjust how fast it reacts, this becomes, intuitively:PWM += something * (speed_setpoint - measured_speed);Tuning this is intuitive and straightforward. If it's oscillating randomly, you lower the multiplier until it works.And it works very well if you use really small multipliers. It's slow as hell, however.This is a good form to represent an I controller, because it shows the necessity - and triviality - of implementing wind-up limitation:PWM += something * (speed_setpoint - measured_speed);if(PWM > BIGGEST_ALLOWED_PWM_VALUE) PWM = BIGGEST_ALLOWED_PWM_VALUE;else if(PWM < 0) PWM = 0;This is a no-brainer - no one expects me to explain why these limits are needed. But when you turn this into the actual PID mathematical representation, this operation moves elsewhere and needs extra scaling factor so that the end-result is still the same - theneedfor this limit is still there, but now, suddenly, this has become an "advanced topic" "second year class" like problem, and this being the case, integrator wind-up limitation is not discussed in the basic PID primers!Now, P is added simply to bring the speed to the system.Alone, P is seldom usable unless you know how the system behaves physically, in an exact way. Equivalently, a P controller is: "I know exactly that I need to press the gas pedal to this exact particular absolute location to correct my speed." Since this is impossible to know exactly, this way of thinking gives you the rightballparkof the control very quickly - and without thinking about the past! - but alone, it results in the motor running at the right ballpark speed only, which can be easily off by, say, even 50%, even if you tried to tune it. Even the tiniest amount of I term will automatically fix any steady-state error. So, in PI, you get to the ballpark quickly, and then to the correct value more slowly, so to say.I don't know how people typically tune their PID controllers - according to the literature, "slowly, in a difficult way, with complex equations all over the place", seems the correct answer. But, I recommend that you implement any kind of interface to adjust PID coeffs on-the-fly. An UART serial console with simple keypresses, such as A/Z increase/decrease P, S/X increase/decrease I, D/C increase/decrease D, and a continuous printout of the current values, will do. Add a button to change between a few typical setpoints, since a quick setpoint change is often easier to test the transient response with, compared to a load change. This way, you'll be able to learn quickly how everything works. I recommend something like this as well:#define TRACE_LEN 128volatile int dbg_tracevolatile int trace_at;control_isr(){int err = setpoint - measured;stuff...;if(trace_at < TRACE_LEN){dbg_trace = err;trace_at++;}}void main_program(){if(cmd == 't'){trace_at = 0; // start tracingsetpoint_change();while(trace_at < TRACE_LEN) ; // wait until fullfor(...) // print the table to uart as comma-separated list you can copy-paste in Excel for comparing different traces, seeing min/max, plotting a graph, etc...}}You seem to be pondering about the interval you do the calculation. Do it every time you have some relevant input data to process, or on a similar timescale. This is non-critical - you just need to scale the P,I,D terms linearly if you change your timebase.

author: XaviPacheco
author_rank: Regular Contributor
timestamp: November 20, 2018, 06:13:35 pm
content: Yeah, it's all about tuning. With Kp = 0.25 and Ki = 0.035 the system has improved. I still have little oscillations, but better than before.

author: XaviPacheco
author_rank: Regular Contributor
timestamp: November 20, 2018, 06:56:55 pm
content: Manual tuning is a headache. My sistem has improved, but still is not stable at low speeds.

author: pelule
author_rank: Frequent Contributor
timestamp: November 20, 2018, 06:59:33 pm
content: I would check the pulse rate (speed) with a counter/frequency counter first. To get confident the pulse rate is a expected.If the pulse rate is correct you are confident, its caused by the signal quality and/or your program./PeLuLe

author: Siwastaja
author_rank: Super Contributor
timestamp: November 20, 2018, 08:10:07 pm
content: Quote from: XaviPacheco on November 20, 2018, 06:56:55 pmManual tuning is absolutely necessary so that you learn in practice how the PID works. It may take a few hours of total learning in a few different kind of controller projects to get the grasp. Having interactive controls is everything so that you don't generate the headache by wasting time operating a slow/tedious parameter change interface (such as code recompilation).If your system is unstable, turn all P,I,D completely down to zero. Bring either I or P slowly up and see what they do on their own, first with low, then higher levels. Implement integrator windup limit, possibly light up a LED every time the integrator value is saturated to the maximum or negative maximum, so you get a grasp how quickly it's being limited.Implement input data sanity checking. For example, a single totally out-of-limits measurement can mess up stability. Either ignore it, or error out.Measuring low speeds can be tricky, since you need to wait for a significant time between the pulses. You need to decide a certain limit where you time out and decide that the speed is "below minimum measurable". If you wait for too long (a few hundred milliseconds can be quite long already), sudden changes are problematic - when the speed is slowing down, speed information updates get less and less frequent, and your feedback loop is working with the "old" information (of higher-than-actual) speed measurement for too long - causing it to slow down even more, causing even slower updates! On the other hand, if you timeout too early, you simply cannot measure low speeds.

author: rstofer
author_rank: Super Contributor
timestamp: November 21, 2018, 12:23:23 am
content: It would seem to me that averaging would add inertia to the system. That is, a sudden change in speed won't be noticed until the average changes substantially and that might be some relatively long time later. Perhaps the D term can notice the smaller change and start to correct for it before the average drops completely.More than you ever wanted to know about PID controls:https://www.youtube.com/user/ControlLectures/videosThe Ziegler-Nichols methods of tuning are pretty straightforward - watch for oscillation.http://faculty.mercer.edu/jenkins_he/documents/TuningforPIDControllers.pdfThere are MANY methods for tuning, Google has them all!

author: Totoxa
author_rank: Newbie
timestamp: November 21, 2018, 01:21:04 am
content: This article may be interesting for you:https://www.embeddedrelated.com/showarticle/158.phpQuote from: XaviPacheco on November 19, 2018, 08:31:25 pm

author: hamster_nz
author_rank: Super Contributor
timestamp: November 22, 2018, 08:14:22 am
content: Have you considered that you might be targeting the wrong thing? Rather than targeting the time between pulses, you might want to target the number of pulses per second?If you do that, then your interrupt increments a counter, and then every fixed time window (e.g. maybe 1/10th of a second) see how much that counter has changed over the last time.Code:... run every 100 ms..../* sample the counter - counter should be "volatile"*/new_count = counter_incremented_by interrupt;/* See how much it has changed */change = new_count - last_count;/* Remember for the next check - last_count should be a "static"*/last_count = new_count;... then use "change" to update your pwm duty cycle....You then want to have your system control so that "change" averages to the number of pulses you want per sampling interval.It may also be less error prone, as timing between interrupts requires an interrupt to make things happen - if the motor stops turning, you never update the speed.However, counting pulses over a time period still works if you never get an interrupt - you just get a count of zero and you learn that the motor has stalled.It really depends on what you are controlling. If it is a 3D printer's drivechain where absolute positioning is important then you may need to think differently than if it is a propeller or a drill where you want to control speed.