Monday, January 7, 2019

Electrically driving a mechanical speedometer from a remote wheel sensor using a motor and PID loop

A friend of mine [Dan] is building a side-by-side 4 wheeler more or less from scratch using a motorcycle engine and transmission, an instrument cluster from a motorcycle, a home-built transfer case, electronic-assist steering rack from a Honda civic and a lot of custom welded aluminum.  As you might expect, this is a long-term project and has been in the works for several years, now.

Some time ago I helped him reverse-engineer all of the wires that connect to the instrument cluster and how they are to be connected to everything else, but one thing continued to vex him:  How to make the existing speedometer read correctly.
Figure 1:
The side-by-side as it was about a year ago.  It is much farther along, now.
Click on the image for a larger version.

While it may have been possible to do so with the original-type hardware, he chose not to use the existing mechanical (spinning cable) coupling of the speedometer to the drive train - and that didn't even take into account the need of providing a precise gear ratio to make everything read correctly.  Being the sort of person that I am, I suggested that I could make this happen electronically with a bit of microcontroller magic:  I'd just need pulses from the drive train.

To that end, he added eight steel screws to an aluminum disk that was on the drive shaft output to magnetically couple the sensor meaning that I would get eight pulses per revolution of the drive shaft.  Mathematically, this meant that with the 25 inch (635mm) diameter wheel and the 3.75 differential ratio that one would get approximately 6.72 pules per second per mile per hour of vehicle speed - good enough to measure even fairly slow speeds.

The trick was to be able to drive the cable input of the mechanical speedometer with some sort of motor.  While the first guess might be to use a stepper motor for this, that guess would be wrong:  At a speed of 80 miles per hour (on the high side of the speed range at which this would ever be driven!) it was determined that a motor rotation of more than 3000 RPM would be required, a bit on the fast side for a typical stepper motor - and even though it is likely that one could make it work, there was an alternative:  Employ a DC motor with a built-in tachometer winding.

As is the case with many things, EvilBay had the answer:  A Barber-Colman FYQM 63160-25-3 brush-type DC motor was procured.  After it arrived, this motor was analyzed and it was determined that the application of 12 volts produced a speedometer reading exceeding 120 miles-per-hour and also that one rotation of the shaft produced approximately 7.85 pulses (e.g. 60RPM = 7.85Hz).  I have no idea how that fractional number of pulses per rotation occurs unless there is some internal gearing for the tachometer, but I can't detect any evidence of "gear meshing".

Dan was able to attach the motor to the speedometer with an adapter that he'd machined so I went to work on constructing a circuit that would drive it - see below.

Figure 2:
Schematic diagram of the described circuit.
Click on the image for a larger version.
Circuit description:

Pulse amplifier/detector

The operation of the circuit itself is fairly straightforward.  Starting with the pick-up coil inputs, let's look at the "Motor Tach In" circuit:  The other circuit for detecting the pulses from the wheel sensor is identical and operates in the same way.

The motor has an internal pick-up coil that outputs an AC signal that, to some extent, has an amplitude that is proportional to its rotational speed, but we are interested only in the frequency of this signal.  This coil (and the one on the wheel sensor) are "floating" (e.g. no ground/chassis) reference which is helpful to minimize the pick-up of electrical noise.  Via R101/R102, the input signal passes to a pair of back-to-back diodes (D101/D102) that limit the amplitude of this signal to about 1.2 volts peak-to-peak, and this signal is then filtered/smoothed a bit with capacitor C101.

R103-R106 and U1d form a "difference" amplifier - that is, its output is proportional to the voltage difference between the two wires from the pick-up coil and somewhat less sensitive to common-mode noise on the leads.  Having a fixed gain of about 5, this stage also amplifies the signal, yielding about 6 volts peak-to-peak maximum.  At very low speeds (several dozen RPM) the output voltage of the pick-up coil is very low - only a few 10s of millivolts - so this amplification is necessary.

The next stage (R107, U1a, R108) forms a Schmidt Trigger.  This is a short of amplifier with very high gain, but it has a built-in hysteresis meaning that there a sort of "deadband" at the center-crossing voltage:  Once the output goes "high", it takes a lower voltage to make it go "low" again than was the threshold to make it go high.  This circuit prevents "bounce" and oscillation about its threshold and once set "high" or "low" it is stuck there until the input signal crosses the threshold.

The result of this that when the motor is turning, a square wave appears on the output of U1a at the same frequency as the AC signal coming from the pick-up coil - but the amplitude at this point is constant, regardless of the speed.  The output of the Schmidt trigger circuit is then applied to Q101 via several resistors.  This simple circuit is used to act not only as a level shifter, converting the higher-voltage signal to a 5 volt signal for U2a, but it also "speeds up" the on/off transitions for the circuit that follows.

The signal from Q101 is applied to U2a which is an XOR gate wired as an inverter and buffer which is then applied to XOR gate U2b.  As can be seen, one of the inputs of U2b is tied directly to the output of U2a, but the other one passes through a resistor (R111) which has, on its other end, connected a capacitor to ground (C102) which takes a finite time to charge compared to the other input.  The result of this is that at the instant that the output of U2a changes state, the input with the resistor/capacitor combination will lag slightly, causing the output of U2b to go high for a short period before it goes low again as the capacitor is charged.  When this circuit was first constructed Q101 was omitted, but the relatively slow slew rate from the output of the op amp didn't permit the U2b pulse generator circuit to work properly - even when it had been buffered by U2a - the result being that each logic transition had multiple pulses, each being counted by the microcontroller and thus skewing the results.

The result of this is that there are two pulses for each cycle (one on the rising edge from U1a and another on its falling edge) that is input from that motor sense tachometer winding, effectively doubling the pulse rate:  Because the microcontroller's counter input is sensitive to only the rising edge of the applied signal, it will now count twice as many pulses as it normally would.  At very low speeds this increased "temporal" resolution as there will now be more pulses to count over the measurement period to determine the rotation speed.  This output pulse is very narrow - only about 2 microseconds - but the microcontroller has no problem "seeing" it with its edge-triggered input.

Microcontroller

The heart of this circuit is U3, a PIC16F1847 microcontroller.  This device was chosen because it has a wide variety of support hardware - including several externally-clockable timers and a built-in PWM (Pulse-Width Modulator) generator.  It also has a built in system clock, eliminating the need for an external crystal as well as a hardware UART so that speed information can be sent from this device on a serial data line in case we want to supply this to another device.  For speed conversion, the absolute clock rate of the processor is irrelevant because we are doing a ratiometric conversion, but any differences in the CPU clock do effect the reading of the absolute speed that might be produced - but a couple of percent variation is perfectly acceptable.


The pulses from the motor that drives the speedometer are applied to the "RB5" pin which is configured to be the input of the PIC's 16 bit hardware counter, "Timer 1" and every time a pulse occurs, this internal counter increments by one.  Another hardware timer built into the PIC is configured as an interrupt which looks at the contents of this counter 7.63 times each second and it is by looking at the difference between the count values of successive interrupts that the rotation rate can be directly measured.
Figure 3:
The completed (and working) PID controller board.  The motor's
tachometer is connected via the yellow wires (bottom-right) with
the wheel speed sensor's input (from a sensor that "sees" 8 steel bolts
coupled to the drive shaft)  being applied to the two connects
just to the left.  The red and blue wires (top left) go to the
motor's windings.
Click on the image for a larger version.

The input from the wheel sensor is very similar:  Its output pulses are applied to the "RA4" pin which is configured to be the input to the PIC's 8 bit hardware counter, "Timer 0", which also increments on each applied pulse.  Using the same 7.63 Hz interrupt the counts from this timer are used to measure the rotation rate of the the wheel via the sensor that looks at the drive shaft.

Pin "RA3" is configured to use the microcontroller's internal PWM generator.  When this pin goes high, transistor Q401 is turned on, applying power to the motor that drives the speedometer and by varying the pulse width of this signal, more or less power can be applied to the motor.  Surrounding Q401 and the motor is a network of resistors, capacitors and a diode:  These components suppress "spiky" components of the back-EMF of the motor to reduce electrical and radio-frequency noise as well as protect the components from potentially high-voltage transients that may result from the PWM switching.

Because we know the rotation rate of the motor via its tachometer coil, we can, via software, set the speed of the motor precisely - and because we also have the input from the wheel sensor (via the drive shaft) we can also know how fast the vehicle is moving.  By applying some correction factors in software, we can convert the wheel rotation rate to the motor rotation rate that will give us a correct reading on the speedometer.

Transistor Q501 and its associated resistors comprise an inverter/driver to produce a "pseudo RS-232" signal that carries serial information such as the calculated speed:  This could be used for debugging, or it may be used by some yet-to-be-built module that would use this information.

Finally, components D601, R601 and C601 form a polarity protection and filter network to suppress high-voltage transients that may appear on the vehicle's power supply while C603, C603 and U4 provide a regulated 5 volt supply for both U2 and U3 as well as being a mid-supply reference for U1.

The PID controller:

Perhaps the most complicated part if this circuit is not the hardware, but the software that takes the tachometer output from the speedometer driver motor and adjusts the PWM duty cycle.  To attain a stable and accurate result, closed-loop feedback is required - but there are several things that tend to complicate this task, including:
  • The rotational speed of the motor is not directly proportional to the PWM duty cycle.   The power supply voltage, the temperature of the motor (and the viscosity of its lubrication) and the amount of mechanical loading can affect this.  For example, when the speedometer's odometer "turns over" several digits, the motor loading is increased slightly.
  • The motor itself has significant rotational intertia.  This means that it takes a finite time for the motor to speed up or (especially) slow down when the PWM duty cycle changes.  Because of this delayed response, having some sort of "direct" feedback loop where the PWM is adjusted based on the desired rotation speed and the actual rotation speed will result in "ringing" (oscillation) about the desired value.
The PID (Proportional-Integral-Derivative)  See Ref. 1 controller algorithm is designed to accommodate this problem, being somewhat "adaptive" in that if properly implemented, it can accommodate longer-term errors while being fairly quick to respond and avoiding "ringing".  As the name implies, this controller is somewhat more complicated than a simple proportional controller, but it is still quite manageable in a microcontroller.

In many cases, a PID controller is used for position control where one inputs where a particular axis is supposed to go, using some sort of position feedback, and "output" terms are applied to the motor (or whatever) to cause movement.  In this application there are both "negative" and "positive" results related to the attained position - the positive meaning an overshoot, past the desired position and negative meaning that it isn't quite there yet - and a "negative" output might mean that the drive motor should be reversed.  In this case we have no need for a "negative" output as all we can do is speed up the motor by increasing the PWM duty cycle or slow it down by lowering the PWM duty cycle and waiting for the motor to "coast" down in RPM.

A PID controller in "pseudo" code, assuming floating-point mathematics, is as follows:

// 
// PID control
//
error = desired_speed - actual_speed;
integral = integral + (error * ITIME);
derivative = (error - error_prior)/ITIME;
output = Kp*error + Ki*integral + Kd*derivative + BIAS;
error_prior = error;

In the above:
  • desired_speed - This is the rotation speed that we would like the motor to have.  (If we were moving some sort of actuator arm, this would be used to input the desired axial position.)
  • actual_speed - This is the rotation speed of the motor being driven.  "desired_speed" and "actual_speed" are in the same units.  (If we were moving some sort of arm, this would be the actual position.)
  • error - This is the difference between the actual and desired values.  This number could be positive or negative.
  • integral - This term gets larger and larger in proportion to a constant amount of error and it acts as sort of a "memory" of the amount of error that has occurred over a longer period - which is then used to make the long-term average correct.
  • derivative - This operates on the short term, taking into account the amount of error between the current reading and the previous reading.  This helps compensate for rapid changes that might occur.
  • output - This is the "output" of the loop, the result being proportional in both amplitude and sign of how much correction to apply to make the motor do what it is supposed to do.  In the case of our PWM drive, all we can do is go to zero to cause the motor to slow down, but if we were moving some sort of actuator arm, this value might go negative to make its motor go backwards.
  • BIAS - This is a "baseline" amount when the system first starts.  As an example, if the motor doesn't start turning until the PWM value gets to 5% of maximum, it may take a short while for the loop to "ramp up", but if you set BIAS to a value just below where it starts to turn (or for a typical value the represents an acceptable "starting speed") the initial start-up of system can be quicker.  This value quickly "disappears" in normal operation.
  • ITIME - This is typically referred to as the "iteration" time, but in reality it just represents "how much" response should happen at each iteration.  If the loop is called only occasionally and "output" needs to be a fairly large value for full-speed operation, "ITIME" should be fairly large.  In short, a high number causes quicker response than a lower number if everything else is kept the same as it directly affects how much/little the integral and derivative terms change on each iteration.
  • Kp - This is the magnitude of the "proportional" (the "P" in PID) term and it sets how much a given amount of error will affect the output result.  This is a constant that must be chosen.
  • Ki - This is the magnitude of the "integral" (the "P") and it affects how much the most recent error will affect the output per each iteration.  This constant must also be chosen appropriately.
  • Kd - This is the magnitude of the "derivative" (the "D") and it affects how much the differences between the current and past reading will affect the output per iteration.  This is a constant that needs to be chosen for the application.
In the above, the "integral" and "derivative" terms start out as zero and will "adapt" as conditions change.

Ideally, when output term becomes negative we would apply "negative" acceleration to the motor to make it slow down more quickly, but in our case, our motor is driven only by a single-ended PWM and with the PIC, the PWM is set by a 10-bit value that can go from 0 (no power) to 1023 (full power) - but it cannot go below zero.  What this means is that when the motor is too fast and we wish to slow it down most quickly, all that we can do is set the PWM value to zero to shut off the drive.

Conversely, if the motor could not go fast enough, quickly enough, the output term can grow - theoretically to infinity if our motor were locked and could never turn.  Even during normal operation there may be some cases where the output term gets too large for our PWM hardware:  In the case of our 10 bit PWM hardware we must therefore limit the PWM value to a maximum of 1023.

Code to drive the PWM output within these constraints would be as follows:

if(output < 0)           // trap negative numbers
   output = 0;
//
if(output > 1023) {      // trap numbers that exceed the PWM operating range
   output = 1023;
// 
pwm_value((int)output);  // Convert the floating point "output" value to integer
                         // and set the PWM to that value


Determining the values for Kp, Ki, and Kd.

The PID loop must be "tuned" to obtain the appropriate response for the system in question and this means that the values of Kp, Ki and Kd must be derived.  While it is possible to "fiddle" with these values and find something that will work, this can be very tedious there is a "shortcut" method that will allow one to arrive at useful values more quickly and that procedure is approximately thus:  See Ref. 2
  • Set Ki and Kd to zero:  This will make this a "P" (proportional) loop only.
  • Start with a small-ish value for Kp and increase it until the motor's speed starts to oscillate slightly - that is, it will "hover" around its ultimate speed - and then reduce it until this oscillation just stops occurring or is a very small proportion of the final attained speed.  It is important that when using a motor with a bit of "drag" (as is the case with all motors!) it will never reach its intended speed if Ki is set at zero - and if Kp is increased too much to attempt for it to reach that speed, it will surely oscillate wildly!
  • Now start to increase the value of Ki, turning it into a "PI" loop.  As the value is increased, the desired motor speed will more quickly reached.  Keep increasing the value of Ki until it starts to "overshoot" the desired speed slightly.  If the value is too high the speed will oscillate a bit.  The "ideal" value is one that allows the motor to settle to the desired speed quickly, but doesn't overshoot:  Often times it is permissible for the speed to overshoot slightly and then settle back down to the proper speed without constant oscillation about the desired speed (e.g. "ringing").
  • Now, calculate a starting value of Kd using what we already know using these equations:
    • Calculate PuPu = 1.2*Ki
    • Calculate KuKu = Kp/0.45
    • Calculate KpKp = 0.6*Ku
    • Calculate KiKi = (2*Kp)/Pu
    • Calculate KdKd = (Kp*Pu)/8
  • The above values should result in a PID loop that is "fairly close" to being desirable.  At this point, adjustment of Kd will provide a bit of a "damping" response to help tune out instabilities:  Too-high a value of Kd, the response will be sluggish.
  • The numbers above are just a starting point, but should (in most cases) yield reasonable results.  Increasing Kp will speed the response time, but too much will cause overshoot and instability while too-high a value of Ki will cause the system to oscillate around an established value.
  • It is important to remember that with any real-world system, there will be a limit of the speed it can respond to changes and this is set not only by the PID loop itself, but also how often the conditions are sampled and in the case of a motor, how fast it speed up or coast down to a lower speed.
It turned out that for this application where the PID loop is updated at 7.63 Hz, good values for Kp, Ki, and Kd were 1.33, 0.055 and 7.125 respectively with ITIME set to 20 and BIAS set to 15 - a value that corresponds to a PWM value of 15, just below which the motor would start turning on its own.  The PID loop itself was implemented using floating-point numbers which are both slow and consume a lot of memory, but because the update rate was rather low and the processor really didn't need to do anything else besides control the motor, I simply accepted the penalty.

Were I more miserly with code space and execution speed I would have used signed integers instead.  My PIC compiler (the PICC compiler by CCS) supports the use of 32 bit signed and unsigned integers and the "quick and dirty" way to have converted the PID portion to signed integer would have been to take the above values (Kp, Ki, Kd, ITIME, BIAS and the value of ERROR after it is calculated) and multiply them by a rather large binary (2n) number such as 32768 (to make the binary math as simple as possible) and then divide the value of OUTPUT by the same.

Other features

A few more "features" were added to the code, namely:
  • Overrun Detection.  There should never be any condition where the PWM value is >=1023 (the maximum PWM value) in normal operation.  If this condition is detected for more than 8 or so PID cycles (e.g. those that occur at a rate of 7.63 Hz) the variables in the PID loop are zeroed out as a "sanity" check.
  • Auto turn-off.  If the input from the wheel sensor drops below one pulse per PID cycle on average for more than 8 PID cycles, the output PWM signal is turned off and the PID loop variables are zeroed out.  This pulse rate corresponds with a very slow rotation rate of the speedometer motor and is below that which can be reasonably produced - and it shuts off the PWM completely when the vehicle is stopped.
  • Locked rotor detection.  This is similar to "Overrun Detection" except that if the PWM value is >=1023 and the measured speed of the motor is below a threshold for more than 24 PID cycles (about 3 seconds) the PWM drive is disabled until the unit is power-cycled.  This prevents the speedometer drive motor from being damaged if, for some reason, it gets "stuck".
Setting the PWM rate:

Not mentioned up to this point is the PWM rate itself.  In the case of the PIC, the actual rate of the PWM pulses is configurable from a few 10s of pulses-per-second to several kHz - but which is best?

Any motor - and the DC motor used is no exception - will have a minimum input power where rotation will start, and this power is usually a bit higher than the amount of power needed to keep it turning once it has started.  What this means is that at start-up or at very low RPM that some "extra" power will be required to start rotation and that this needs to be backed off once it starts to spin.  Unfortunately, this usually leads to a bit of overshoot - and the PID values for higher speeds may not be appropriate for very low speeds.

One way to minimize this is to slow the PWM rate such that several 10s of pulses per second are applied.  Because of the slow repetition rate - and the fact that the duty cycle is always a certain percentage of the repetition rate - even low duty cycle pulses will last longer and contain more energy per pulse and will also be able to overcome the inductance of the motor winding and be more likely to get the motor moving more "easily".

In the case of this motor a PWM rate of about 61 Hz was a good compromise:  Because it is to be used on a speedometer, the ability to run the motor at very low speeds (a few 10s of RPM) was considered to be important.  The 61 Hz rate is low enough that a fairly low duty cycle pulse could "nudge" the motor and spin it at a few 10s of RPM and yet high enough that it still allowed for stable, higher-speed (3000 RPM) operation.  The down-side is that the motor will "buzz" a bit, but since this will be used on a noisy vehicle, anyway, that is unimportant.  If the motor is to be run over a narrower, higher-RPM range (say, a few hundred RPM to several thousand RPM) then a higher PWM rate - even one that is above the range of hearing - may work out well.

It should be noted that changing the PWM rate may require that the PID values be recalculated (or at least checked) so you should determine early on the appropriate PWM rate for your application.  In this particular application, a simple "single-ended" drive (e.g. no way to decelerate the motor other than lowering its input drive) and rather slow response time was considered to be adequate because it is simply not possible to change the speed of the entire vehicle quickly and safely anyway!

Does it work?

On the bench, it does - but it will be some months before it's ready to be driven on the road.  At that time I expect that I'll need to tweak the calibration (the current settings are only approximate, based on gear ratios and wheel diameters), but I don't anticipate any problems.


References:
  1. Wikipedia on PID controllers (link)
  2. Detailed article about PID loops, pseudocode examples and how to tun them at Robotics for Roboticists web site (link)
 * * *

This article stolen from ka7oei.blogspot.com


[End]

2 comments:

  1. Thank you for posting about using PID to control a speedometer. I have a motor connected to a speedometer and it works o.k. so far. I would like to use PID for smoother operation and your description is easy to understand.
    I have found that there is a motor torque curve and a speedometer spring tension curve. If I set the motor to start at the predetermined input frequency, and reach maximum speed at the correct frequency, the values between min and max are off, not linear. I corrected that by using an equation determined by the curve.
    I am using a signal from the vehicle generated from a reluctor ring and converted to 2000 pulses per mile. I use that 2000ppm frequency to determine a PWM from an Arduino. I created a chart in excel and inserted a trendline to find the regression model of the torque curve equation. I then use this equation with the 2kppm to get the PWM.
    My motor has an optical disk.To use the PID, I will use the optical disk for the actual speed, and the 2kppm to determine the desired speed.
    I have created a spreadsheet with the 2kppm frequency and my measured optical disk at each frequency. Create a chart from the two datasets, and insert trendline to find the regression model equation. Use that equation with the 2kppm to find the desired speed.
    How do you determine the desired_speed for the 4wheeler speedo? I figured out an equation to convert the control frequency to match the optical disk frequency. It is close but not exact.
    My speedometer is running smoother with the PI code, and pretty acurate too! I wanted to add the D, but got confused with your equations. Because you listed a reference, I checked the Ziegler-Nichols method and found the Kd=(Kp*Pu)/8 for PID where your equation shows Kd=(Kd*Pu)/8. The equations are a bit confusing, some that you list look like the PI type on the Ziegler-Nichols chart.
    Thanks again for posting this easy to understand version of using PID to run a speedometer with a motor. Is the 4wheeler on the road yet?

    ReplyDelete
    Replies
    1. Thank you for your comments: Your comment made me look at the equation and I found that I'd made a typo - so I corrected it so that Kd=(Kp*Pu)/8 - just as it appeared in Reference #2, which is where I started with the initial values.

      The Speedometer calibration "check" was done simply by powering the drive motor from a variable DC power supply and correlating the rotational speed to what the speedometer said. In doing this, we noticed that - as it typical with mechanical speedometers - there is a bit of nonlinearity, but this simply means that at some point - if it is bothersome - that a simple lookup/correction table will be added to the code.

      While the 4-wheeler has been driven around the neighborhood and the speedometer was observed to go up and down and indicate something sensible, it's not yet at the point where it will be taken out into the wild and driven. When that happens, my friend will make note of the speedometer readings and the actual speed (as read from a GPS receiver) and come to me with that information and we'll make the corrections as necessary.

      Delete





PLEASE NOTE:


While I DO appreciate comments, those comments that are just vehicles to other web sites without substantial content in their own right WILL NOT be posted!

If you include a link in your comment that simply points to advertisements or a commercial web page, it WILL be rejected as SPAM!