PDA

View Full Version : Closed loop speed controller



Ryan7777
- 26th January 2008, 01:49
I need to control the speed of a 90 volt DC PM motor (1.3 Amp, 11:1 reduction, 173 RPM final), one direction rotation. I will be using a mosfet to drive it. I need to be able to vary the speed between dead stop ( or a slow crawl) and full speed (or close to it) and have closed loop control, i.e. as the motor meets varying loads, the PWM changes to keep the motor as close to the set speed as possible. I got the PWM thing down, in fact, I'll probably use DT's HPWM10 include for that part of it. I figure i'd use a hall effect sensor on the primary motor shaft so it'll be easier to read at lower RPM's. I'm guessing around 1900 RPM full speed? and using one magnet. I just don't know how to code it! i figured i'd use a 16F684 and Timer1 to get the speed into the PIC, and ADCIN to grab a 10 bit value from my speed control pot, but im lost as what to do from there.. i need some code example or thoughts on how to get the error between my ADCIN value and the motors speed and then how to adjust my PWM to keep it steady? i don't need to know how to "flash an LED" first, i use PIC's alot, i've just never tried any closed loop stuff before. Thanks in advance for any help!!

Darrel Taylor
- 26th January 2008, 03:03
This would be a good place to start...

PID-filter routine (2nd try). (HenrikOlsson)
http://www.picbasic.co.uk/forum/showthread.php?t=5874

It takes a bit of playing to get the right sample rate and P-I-D constants, but it works pretty good when you do.
<br>

Ryan7777
- 26th January 2008, 03:57
Thanks for the fast reply Darrel!

1. The PID code is for a servo right? I see that it has direction control and I'm guessing when the setpoint and the ADC value match, it's going to stop the motor and fight to keep it in that position?

2. what do I change in the code to make this thing run as a speed control?

Darrel Taylor
- 26th January 2008, 04:11
That's what the example does, but the PID routine itself can be used for MANY different things.

In Henrik's example, it's controlling "Position". But if the numbers presented to the PID are representative of the Speed, then it will adjust the output until the Speed is matched.

It doesn't really matter what it is, temperature, humidity, speed, acceleration ...

If you can read it, then you can make a "Setpoint" within that range.
The PID loop adjusts the output until the "Reading" and the "Setpoint" are the same.

It does it in a way that is as fast as possible while minimizing overshoot and rejecting spurious influences as much as it can.

All you need to do, is find a way to read the speed of the motor, and a way to control the speed of the motor, which I think you already have.
<br>

HenrikOlsson
- 26th January 2008, 06:57
Hi,
Just as Darrel says it can be used to control "anything". Basicly you feed it the Error and it returns the amount of Drive it thinks is neccesary to reduce the Error to zero based on the P- I- & D-gains and a couple of other paramters. It doesn't know, or care, if it's position, speed or whatever.



pidError = Setpoint - CurrentSpeed 'Calculate error
Gosub PID 'Call PID. Result returned in pid_Out


There are a couple of things you need to concider. Since you're driving the motor in one quadrant only. There may be occations when the output of the PID routine is "negative" ie. it want's to actually reverse the motor. Obviously you can't do that with only one transistor so you need to clamp the output from the PID routine to positive drive only, perhaps something like this:


pid_Error = Setpoint - CurrentSpeed 'Calculate error
Gosub PID 'Result returned in pid_Out
If pid_out.15 then 'If "negative drive" is called for...
pid_out = 0 '..set output to 0...
pid_Ei = 0 'And keep the I-term from accumulating..

Else 'and if positve drive is called for....

pid_Out = ABS pid_Out '...set it to absoulte value of pid_Out
Endif


Since you want to controll the speed from zero all the way up I think you should concider more than a single a pulse per rev. Otherwise it can get really hard to get good control in the lower speedrange.

Another thing that is important in digital PID control is constant sampling rate. Ideally you set up a timer interrupt to run at whatever rate you need. What rate to run at depends on the timeconstant of the load and you may need to experiment with a bit but for a basic motorcontrol I'd say somewhere between 100-1000Hz.

/Henrik Olsson.

Ryan7777
- 26th January 2008, 08:00
yeah, that was one thing i was worried about, when my speed goes below the clamp setting.. and it doesnt have to go to complete zero, I can handle that in other ways. my final output shaft is 173RPM, I might add more magnets to the main motor shaft, before the gear reduction just to be safe, i'll have to see what i can get away with. I'm not quite sure i know what your saying about sampling rate? I was going to use timer1 as a counter to get the tach of the motor, which people typically want as RPMs for a display and would count for a second, but are you saying i should count for less then a second? or as much as possible? and also, im sure the number i get for the speed isnt going to be 1:1 with my ADC (the speed setpoint), should i be adjusting something to scale those, or? I don't have everything on hand i need at the moment for a bench test and experiment, I'll have to order a few more things (fets, a rectifier and cap for the motor). i'd like to have a handle on code things before hand tho! thanks again.

HenrikOlsson
- 26th January 2008, 09:17
Hi,
About the sampling rate. The motor and whatever is attached to it has a certain mechanical time constant. If you apply a voltage to it it it takes a certain amount of time for it to "respond". If you sample a lot slower than this time constant you won't be able to get good control.

I'll take a shot at an example:
If we are controlling the temperature in our swimming pool (which probably have a very large time constant) perhaps we should read the actual temperature and calculate a new "drive-value" for the heater once every hour or even slower than that. But if we're controlling the position of the mirror in a laser-scanner we may need to read the actual position and calculate a new "drive-value" tens of thousands of times per second.

Whatever the rate is (once a day or once every uS) it needs to be constant because the final output is based on the error itself (P) the magnitude and amount of time the error has been present (I) and the rate of change of the error (D).

It all depends on how "stiff" you want the control-loop to be. You may get away with a sampling rate of 1s if you can live with very a slow response. If you need to "instantly" respond to the change in load you need to sample much much faster than that.

There was a really good article available on the web called PID without a PhD explaining this a lot better than I can but I can't seem to find it. If you look around a bit you may be able to find it.

EDIT: Here (http://www.embedded.com/2000/0010/0010feat3.htm) it is.

HTH
/Henrik Olsson

Acetronics2
- 26th January 2008, 10:08
Hi, Enrik

You should browse the Parallax site : in their applications ... there's all your link shows ... but for PICs AND PbP ( or compatible) written.

http://www.parallax.com/Portals/0/Downloads/docs/prod/sic/Web-PC-v1.0.pdf

see also here : http://www.emesystems.com/BS2math5.htm#Smoothing

" Hope it can help " ... as we use to say !!!

Alain

eggman
- 26th January 2008, 14:15
I made a simple pid speed control some time ago, not very scientific but it works o.k.
I used a surplus motor with an optical encoder, then used the 'count' command to measure speed. I looked at the parallax code and copied their way of dealing with negative numbers.
here's my code:



<font color="#008000">@ DEVICE PIC16F88, MCLR_OFF, INTRC_OSC_NOCLKOUT, WDT_ON, LVP_OFF, BOD_OFF, PWRT_ON, PROTECT_OFF, CCPMX_ON

</font><b>DEFINE </b>OSC 8

<b>DEFINE </b>CCP1_REG PORTB
<b>DEFINE </b>CCP1_BIT 3 <font color="#000080"><i>' portb.3 motor PWM output
</i></font><b>DEFINE </b>ADC_BITS 8 <font color="#000080"><i>' Set number of bits in result
</i></font><b>DEFINE </b>ADC_CLOCK 3 <font color="#000080"><i>' Set clock source (3 = RC)
</i></font><b>DEFINE </b>ADC_SAMPLEUS 50 <font color="#000080"><i>' Set sampling time in uS

</i></font>PORTA = %00000000
TRISA = %00000001
PORTB = %00000000
TRISB = %00010000
OSCCON = %01111000 <font color="#000080"><i>'8MHZ clock
</i></font>ANSEL = %00000001
CMCON = %00000111

led <b>VAR </b>PORTA.1
sensor <b>VAR </b>PORTB.4 <font color="#000080"><i>'optical feedback from motor

</i></font>counter <b>VAR BYTE
</b>set_speed <b>VAR WORD
</b>actual_speed <b>VAR WORD</b>
error <b>VAR WORD
</b>old_error <b>VAR WORD
</b>error_sum <b>VAR WORD
</b>sign <b>VAR WORD
</b>Kp <b>CON </b>1
Ki <b>CON </b>2 <font color="#000080"><i>'inverse !!
</i></font>Kd <b>CON </b>0
power <b>VAR WORD
</b>P <b>VAR WORD
</b>I <b>VAR WORD
</b>D <b>VAR WORD

CLEAR </b><font color="#000080"><i>'clear all variables

</i></font>loop:
<b>GOSUB </b>read_sensors
<b>GOSUB </b>calculate_error
<b>GOSUB </b>calculate_proportional
<b>GOSUB </b>calculate_integral
<b>GOSUB </b>calculate_differential
<b>GOSUB </b>calculate_drive
<b>HPWM </b>1,power,10000 <font color="#000080"><i>' drive motor
</i></font><b>TOGGLE </b>led
<b>GOTO </b>loop

read_sensors:
<b>ADCIN </b>0,set_speed <font color="#000080"><i>'read potmeter
</i></font><b>COUNT </b>sensor,20,actual_speed <font color="#000080"><i>'count number of pulses
</i></font><b>RETURN </b><font color="#000080"><i>'from encoder in 20msec

</i></font>calculate_error:
error = set_speed - actual_speed
<b>RETURN

</b>calculate_proportional:
P = Kp * error
<b>RETURN

</b>calculate_integral:
error_sum = error_sum + error
sign = error_sum
<b>GOSUB </b>SetSign
<b>IF ABS </b>error_sum &gt; (250*Ki) <b>THEN </b>error_sum = sign * (250*Ki) <font color="#000080"><i>' limit
</i></font>I = <b>ABS </b>error_sum / Ki
I = sign * I
<b>RETURN

</b>calculate_differential
D = Kd * (error - old_error)
old_error = error
<b>RETURN

</b>calculate_drive:
power = P + I + D
sign = power
<b>GOSUB </b>SetSign
<b>IF </b>sign = 1 <b>THEN </b><font color="#000080"><i>' no negative power
</i></font><b>IF </b>power &gt; 255 <b>THEN </b>power = 255
<b>ELSE
</b>power = 0
<b>ENDIF
RETURN

</b>SetSign:
<b>IF </b>sign.Bit15 = 0 <b>THEN
</b>sign = 1
<b>ELSE
</b>sign = -1
<b>ENDIF
RETURN

END





</b>