PDA

View Full Version : PID-filter routine (2nd try).



HenrikOlsson
- 8th March 2007, 22:31
Hi,
(Ok, lets try this again. The previous version contained a non working example which is not very suitable in the Example Section so it has been updated. Here goes...)

This is a basic PID filter routine in the form of an include file.

Instructions:
1) Download the file incPID.txt attached to this post
2) Rename it to incPID.pbp
3) Place it in your project folder.

Example code:
This is a short example demonstrating how the PID routine can be used. The PIC's CCP module drives a motor via a suitable driver chip such as the LMD 18200. The motor shaft is connected to a potentiometer which is feeding position information back to the PIC's ADC forming a closed loop.




' |--PIC CCP --> LMD18200 --> Motor --> Potentiomter --> PIC ADC --|
' | |
' |---<-----<-----<------ P I D F i l t e r <------<------<-------|



'// Set up hardware registers, ADC etc here (not shown) //

ADValue VAR WORD '<---This is your variable.
SetPoint VAR WORD '<---This is your variable.
Direction var PortB.7 '<---Direction bit to motor driver.

Setpoint = 512 '<---Set desired position.

INCLUDE "incPID.pbp" 'Include the PID routine.

'These variables are declared by the incPID routine but
'the user needs to assign values to them.
pid_Kp = $0700 'Set Kp to 7.0
pid_Ki = $0080 'Set Ki to 0.5
pid_Kd = $0225 'Set Kd to 2.14
pid_Ti = 8 'Update I-term every 8th call to PID
pid_I_Clamp = 100 'Clamp I-term to max ±100
pid_Out_Clamp = 511 'Clamp the final output to ±511

Start:
Gosub GetAD 'Get position from AD - NOT SHOWN HERE.

pid_Error = Setpoint - ADValue 'Calculate the error
Gosub PID 'Result returned in pid_Drive
Direction = pid_Out.15 'Set direction pin accordning to sign
pid_Out = ABS pid_Out 'Convert from two's comp. to absolute
HPWM 1, pid_Out, 10000 'Set PWM output
Pause 10 'Wait....
Goto Start '...and do it again.


The code does not check for overflow in the calculations so the user needs to make sure that the value sent to the PID routine doesn't cause an overflow based on the current P, I and D gains.

More detalied information can be found in the attached file.

A special thank you goes to Darrel Taylor for spotting a couple of misstakes in the example but most of all for streamlining the code further, reducing its footprint about 20%.

/Henrik Olsson.

RodSTAR
- 16th April 2008, 02:08
hi Henrik;
i see you are a motor control specialist..
first of all thank you for sharing your work on your PID routine. About it i have some questions to understand.
_________
question1:
i don't understand theese variables:
pid_I_Clamp = 100 'Clamp I-term to max ±100
pid_Out_Clamp = 511 'Clamp the final output to ±511

_________
question2:
I see it controls position. ¿Does it control speed?

_________
question3:
from my pc i get two parameters: position and speed. i want to preset a 1 second of gradual speed increasing (you know, for not damaging mechanism & stuff..) and speed decreasing for before getting to the given position. Do any of your PID inc file variables handle this parameter/s or i need to do it by myself?

to let you breathe, it's all for now :) thank you.

regards, Rodrigo

HenrikOlsson
- 16th April 2008, 06:52
Hi Rodrigo,
No, no motorcontrol specialist at all actually, just a guy who tinkers with a lot of stuff, trying to pick up on what ever interests me at the moment ;-)


question1:
i don't understand theese variables:
pid_I_Clamp = 100 'Clamp I-term to max ±100
pid_Out_Clamp = 511 'Clamp the final output to ±511

The pid_I_clamp helps preventing what is called integrator wind-up. Let's say you are controlling the position of a motor. You command a new position but the mechanics that the motor is moving is blocked so it can't move. The I-term will now start to add "effort" to the total output of the filter to get the motor moving (decrease the error) but since the mechanics is blocked the error won't change so the I-term gets larger and larger and larger. When the mechancis finally "loosens up" you can imagine what happens. So with pid_I_clamp you can set the max amount of "effort" that the I-term will apply to the total output, no matter how big or how long the error persists.

(Another thing is that the I-term output is not protected from overflowing internally so we have to clamp it to something)

pid_Out_Clamp works the same way. Let's say you are running a 12V motor with a 24V powersupply. Applying full power (24V) to the motor may damage it so you can use the pid_Out_Clamp to prevent the final output of the PID-filter to ever go above a certain value, for example.



question2:
I see it controls position. ¿Does it control speed?

"It" controls anything you like. You provide it with an "error" and gives back a "drive-value" based on the gains etc that you've specified. It doesn't know or care what it is you are controling. With that being said it may lack features that is better suited for controling one thing or another, like feedforward etc but more or less, it doesn't care.



question3:
from my pc i get two parameters: position and speed. i want to preset a 1 second of gradual speed increasing (you know, for not damaging mechanism & stuff..) and speed decreasing for before getting to the given position. Do any of your PID inc file variables handle this parameter/s or i need to do it by myself?
No, there's no trajectory planner built in. If you want acceleration and deceleration ramps you need to "slowly" increase the "setpoint" to bring it up to speed and so on.

Hope that answers your questions.

/Henrik.

RodSTAR
- 21st April 2008, 07:48
Thank you so much Henrik, now I got it!
I'll test it as soon as I can. Then i'll bother you again :)

I have another question in a new thread, not related to PID, but related to counting position. Thread title: "Pulse count matching"

infinitrix
- 27th March 2009, 17:31
can i use the code to control speed and position of motor using same pic?

RodSTAR
- 27th March 2009, 18:33
can i use the code to control speed and position of motor using same pic?

First step (get), you Get variables from hardware (via pot, encoder, ...)
Second step (control), you correct those variables (PID between desired and got variables)) to reach your desired speed, direction & position by PWM and polarity (depending on the type of motor DC, BLDC or what ever). That is control.

And yes, both steps you can perform them in a single MCU. Motor control oriented MCU's works great for this, such as a dsPIC30F3011; or for this compiler, a PIC18F2431 (or any of the family PIC18Fxx31) which have QEI peripheral, where QEI stands for Quadrature Encoder Interface, which decodes signal from a rotary Quadrature Encoder, ideal for gathering motor rotation activity.

Rodrigo M.-

infinitrix
- 27th March 2009, 23:22
i'm using 1 channel incremental encoder and PIC16F877A.i use timer 1 to count pulse on background.to get the speed i use "count" instruction but it take a 0.5 second.is there any faster solution to calculate speed?

i decide to use incPID program to control speed,but how can i tune to find Kp,Ki, and Kd value..

infinitrix
- 31st March 2009, 21:53
anyone know how to tune PID coefficient? help me..

amgen
- 1st April 2009, 02:36
.5 sec is too long to get speed reading, you may be able to timer/counter 1 pulse duration to get speed ref faster .
PID itself is hard to tune. Isn't each correction just how-much and how fast ? and slowing-down and reducing each correction until there is no overshoot ? I made an alternator/regulator controller that self-tuned by jointly reducing correction amount and slowing corrections (longer pauses) until there were no more overshoots. The output was the PWM to MOSFET. You could also adjust up/down the gain/time with a pot input ref.
Are you interested in PID discussion ?
don
amgen

infinitrix
- 2nd April 2009, 06:20
i'm interested in pid to control position and speed.. can u show me some program and schematic for self tune pid.. i use 3 pot to adjust kp,ki and kd.. i'm just not very clear about tuning the pid..:o

amgen
- 2nd April 2009, 15:54
do you need to move something to a position and stop, or shift motor from 1 speed to another speed or hold speed constant while the load changes.
That dictates the type of feedback needed and how fast the control needs to actuate.
Explain your control operation a little.

don
amgen

HenrikOlsson
- 5th April 2009, 12:26
Hello,
Sorry for the slow response on my behalf, I've been on the road so I haven't been on the 'net that much.

As I've written en previous posts in this thread the incPID routine itself does not know or care if it controls position, speed, temperature, waterlevel, light, torque or whatever. It simply returns a value based on the "error" you send it and the tuning parameters you set.

You can only run one PID-loop with it though (without rewriting the include) so controling BOTH speed AND position with the incPID won't work. What you CAN do though is to set up your system to control position and then in your program you continously change the targetposition. Set up a 100Hz (or whatever) interrupt and add to or subtract from the target position, the more you add or subtract the faster it will go. I've done that myself and it works fairly well.

Tuning is an art and there's more than a few ways to do it, Google Ziegler Nichols to read up on one of them. I've simply done it by "feel" but you can for example send the errror value to the USART and then plot the value on the PC using EXCEL or whatever to "see" the response of the system.

I suppose doing an autotune routine would be possible but it's not something I'm going to try. If you're going to write an autotune routine I suggest you read up and get good at manually tuning first so you know and understand how to respond to different situations. (Undershoot, overshoot, oscillation etc).

Hope it helps!
/Henrik.

hadiengg
- 5th August 2009, 16:03
Dear sir,
I check INCPID routine it works fine as position control.
I want to control speed via PWM what should i change in the INCPID routine
and also how can i give pid_Kp pid_Ki pid_Kd using external potantiometer
which is reading through ADC CH which gives 0-1023 value and how i convert
this value to $0xxx figure.

RodSTAR
- 5th August 2009, 17:05
Dear sir,
I check INCPID routine it works fine as position control.
I want to control speed via PWM what should i change in the INCPID routine
and also how can i give pid_Kp pid_Ki pid_Kd using external potantiometer
which is reading through ADC CH which gives 0-1023 value and how i convert
this value to $0xxx figure.

As I understand, this PID checks only position.
To control speed, you should use another routine to control position AMONG TIME (speed), using this PID routine as position control. I did that and works fine. Not just simple speed but parametrical trapezoidal (lineal, curve, log, exp) starts and ends, via USB to my laptop. It works fine almost like an industrial servomotor (24V 500W DC motors). My next experiment is to implement it with 4 BLDC motors I have. I'm in the way to control with a iPhone. Henrik doesn't know how gratefull I am to him for his seed piece.

hadiengg
- 6th August 2009, 06:54
Dear sir,
What should i change in INCPID file that it controls the speed of motor not position.
And also i want to put Kp Ki Kd values through potantiometers.How it is possible.?:)

HenrikOlsson
- 6th August 2009, 10:47
Hi,
You don't need to change anything. If you're aiming for speed control only then simply measure the speed of your motor with whatever you like, encoder, tachometer, BEMF or whatever. Compare that to your target speed and send the difference between the two (ie. the error) to the PID-filter. The filter itself does not know or care what you are regulating, it might as well have been air-pressure as far as the PID-routine is concerned.

If you need to move to a certain position WITH a certain speed then you need to take the moving target aproach like RODStart outlined. If you control position and add 1 to the target position every second the motor will move 1 encoder count every second or 10.8° per minute or 0.03rpm with a 500line encoder.

Regarding the pots, you can set the PID gains in any number system you like. The reason I show it in hex is that it is easier IMO. You can simply do pid_Kp = myADResult if you like.

1023 in decimal is the same as $03FF which, in the PID filter, is treated as 3.996 basically. See, 03 is the number on the left side of the decimalpoint and FF is on the right side. FF represents 1/256*255 or 0.996. If you'd feed it 102 from the ADConverter it would be the same as $0066 or a gain of 0.258.

If you need more gain than 1023 then simply multiply your ADResult, pid_KP=myADResult*2, now you get a gain of 0-2046 or $0000-$07FE or 0.0-7.992

/Henrik.

hadiengg
- 7th August 2009, 14:44
Dear sir thank u for reply.
My question is that i can get 10 bit resolution of PWM output to control motor speed.
I use following code.
CCP1CON = %00001100 ' Set CCP1 to PWM
T2CON = %00000111 ' Turn on Timer2, Prescale=4
PR2 = 249 ' Set PR2 to get 1KHz out
CCP1CON.4 = pid_out.0 ' Store duty to registers as
CCP1CON.5 = pid_out.1 ' a 10-bit word
CCPR1L = pid_out >> 2
It will give 10bit PWM output at 16MHz crystal.
Now spose pid_Error=+5 then spose after calculation
pid_Out=100
pid_out.15=0 'DIRECTION BIT
same result will be if pid_Error=-5 but
pid_out.15=1
With both errors final PWM resolution is same but how can I use direction bit to change PWM output to increase or decrease motor speed according to requirement.?
If there is any OFFSET which must be used ?

HenrikOlsson
- 8th August 2009, 07:47
Hi,
The approach with the direction bit is for sign and magnitude PWM while, by the sound of it, you're looking for locked anti phase. In locked antiphase the PWM dutycycle is 50% when no torque should be delivered by the motor (the average current thru the motor is 0A). The dutycycle is then increased or decreased from 50% depending on which direction and how much torque the motor should deliver.

So, if you ARE doing locked antiphase PWM simply set the PWM duty cycle to 50% on startup and then, if pid_out.15 is set (output is negative) you subtract the ABS value of pid_out from the value coresponding to 50% and if pid-out.15 is "0" you add the ABS value of pid_out to value coresponding to 50%.

If you are doing sign and magnitude then your hardware will have an input for direction and you need to set that according to pid_out.15 and then set the dutycycle to the ABS value of pid_out.

I sugest you set the pid_Out_Clamp variable to a suitable value to prevent over or underflow in the duty-cycle register.

/Henrik.

hadiengg
- 13th August 2009, 06:54
Thanks for coperation.
I am trying to complete my job.

daan.joubert
- 2nd September 2009, 13:47
Hi Henrik,
Is it possible to use portion of the code on a RC servo which is connected to an externat 10k potentiometer.?
My existing circuit is driving an RC servo in the range of 1mS to 2mS, the servo pulls a throtle of an engine, to keep revolutions at 1500 RPM (50Hz). The engine is directly coupled to an alternator. The position of the servo is controlled by a voltage (F/V), form the alternator frequency and a 10k setpoint potentiometer. I use analog inputs on 16f88 to convert volt to value. I then subtract values from each other and send the difference to the servo. (simple calculation)
My problem is that the voltage conversion is not linear to the servo movement. The result is that servo movement is always too litlle in comparison of the set speed needed.
I was wondering whether I can use feedback in order to counteract the non-linear characteristic of the servo.

Acetronics2
- 2nd September 2009, 13:58
Hi, Daaan

I use such a routine to drive cooling flaps of an engine cowl ... ( of course with temp. as input ... lol )

no problems just change the lines :



Direction = pid_Out.15 'Set direction pin accordning to sign
pid_Out = ABS pid_Out 'Convert from two's comp. to absolute
HPWM 1, pid_Out, 10000 'Set PWM output
Pause 10 'Wait....


to drive a servo and not a " Bridge driven " DC motor.

Did not understand your linearity problems ... IF you use the servo within its NORMAL RANGE ( say 800 - 2200 µs ) linearity is fine - Whatever its brand ( humour !!! )

Moreover, ... as it's a closed control loop with Integral action, your servo HAS to reach the required position ... more or less qickly !!!

But, might be, your project is only a PROPORTIONNAL regulator ... This could easily explain never reaching the setpoint ...

Alain

anonymouse
- 3rd September 2009, 01:17
Hi Henrik,
Is it possible to use portion of the code on a RC servo which is connected to an externat 10k potentiometer.?
My existing circuit is driving an RC servo in the range of 1mS to 2mS, the servo pulls a throtle of an engine, to keep revolutions at 1500 RPM (50Hz). The engine is directly coupled to an alternator. The position of the servo is controlled by a voltage (F/V), form the alternator frequency and a 10k setpoint potentiometer. I use analog inputs on 16f88 to convert volt to value. I then subtract values from each other and send the difference to the servo. (simple calculation)
My problem is that the voltage conversion is not linear to the servo movement. The result is that servo movement is always too litlle in comparison of the set speed needed.
I was wondering whether I can use feedback in order to counteract the non-linear characteristic of the servo.
I have used a magnetic pickup on the fly wheel in the past, adc this to a stepper motor, incorporate the feedback once you get to the stage of engine hunting, then set your fine tuning (gain etc).

hadiengg
- 3rd September 2009, 06:49
I have many trys to solve my problem but fail.Actually i want to control GAS engine speed.I am using AGB130 LINEAR ELECTRIC ACTUATOR to drive BUTTERFLY VALVE.I am using MAGNETIC PICKUP UNIT located on flyweel to sense engine speed.After F/V conversion i have 0-5000Hz to 0-5V.
Problem is that ACTUATOR drive only forward direction through given voltage
and reverse direction through SPRING located inside.
ACTUATOR always needed some voltage to remain in a position to maintain speed.But INCPID routine gives 0 output when no error found.
I am giving some examples in atachements which will help to identify problem.
Best regards
IFTIKHAR AHMAD

HenrikOlsson
- 3rd September 2009, 11:41
Hi,
Don't really know how the spring return will work here...Can't really see why it wouldn't work....

The 0V at 0 error isn't really true.... When the error is 0 output from the P- and D-term is indeed 0 (the D-term might not be 0 but for this discussion let's say it is). But the output from the I-term is whatever was needed to bring the error to 0. (It might be 0 but not necessarily).

Think of the car analogy, you're trying to keep the speed at 50mph and you're starting to climb a steep hill, if you simply keep the gas-pedal at the exact same position the speed will start to drop. The P-term adds a little effort based on the error but not enough since the load now is larger (we're climbing a hill). The I-term starts adding a little effort (giving some gas) and soon the velocity is back at 50mph. Now the P term is 0 (since there is no error) the D-term is 0 since there is no error but the I-term is not zero - if it were the speed would start to drop again.

With that said, simply add a little to the output of the PID-filter rto overcome the force of the spring - if that's what you want. This is often called feed-forward and means that for a particular speed, torque, position or whatever you have like a "starting point" for the "drive" - the PID filter then takes it from there.

/Henrik.

RodSTAR
- 3rd September 2009, 15:02
I have many trys to solve my problem but fail.Actually i want to control GAS engine speed.I am using AGB130 LINEAR ELECTRIC ACTUATOR to drive BUTTERFLY VALVE.I am using MAGNETIC PICKUP UNIT located on flyweel to sense engine speed.After F/V conversion i have 0-5000Hz to 0-5V.
Problem is that ACTUATOR drive only forward direction through given voltage
and reverse direction through SPRING located inside.
ACTUATOR always needed some voltage to remain in a position to maintain speed.But INCPID routine gives 0 output when no error found.
I am giving some examples in atachements which will help to identify problem.
Best regards
IFTIKHAR AHMAD

PID is a loop. If spring moves something, PID will detect it and it will correct it immediatly. Don't confuse error variable with PWM value. In my project it works fine and it responds to dynamic loads and it correct it as it should (read above post of my project). Sorry for answering you, as you only refer to Henrik.

anonymouse
- 3rd September 2009, 22:06
I have many trys to solve my problem but fail.Actually i want to control GAS engine speed.I am using AGB130 LINEAR ELECTRIC ACTUATOR to drive BUTTERFLY VALVE.I am using MAGNETIC PICKUP UNIT located on flyweel to sense engine speed.After F/V conversion i have 0-5000Hz to 0-5V.
Problem is that ACTUATOR drive only forward direction through given voltage
and reverse direction through SPRING located inside.
ACTUATOR always needed some voltage to remain in a position to maintain speed.But INCPID routine gives 0 output when no error found.
I am giving some examples in atachements which will help to identify problem.
Best regards
IFTIKHAR AHMAD
You will need to find the required pwm to output 1500 rpm (1.5v) from your engine,(you will need this on startup to have your engine idling at no load) this you will use as your setpoint after adc. Anything above it will generate a negative P substracting from setpoint,slowing the engine down, above it adding to the setpoint,speeding up the engine. The gains of each part of p.i.d will be from the variable resistors via adcs. Have these initially set to one, set the p then p gain etc the idea is to have the engine fairly responsive to load change and the fine settings of the gains will limit the droop and overshoot.

daan.joubert
- 4th September 2009, 09:05
Hi Alain and others,
Thank you all for info given, it is much appreciated.

Alain, regarding the linear problem - I have tested the actual distance travel of the servo arm by connecting a ruler to the arm.
To test I then adjusted the speed voltage, taking voltage readings and distance readings as I go along.
The distance values on Excel graph showed a non linear line in comparison to the linear nature of the voltage.
/Daan Joubert

Acetronics2
- 4th September 2009, 09:33
Hi Alain

The distance values on Excel graph showed a non linear line in comparison to the linear nature of the voltage.
/Daan Joubert

Hi, Daan

I hope you've considered the rotation angle of the arm is linear to the signal ...

But, whatever your transmission system ... only rack and pinion or cable and pulley can give a linear travel !!!

You could use a LOOKUP Table to correct your output ... but, once more, if it is a real PID controller, it will compensate for non-linearities ...

Or try to use ... FUZZY LOGIC !!!

Or find an old LINEAR R/C servo ( Kraft, Robbe, Varioprop, Lextronic ... produced some in the 70-80's )


Alain

anonymouse
- 4th September 2009, 12:00
Hi Alain and others,
Thank you all for info given, it is much appreciated.

Alain, regarding the linear problem - I have tested the actual distance travel of the servo arm by connecting a ruler to the arm.
To test I then adjusted the speed voltage, taking voltage readings and distance readings as I go along.
The distance values on Excel graph showed a non linear line in comparison to the linear nature of the voltage.
/Daan Joubert
Your gains might need to be fractional all the way up to 20 times and more via your vresistors via adc. Example engine running no load 1500 rpm, adc 1.5/5 * 256 = 77. setpoint = 77,unknown but say pwm 25, all gains apart from p set to zero. 25% load applied engine droops to 500rpm,adc 0.5/5 * 256 = 26. 77-26= 51, pwm output 51. If output to much p gain * 0.8 etc,to little * 1.5 etc. You should get to the stage of engine hunting turn the gain slightly below this. This is then where the data from i and d comes into play and slightly tweaking each one results in a reactive genset regardless of load.

hadiengg
- 7th September 2009, 07:25
Thanks Mr Henrik,Mr RodSTAR and Mr Anonymouse for help.
I succesfully run the motor at constant speed.Motor speed also change through given speed refrence.
Now one problem that i face is motor overshoot in slow speed refrence whenever i turn on the power.Its overshoot cycle comes down when i increase speed refrence.At full speed refrence it get exact speed never overshoot.I am using same PID gains used in original file.I think i must change the GAIN value to overcome this problem.
And also i am using variable for 0-5V to 0-1023 value through ADC.How i use this value to put the gain as used under.
pid_Kp = $0700 'Set Kp to 7.0
Please give some code to convert the value.

HenrikOlsson
- 7th September 2009, 08:58
Hello,
I thought I explained that in post #16 above. I'll simply quote myself:

Regarding the pots, you can set the PID gains in any number system you like. The reason I show it in hex is that it is easier IMO. You can simply do pid_Kp = myADResult if you like.

1023 in decimal is the same as $03FF which, in the PID filter, is treated as 3.996 basically. See, 03 is the number on the left side of the decimalpoint and FF is on the right side. FF represents 1/256*255 or 0.996. If you'd feed it 102 from the ADConverter it would be the same as $0066 or a gain of 0.258.

If you need more gain than 1023 then simply multiply your ADResult, pid_KP=myADResult*2, now you get a gain of 0-2046 or $0000-$07FE or 0.0-7.992

I don't really know how to explain it in any other way but let me know if doesn't make sense and I'll try, or perhaps someone else can attack it from another angle.

/Henrik.

Acetronics2
- 7th September 2009, 09:33
Hi, Hadienggggggg



At full speed refrence it get exact speed never overshoot.I am using same PID gains used in original file.I think i must change the GAIN value to overcome this problem


EACH system has its own set of parameters ( Kp, Ki, Kd ) and you are the one and only one who can find them ...

NOW, just Google around for the " Ziegler and Nichols method " ... sure it will help you !

FURTHER ... you can disable The Ki and Kd action at startup and engage then for a maximum error of ???, you can use retrieve tables for Kp,Ki,Kd as a function of the running point, you can program a rampup for engine start ...

But that is VERY typical to YOUR project ... and far out from Henrik's project shown here ...

BTW ... MERCI Henrik ...

Alain

phoenix_1
- 26th September 2009, 14:14
The QEI reading work good with count for position from 0-1000.
The PPWM give good voltage and signal too from 0 - 1024 at 20KHz.
The Henrik PID rutine work good but only when position and setpoint is max +/-50 !
and realy how make to motor clip +/- when position = setposition in some range about +/- 25% as break in position.
My output driver have next logic :
with PWM 512 motor STOP
0<--------512----->1024
left <---- stop-----> right
fast slow stop slow fast

Here is my debug program:


'************************************************* ***************
' Definicije PIC-a 18F4431 *
'************************************************* ***************
DEFINE OSC 20
include "incPID.pbp"
'************************************************* ***************
' LCD DEFINE LCD 4X20 chr *
'************************************************* ***************
DEFINE LCD_DREG PORTD
DEFINE LCD_DBIT 4
DEFINE LCD_RSREG PORTC
DEFINE LCD_RSBIT 0
DEFINE LCD_EREG PORTC
DEFINE LCD_EBIT 1
DEFINE LCD_BITS 4
DEFINE LCD_LINES 4
DEFINE LCD_COMMANDUS 2000
DEFINE LCD_DATAUS 50

'************************************************* ***************
' Definicije Portova *
'************************************************* ***************
ADCON0=0
ANSEL0=0
TRISB = %00000000
PortA = 0
PortB = 0
PortC = 0
PortD = 0
PortC.3 = 1

'************************************************* ***************
' Definicije PPWM *
'************************************************* ***************
DTCON = 0
PTCON0 = %00000000
PTCON1 = %10000000
PTPERL=$FF
PTPERH=$00
PWMCON0 = %00100000
PWMCON1 = 1
OVDCOND = %11111111
'************************************************* ***************
' Definicije QEI encodera *
'************************************************* ***************
'QEICON = %10011000
'DFLTCON = %00111000
'MAXCNTL = %11001111
'MAXCNTH = %00000111
QEICON=%10001000
DFLTCON = %00111000
MAXCNTL = %11101000
MAXCNTH = %00000011
PosLow VAR BYTE
PosHigh VAR BYTE
PosTemp VAR BYTE
Position VAR WORD
POSCNTL = 0
POSCNTH = 0
'************************************************* ***************
' Definicije TMR0 *
'************************************************* ***************
T0CON = %11001000
INTCON.5 = 1
INTCON.1 = 0
INTCON.7 = 1
INTCON.4 = 0
INTCON.3 = 0
INTCON.2 = 0
INTCON2.2 = 1
RCON.7 = 1
TMR0L =254
'************************************************* ***************
' Definicije PID filtera *
'************************************************* ***************
x var word
y var word
ADValue VAR word '<---This is your variable.
Setpoint VAR WORD '<---This is your variable.
Direction var bit '<---Direction bit to motor driver
pid_Kp = $0700 'Set Kp to 7.0
pid_Ki = $0080 'Set Ki to 0.5
pid_Kd = $0225 'Set Kd to 2.14
pid_Ti = 8 'Update I-term every 8th call to PID
pid_I_Clamp = 100 'Clamp I-term to max ±100
pid_Out_Clamp = 511 'Clamp the final output to ±511
x = 0
y = 0
setpoint = 100
pause 250
lcdout $fe,1
'************************************************* ***************
' Osnovna petlja *
'************************************************* ***************
on interrupt goto PIDcalc

loop:
if portc.3 = 1 then setpoint = setpoint + 1
if setpoint > 1000 then setpoint = 0
LCDOUT $fe,128,"POSITION = ",dec position
LCDOUT $fe,192,"SETPOINT = ",dec setpoint
LCDOUT $fe,148,"PID = ",dec pid_out
LCDOUT $fe,212,"PWM = ",dec y
pause 10
lcdout $fe,1
goto loop

disable
PIDcalc:
INTCON.1 = 0
if INTCON.2 = 1 then
PosHigh = POSCNTH
PosLow = POSCNTL
PosTemp = POSCNTL
If PosLow - PosTemp = 0 then Goto Done
PosHigh = POSCNTH
PosLow = POSCNTL
Done:
Position = POSHIGH * 256 + PosLow
advalue = position
pid_Error = Setpoint - ADValue
Gosub PID
Direction = pid_Out.15
pid_out = ABS pid_Out
x = (512 - pid_Out) + 512
select case direction
case 1
y = pid_Out
case 0
y = x
end select
PDC0L = y.LowByte
PDC0H = y.HighByte
endif
INTCON.2 = 0
resume
enable
end

If any help pls.
Regards Robert

HenrikOlsson
- 26th September 2009, 14:51
The Henrik PID rutine work good but only when position and setpoint is max +/-50 !
OK, so what does it do if the setpoint is 51 then?

I see you're using On Interrupt and it seems you're using TMR0 as the source for the interrupt. It would be good if you could comment that section a bit so we don't have to read thru the datasheet in order to figure out what you're trying to do.

If I haven't missed anything you're running TMR0 in 8bit mode, clocked from the internal clock @20/4=5Mhz, prescaler selection is set to 1:2 which means the timer is clocked at 2.5Mhz, overflowing and generating interrupts at 9765.625Hz. There's no need to run the PID-loop at that speed, generally 1000Hz is good enough for motor control.

The Pause 10 statement needs to go away, and the LCDOut statements will likely mess with the timing as well. Read up on how On Interrupt works in the PBP manual. I highly recomend you look up Darrels Instant Interrupt routines as they don't suffer from the things On Interrupt does.

What do you mean by

and realy how make to motor clip +/- when position = setposition in some range about +/- 25% as break in position
It's really hard to understnad what you mean..... A deadband where anything within 25% of the setpoint is good enough or a following error trip and when the error is bigger than a set amount?

I've just received my copy of v2.60 so I can start using 32bit variables. I'm having a bt of a problem getting it going though. On top of that I fried my servo development board by connecting 24V to the 5V input so I need to get that sorted....

phoenix_1
- 27th September 2009, 00:19
here is what I rewrite in incPID


'************************************************* ******************************
'************************************************* ******************************
'Calculate the total drive.

pid_Out = pid_P + pid_I + pid_D 'Calculate total drive....

pid_Sign = pid_Out.15 'Save Sign
pid_Out = ABS pid_Out 'Convert from two's comp. to abs.
'if pid_Out >= pid_Out_Clamp then 'Output is saturated...
if pid_Out >= 640 then
pid_Status_Out_Sat = 1 'set status bit and...
pid_out = pid_Out_Clamp 'clamp output.

Endif
'If pid_Sign then pid_out = -pid_out 'Re-apply sign.

RETURN 'And return to sender.
SkipPID:


now the PID work close to good , ABS pid_error is in range close to 511 +/- 100 when I probe to personal move shaft of motor.
Becous my motor have gear 6.25:1 my variable ( y as LONG) go from 0-6250 for one full turn of shaft.Motor must go 6.25 * 1000.
QEI is setup for one turn of motor shaft go 0-1000.
It is not full good but it is close to be PID :-(
PBP code :


'************************************************* ***************
' Definicije PIC-a 18F4431 *
'************************************************* ***************
DEFINE OSC 20
include "incPID.pbp"
'************************************************* ***************
' RS 232 HSEROUT 19200 kbps *
'************************************************* ***************
DEFINE LCD_DREG PORTD
DEFINE LCD_DBIT 4
DEFINE LCD_RSREG PORTC
DEFINE LCD_RSBIT 0
DEFINE LCD_EREG PORTC
DEFINE LCD_EBIT 1
DEFINE LCD_BITS 4
DEFINE LCD_LINES 4
DEFINE LCD_COMMANDUS 2000
DEFINE LCD_DATAUS 50

'************************************************* ***************
' Definicije Portova *
'************************************************* ***************
ADCON0=0
ANSEL0=0
TRISB = %00000000
PortA = 0
PortB = 0
PortC = 0
PortD = 0
PortC.3 = 1

'************************************************* ***************
' Definicije PPWM *
'************************************************* ***************
DTCON = 0
PTCON0 = %00000000
PTCON1 = %10000000
PTPERL=$FF
PTPERH=$00
PWMCON0 = %00100000
PWMCON1 = 1
OVDCOND = %11111111
'************************************************* ***************
' Definicije QEI encodera *
'************************************************* ***************
QEICON=%10001000
DFLTCON = %00111000 ' enable error filter for all capture inputs
MAXCNTL = %11101000 'low set for maxcnt of 12500 becouse have gear 6.25:1
MAXCNTH = %00000011 'hig set for maxcnt of 12500 becouse have gear 6.25:1
PosLow VAR BYTE
PosHigh VAR BYTE
PosTemp VAR BYTE
Position VAR WORD
POSCNTL = 0 ' clear lowbyte of counter for start
POSCNTH = 0 ' clear highbyte of counter for start
'************************************************* ***************
' Definicije TMR0 *
'************************************************* ***************
T0CON = %11001111
INTCON.5 = 1
INTCON.1 = 0
INTCON.7 = 1
INTCON.4 = 0
INTCON.3 = 0
INTCON.2 = 0
INTCON2.2 = 1
RCON.7 = 1
TMR0L =237 ' 8bit ,1:256 divider ,237 preset = 1000HZ at 20MHz clk
'************************************************* ***************
' Definicije PID filtera *
'************************************************* ***************
s var word
f var word
y var long 'Total pulse count storage variable 32bit
x var byte 'Number of full shaft rotation of 360'
z var BIT 'Actual bit if any move of shaft to any direction
h var BIT 'direction bit 0 for left , 1 for right turn of encoder
ADValue VAR word '<---This is your variable.
Setpoint VAR WORD '<---This is your variable.
Direction var bit '<---Direction bit to motor driver
pid_Kp = $0700 'Set Kp to 7.0
pid_Ki = $0080 'Set Ki to 0.5
pid_Kd = $0225 'Set Kd to 2.14
pid_Ti = 8 'Update I-term every 8th call to PID
pid_I_Clamp = 100 'Clamp I-term to max ±100
pid_Out_Clamp = 511 'Clamp the final output to ±511
s = 0
f = 0
z = 0
x = 0
y = 0
h = 0
PIR3.2 = 0
setpoint = 0
pause 250
lcdout $fe,1
'************************************************* ***************
' Osnovna petlja *
'************************************************* ***************
on interrupt goto PIDcalc

loop:
if portc.3 = 1 then setpoint = setpoint + 1
if setpoint > 6250 then setpoint = 0
LCDOUT $fe,128,"POSITION = ",dec y
LCDOUT $fe,192,"SETPOINT = ",dec setpoint
LCDOUT $fe,148,"PID = ",dec pid_out
LCDOUT $fe,212,"PWM = ",dec f," PE = ",dec ABS pid_error
LCDOUT $fe,128,"POSITION = " 'clear line without use $fe,1
LCDOUT $fe,192,"SETPOINT = " 'clear line without use $fe,1

goto loop

disable
PIDcalc:
INTCON.1 = 0
if INTCON.2 = 1 then
z = 0
PosHigh = POSCNTH 'Get high byte of counter
PosLow = POSCNTL 'Get low byte of counter
PosTemp = POSCNTL 'Get low byte again
If PosLow - PosTemp = 0 then Goto Done 'Compare, if equal we're done.
PosHigh = POSCNTH 'If not, get values again.
PosLow = POSCNTL
z = 1
Done:
Position = POSHIGH * 256 + PosLow 'Put high and lowbyte together.
h = QEICON.5 'QEICON.5 for right = 1 for left = 0
if PIR3.2 = 1 and h =1 then x = x+ 1
if x <> 0 and PIR3.2 = 1 and h =0 then x = x- 1
PIR3.2 = 0 'PIR3.2 maxcount transition down or up was detected
h = 0
y = (1000 * x) + position ' increment total counter
if z = 1 and h = 0 then y = y -(1000-position) ' dectement total counter
if y > 6250 then
x = 0
endif
advalue = y
pid_Error = Setpoint - ADValue
gosub PID
Direction = pid_Out.15
pid_out = ABS pid_Out
s = (512 - pid_Out) + 512
select case direction
case 0
f = pid_Out
case 1
f = s
end select
PDC0L = f.LowByte
PDC0H = f.HighByte
endif
INTCON.2 = 0
resume
enable
end

HenrikOlsson
- 27th September 2009, 10:23
Hi Robert,
I'm sorry but I'm having a hard time understanding you. I like to help you but it's just hard when you don't even answer my questions. Now you say it's close to being PID? What exactly is it that doesn't work?

If the motor isn't "stiff" enough you need to tune the PID parameters, I see you have Kp, Ki & Kd exactly as in my example code - which is just that - an example. You need to change them to suit your application.

Also, you could as well changed your code to read pid_Out_Clamp = 640 instead of hardcoding 640 into the incPID routine. In any case if you only have 10bits of PWM resolution why allow the output to swing to +/-640 which is 11bits?

Another thing I notice is that you preset TMR 0 with the value 237, but you do that only at the beginning of the program, so it will go from 237->256, cause an interrupt, then it will start from 0 again. With the pescaler at 1:256 that will be an interrupt rate of 38Hz which might be a bit slow. You need to preset TMR0 again in your ISR.

You can change this:


advalue = y
pid_Error = Setpoint - ADValue

To simply read


pid_Error = Setpoint - y

It won't work any better but a little faster and it'll save you a byte or two ;-)

And, try removing the LCDOut statements as a test and see what impact that has on it. Do you have an oscilloscope? Connect it to a pin, set the pin high the first thing you do in the ISR and low again before returning - then you can see if you have the correct interrupt frequency.

phoenix_1
- 27th September 2009, 13:12
ok in UHU or any other PID control if POSITION= SETPOINT motor must clip +/- for to make break in position.
when you set SETPOINT increment +1 or +XXX then PID must move motor in that direction and when MOTOR come to SETPOINT PID must again clip motor +/- in new POSITION becous POSITION is again = SETPOINT.
If you probe to mechanic move MOTOR SHAFT from locked position PID rutine must give reaction to back motor to lock POSITION.
Your PID control work good maybe with L2XX motor driver chip but with real output driver like HP UHU output stage NOT work.
In uhu sistem you have two pins on output stage.One for motor enable / disable (logic 1 enable , logic 0 disable.That is use for motor protect with current detector via resistor in DRAIN of output MOSFETS.
Second pin is in same time PWM drive / direction.
When PWM on these pin is 50/50% that is 512 on 10bit then motor is in stop condition.When on these pin come PWM from 512-0 you have movement of motor from slow 511 to full spid PWM close 10.And when you go from 513 to 1024 you have slow to full speed of motor at close 1014.
My motor have gear 6.25 motor full turn for one shaft full turn.That mean I must use wariable for position storage from 0 - 6250 which represent full range for one full turn of output gear shaft.
When I use your PID rutine it break motor to go from lock position to minus CCW and it give PWM to motor 511 , 510,...492...400 and move motor CW to lock dead point.But when i move output shaft to CW position your PID giwe PWM 511 and normaly that must be to give 513,515....600 to mowe motor in CCW to motor come to lock point.That is becouse in your position is :


'************************************************* ******************************
'************************************************* ******************************
'Calculate the total drive.

pid_Out = pid_P + pid_I + pid_D 'Calculate total drive....

pid_Sign = pid_Out.15 'Save Sign
pid_Out = ABS pid_Out 'Convert from two's comp. to abs.
if pid_Out >= pid_Out_Clamp then 'Output is saturated...
pid_Status_Out_Sat = 1 'set status bit and...
pid_out = pid_Out_Clamp ' pid_Out_Clamp = 511

Endif
If pid_Sign then pid_out = -pid_out 'Re-apply sign.

RETURN 'And return to sender.
SkipPID:

Ok and your suggestions are good but I was spend 10..hours and my head is like baloon...sorry.
Regards Robert

HenrikOlsson
- 27th September 2009, 16:22
Hi Robert,
Don't worry, I know the difference between sign & magnitude and locked antiphase.

If you're working with locked anti-phase, like the UHU chip does, then you need to look at the output from the PID filter, if it's positive you add to the dutycycle and if it negative you subtract the ABS value of the PID_Out from the duty cycle. Basically....

'At startup set the PWM dutycycle to 50%
PID_Error = Position - Setpoint
Gosub PID

If PID_Out.15 = 1 then 'Output is negative
DutyCycle = 512 - ABS PID_Out
Else 'Output is positive
DutyCycle = 512 + PID_Out
Endif


So again, if your dutycycle register is 50% (512) when no torque is delivered (ie. you are working with locked antiphase) then you really should clamp the output of the PID to +/-511, if you don't do that the dutycycle register may over- or underflow. If, for example, the output of the PID filter is allowed to swing to -640, and you subtract that from 512 you end up with -128. -128 in a 16bit variable is the same as 65407 so if you take the lower 10 bits (=895) of that and stuff it into your dutycycle register you'll end up with a dutycycle of 87% instead of 0 - torque will get delivered in wrong direction.....

Also, with a powerstage such as the one on the HP-UHU you need to make sure that dutycycle never reaches 0 or 100% because then the bootstrap capacitors in the highside MOSFET drivers no longer have any time to recharge and bad things will happen.

And, to show you that it does work, here's a link to a video I just uploaded:
http://www.youtube.com/watch?v=GMb4u0KuO4o

Yes, it uses sign & magnitude and no it's not step and direction. It takes serial commands from a host CPU or PC. I've also got a couple of videos there featuring the HP-UHU. I've been quite involved in the HP-UHU project so I know it pretty well.

And as a friendly note, if you think that this PID-code doesn't work for your application then by all means feel free to use something else.

Sincerely,
/Henrik.

phoenix_1
- 27th September 2009, 17:12
No I mean it work good and many thanks to you for writing that...
My best regards and I will continue to test it for my machine.
Regards Robert

phoenix_1
- 24th October 2009, 20:23
I runing your PID rutine in 18F4431 and output is locked antiphase , all work like you explain me . I use two interrupt's one for position reding - QEI module and second to calculate PID , in main loop is only increment of setpoint. No Usart , no DEBUG or LCDOUT.
From time to time not a linear in time it have some little noise which I can also see with scope in PWM out and I can't image from where is ?
Simple motor have jig - agitation in range of couple step's to +/- and after couple next step's it go linear.
I use only CW rotation not CCW all the time and PID becouse it good magnetic and precise break in position.
Can it be from bad tuning of P,I,D parameter's ?
Output amplifier is copy of HP UHU with 4 x IRF540N.
Simple I am lost - it is not disaster but meke me little problems on my machin.
Best regards
\ ROBERT

HenrikOlsson
- 25th October 2009, 17:26
Hi Robert,
Why do you have the position reading and PID-calculation triggered by different interrupts? You should run the PID as soon as possible after getting the current position otherwise you're working with "old" information. One timer interrupt at: Get position, get setpoint, calculate error, run PID, set PWM, other housekeeping (if necessary) - done.

Can't say for sure... Perhaps the glitch appears when the QEI position register wraps from MaxCNT to 0 and/or from 0 to MaxCNT. There's also this thread (http://www.picbasic.co.uk/forum/showthread.php?t=6300) which discusses the HPWM and how to change the dutycycle without upsetting it but IIRC you're using the power control PWM module so it doesn't apply.

Have you solved the other issue when the setpoint is 0 and the motor overshoots?

/Henrik.

phoenix_1
- 25th October 2009, 21:21
Hi Henrik,
Yes I was today put all in one interrupt (TMR1 , 16bit and make little trick)
Interrupt fire at 10KHZ but I have counter variable wich increment every interrupt jump and if it not 10 then servo not operate over jump servo and pwm update but when that variable mach 10 servo will operate and reset variable to zero, that meen I have 10khz interrupt with 1khz servo drive.
After that all work almost perfect but problem is still here and maybe it is in PWM update or I don't know - simple I am near it.
We will see soon...
Regards
/Robert

mlmccauley
- 22nd November 2009, 04:12
I need to get a simple PID application going using a PIC, and this PID include looks to be ideal for what I need to do.

The system I'm working with doesn't incorporate an output device that accepts a direction bit. Everything is a positive variable i.e. the setpoint value, feedback value, and the output to the process (a value that I'll plug into a hardware PWM channel). Specifically, everything operates within the span of a WORD variable, 0-1023.

If I'm properly interpreting how the routine works, I'll need to edit the include file to make the sign indicator passed to the include, currently B.0, a bit variable, and then do a sign determination in the main calling program to indicate if the error signal that I pass to the PID sub is below or above the setpoint.

Likewise, I'll need to take the output of the PID include that gets passed back to the main process, and add/subtract the new output with the last output based on the direction bit, currently B.15.

OR

Can I simply make the direction variable passed to the PID a constant 1, and ignore the sign information returned from the routine?

OR

Do I have this all wrong?

Thanks in advance for the help!

Mike

HenrikOlsson
- 22nd November 2009, 10:51
Hi Mike,
There should be no need to edit the include file for this, I don't think.

All you need to do is calculate the error and send it to the PID-filter, it determines if the error is positive or negative, calculates the "drive" and returns it in the variable PID_Out. You never need to pass any "direction-bit" TO the PID-routine.

Now, if you're driving something like a heater where you can only deliver power in "one direction" (you can just add more or less heat, you can't remove it) then you simply look at the sign-bit of the PID_Out variable (PID_Out.15) and if it's set (meaning negative "drive" becuse the temperature is too high) you simply set the output to 0.

OR (and this is probably a better aproach)

Let's say you know that under normal conditions you need a duty cycle of ~30% to keep the temperature fairly constant. Now you look at the sign of the value in the PID_Out variable and depending on its state you either add or subtract the ABS value of PID_Out from whatever value corresponds to 30% duty cycle. Here you need to make sure you clamp the end value between 0 and 1023 before moving it to the duty cycle register.

Does this make sense?

/Henrik.

mlmccauley
- 23rd November 2009, 16:42
Thanks for the reply.

Yes, makes sense to me. The process does accept a variable input, not on-off. It's a DC drive that I'm generating a reference voltage for using hardware PWM on a 18F2320 @ 40MHz. Sounds like this should work out fine.

Once again, thanks!

Mike

HenrikOlsson
- 23rd November 2009, 18:59
Yes, what I meant with set the output to 0 was that if the ouput of the PID filter goes negative you "clamp" the dutycycle at 0 since you can't effectively have a negative drive with that system.

In any case the second aproach should work better. Just a note though, in your first message you wrote:
Likewise, I'll need to take the output of the PID include that gets passed back to the main process, and add/subtract the new output with the last output based on the direction bit, currently B.15.
You don't add/subtract the PID filter output from the last output, you add/subtract it from the initial "steady state value". I guess you had that figured out but just in case.

Let me know how it goes or if there's anything I can do.

/Henrik.

mlmccauley
- 24th November 2009, 01:30
The routine works like a champ!

Did the first trial run-up today. No problem at all with mono-polar operation. A few tweaks on the constants and it's working great. Need to make a small hardware change in external circuitry, and it'll be wound up.

Again, thanks! A very handy tool to hang onto.

mlmccauley
- 25th November 2009, 06:13
A word of advise to anyone who needs to do PID control with a PIC, and is thinking of using this subroutine but is concerned about how well it works, you needn't worry. I used it to implement a fairly tough industrial application, and it worked like a champ. Highly recommended!

Mike

HenrikOlsson
- 25th November 2009, 10:30
Thanks a bunch Mike, it's always nice to hear when things works out!

If you don't mind (and are allowed) to disclose some details about the system I'd love to hear about it. Like what it is you're controlling, sensor, sampling rate, how you tuned it etc. I think it would make a nice post for future (and current) users of the PID-routine.

I've been considering an update of the routine for quite some time, you gave me the motivation to move forward with it. I just need to get my PBP260 working on my main PC here (looks like an OS re-install is needed and I hate doing that). I'll try adding feed-forward and a few other things, mostly tailored towards motor-control although probably useful in other applications as well.

Thanks again Mike!
/Henrik.

mlmccauley
- 26th November 2009, 01:32
The application was a rewind control on a rather elderly commercial printing press.

The original setup was a commercial PID board made up of *many* op amps. At its best it was cranky acting when it was working well, and it had been going flaky for some time. When it finally failed, and I determined that it had chips on it that I had no idea how to get and that the OEM had discontinued the board long ago, I decided to replace it with a digital implementation.

The system consists of a 50HP speed regulator DC drive and a control board that accepted an input from a pot set by the machine operator that determined the desired dancer position (check out Wiki or the like if you're not up on compressed air loaded printing press dancer rewind setups). A gear driven pot provided feedback from the dancer, indicating its position.

The drive required a 0-15VDC reference signal. I built up a board consisting of a +18 and +5 supply. +5 supply was run out to the operator and dancer pots. Those two signals became setpoint and feedback, respectively, and were dumped directly into PIC A/D pins. Half of a LM358 op amp fed by a simple RC filter took the PIC PWM output, smoothed it, and raised it to 0-15VDC.

My calling app sets the reference voltage to the drive to zero should the PID routine ever call for a negative, but that's probably superfluous.

Took less than an hour to set up the P, I & D constants to achieve better dancer control than the original had *ever* provided.

I have no doubt that the identical hardware, with no changes other than retuning in software, could do a perfectly acceptable job in any web material rewinding application. And speed regulator drives are simpler to set up, less expensive, and more readily available than the torque regulator drives that I've used in similar applications in the past.

Again, thanks for your help and use of a very handy routine!

Mike

HenrikOlsson
- 26th November 2009, 07:12
Thanks Mike!
A 50HP DC drive...that's cool!

/Henrik.

DDDvvv
- 2nd January 2010, 00:25
first of all, a big thanks to mr henrik for this wonderful code. it works, as i have it all set up right here.
but instead of locked antiphase, im trying to implement a h-bridge powered by the two hpwm (forward/reverse motion)on a 18f4431.
i have a lcd displaying variables like position, setpoint, dutycycle1(for hpwm0) and dutycycle2(hpwm1)

Main: ; main loop

LCDOUT $FE, $80, "POS=" ,DEC5 POSITION, " SP=" ,DEC5 SETPOINT
LCDOUT $FE, $C0, "DUTY1=" ,DEC3 DUTY1, " DUTY2=" ,DEC3 DUTY2
IF PORTB.3 = 0 then SETPOINT = SETPOINT + 1 ; Increment setpoint
select case setpoint ; Circular count for Setpoint condition
case is > 65535 ; Circular count for Setpoint condition
setpoint = 0 ; reset setpoint
POSCNTH=0 ; set counter for encoder, H bit ; reset QEI counter high byte
POSCNTL=0 ; set counter for encoder, L bit ; reset QEI counter low byte
end select
;SELECT CASE PID_ERROR
IF POSITION<SETPOINT THEN ; End of Circular count for Setpoint condition
pid_Error = setpoint - position
;GOSUB FORWARD
ENDIF
IF POSITION>SETPOINT THEN
PID_ERROR = POSITION - SETPOINT
endif

gosub pid ; go to PID rutine
DIRECTION = PID_OUT.15
select case direction ; condition determine PWM
case 0 ; condition determine PWM
Duty1 = abs pid_out
GOSUB FORWARD
;DUTY1 = 0 ; condition determine PWM
case 1 ; condition determine PWM
Duty2 = ABS pid_Out
GOSUB BACKWARD
;DUTY2 = 0
END SELECT

goto Main ; do main loop

sub forward turns on first hpwm and sub backward turns on second hpwm.

here is the problem: if i increment/add to the setpoint, both duty cycles reset when position rolls over from 65535 to 0. i know its something to do with my integer math, and ive spent a whole week on this issue. im trying to implement a step/dir routine, for my cnc machine.

any suggestion will be highly appreciated. thanks

HenrikOlsson
- 3rd January 2010, 01:38
Hi,
I'm not sure I understand....sorry....

First of all, the SETPOINT variable you have - is it declared as a LONG or as a WORD? If declared as a WORD then I'm not sure how this...

select case setpoint ; Circular count for Setpoint condition
case is > 65535 ; Circular count for Setpoint condition
...will work or what it'll evalute to. A word variable will "automatically" wrap around from 65535->0 and from 0 to 65535. If setpoint is declared as a LONG that seems not needed since you're trying to reset it 0 once it goes above 2^16.

Then there's this line:

IF POSITION pid_Error = setpoint - position
Does it really compile like that?

If you have code that compiles correctly, please post it and I promise I'll do my best to help you. You may also be interested in this thread (http://www.picbasic.co.uk/forum/showthread.php?t=12399)

/Henrik.

DDDvvv
- 3rd January 2010, 05:10
thanks for the quick reply

the setpoint is a word variable, and resets itself from 65535 to 0 and vice versa.
there must have been an error when i copied and pasted, because the sentence reads:

pid_error = setpoint - position

this is a very buggy piece of code, and when i get it working, im going to rearrange and clean it up good.

hadiengg
- 13th February 2010, 19:32
Hi to all.
Come back after so many time.
I run dc motor and use same code made no change.
It controls the speed fine but i found that there is always a difference between reference signal and feedback signal.Due to this motor speed increase enough when we increase proportional gain.
Is this is a problem or not ?

HenrikOlsson
- 14th February 2010, 09:02
Hello,
If you really are controlling the speed of the motor and not the position then there shouldn't need to be any difference, if the speed never reaches the setpoint you need to increase the I-gain. That should make it come closer.

If you use the "moving target" aproach, ie. you are in reallity controlling position then, as long as the motor is moving, there will always be a discrepency between the target and actual position, called following error. The speed however, should match the command , otherwise the following error would just grow larger and larger.

/Henrik.

HenrikOlsson
- 8th May 2010, 17:01
Hi,
First, bugfix....
I've discovered a bug/problem with the original incPID routine. When there's a persistant error present at the filter input the integrator for the I-term is supposed to accumulate and over time produce an output that will drive the error away. However, a small error in conjunction with low I-gain may actually never accumulate enough to produce an output signal like it's supposed to. This was due to truncation of the numbers during calculations. This version implements a fix for this issue.

Be careful if you move from the previous version to this as the I-term may behave quite differently depending on how your particular application is set up. I've tested it here and it seems to work but please let me know if you find any problems.

Second, new feature...Feed-forward
While a PID filter by itself works on the difference between the "setpoint" and the "target" feed-forward works directly on the command signal. Let's say you're controling the speed of a motor, the friction in the bearings, brushes and in the load the motor is driving causes it to need a certain "baseline" amount of power for any given speed in order to compensate for its mechanical and electrical losses. Feedforward can provide this "baseline" which helps the actual filter and can increase the overall system response.

There are three new "external" variables that you need to use with feed-forward:
pid_Vel_Kff This is the velocity feedforward gain.
pid_Acc_Kff This is the acceleration feedforward gain
pid_Velocity_cmd This is the actual command signal.

As motor control is my personal use for the incPID routine I've used the terms velocity and acceleration but it's applicable to whatever you are controling. Lets take an example where we want to control the temperature in an oven.
By imperical testing you have determined that in order to maintain your desired temperature (lets say 150 degrees) under normal/optimal/stable conditions you need an "output" of 800 to your "powerstage". If you set pid_Vel_Kff to $0600 and pid_Vel_cmd to 150, this will result in a "baseline" output of 800. Then you calculate the difference between the setpoint (150) and the current temperature and send it to filter in pid_Error. The output from the actual PID-filter then gets added to or subtracted from that 800 number in order to compensate for any disturbances that occurs.

The acceleration feedforward works in the same way as the derivative term of the PID filter but instead of acting on the derivative (ie. the difference) of pid_error it acts on the derivative of pid_Vel_cmd.

More info is available in the file itself.

I've tested this quite a bit but as always I'm sure there are things I've overlooked. If you have problems, please post in the thread and we'll try to work it out. If you don't have problems and it just works that's always nice to hear as well ;-)

Sincerely,
/Henrik.

(Change file extension from .txt to .pbp and place it in your project folder or in the PBP installation folder).

HenrikOlsson
- 6th June 2010, 15:55
Hi,
For anyone interested in motor control, here's a link to a Youtube video showing my latest project based on the PID code available in this thread. It's HP-UHU servo drive on which I've replaced the original UHU servo processor with my own "servo module" that I've made pin-compatible with the original chip.

http://www.youtube.com/watch?v=U-jrxK7u_34

<object width="640" height="385"><param name="movie" value="http://www.youtube.com/v/U-jrxK7u_34&hl=en_US&fs=1&"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/U-jrxK7u_34&hl=en_US&fs=1&" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"></embed></object>

/Henrik.

ScaleRobotics
- 6th June 2010, 16:26
Henrik,

You have done excellent work! I tried to look up the HP-UHU servo drive you used, but all I seemed to get was this link: http://www.cnczone.com/forums/showthread.php?t=67453. Is there more information on the servo drive you used somewhere else?

Thanks,

Walter

HenrikOlsson
- 6th June 2010, 17:01
Thanks Walter,
That's its biggest drawback I'd say - the information about it being scattered around several threads on the CNCZone, there are some links in that thread you posted that will take you to other threads about it. There's also some info in this Wiki (http://gsst.wikispaces.com/)

As you probably figured it's not a comercial drive, it was developed by a CNCZone member called Kreutz in effort to get a DIY drive for high(ish) voltage motors where the original UHU-circuit failed to deliver. Kits were made (and probably IS) available by Paul Harshman (tenmetalman) and Irfan Ulla, also on CNCZone at very reasonable prices.

Being involved since the early stages of the HP-UHU's life I know it pretty well so if you have specific questions regarding it fire away and I'll do my best to answer them. Perhaps it's better to keep it separate from this thread though.

Thanks!
/Henrik.

EDIT: Thank you for embedding the video by the way!

Ioannis
- 28th December 2010, 22:30
Here is my first try on PID control. Obviously failed...

At the attached schematic is the part of the circuit, essentialy a Voltage stabilizer. The PWM from the PIC drives the Power stage while from the output a voltage sample is returned to the ADC.

Unfortunately there is no control at all. When Setpoint is 0 or 1023 the output is always high (pid_out is 511).

Also I noticed that Direction bit is high when Voltage sample is higher than Setpoint. Is this correct?

Test PIC is F887 driving the internal HPWM.



'************************************************* *************************
'************************************************* *************************
'**
'** PWM Controller with voltage level feedback
'**
'** 21.11.2010, v.1.0
'**
'** PID Control by Henrik Olsson
'************************************************* *************************
'************************************************* *************************

DEFINE OSC 8

OSCCON = %01110001 'SET SYSTEM CLOCK SWITCH BIT

;----- Configuration bits ------------------------------------------------

@Line1 = _DEBUG_OFF & _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_ON
@Line2 = _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_ON & _INTRC_OSC_NOCLKOUT

@ __CONFIG _CONFIG1, Line1 & Line2

@ __CONFIG _CONFIG2, _WRT_HALF & _BOR40V


PORTA=0:PORTB=0:PORTC=0:PORTD=0:PORTE=0

TRISA=%00101101
TRISB=%00000000
TRISC=%10000000
TRISD=%00000011
TRISE=%00000000

OPTION_REG.0=1 'PSA0 PRESCALER SELECT 1:1 TO 1:256
OPTION_REG.1=1 'PSA1
OPTION_REG.2=1 'PSA2
OPTION_REG.3=0 'PRESCALER TO: 1->WDT, 0->TMR0
OPTION_REG.4=0 'T0SE SOURCE EDGE 1->H TO L, 0->L TO H
OPTION_REG.5=0 'T0CS 1->FROM RA4, 0->FROM INT. CLOCK
OPTION_REG.6=0 'INT EDGE SELECT 0->H TO L, 1->L TO H
OPTION_REG.7=0 'PULL UP 1->DISABLE, 0->ENABLE

WPUB = %00111111

'DEFINE HSER_RCSTA 90h
'DEFINE HSER_TXSTA 24h
'DEFINE HSER_BAUD 19200
'DEFINE HSER_CLROERR 1 ' Clear overflow automatically

'************************* Define LCD parameters

ADCON0 = %11000001
ADCON1 = %10000000 'Set PORTA/E analog/digital, right justify result
ANSEL = %00101101 'lower port A as Analog input
ANSELH = %00000000 'all others Digital inputs

DEFINE ADC_BITS 10 'Number of bits in Adcin result
DEFINE ADC_CLOCK 3 'ADC clock source (rc = 3)
DEFINE ADC_SAMPLEUS 50

but_0 var portd.0
but_1 var portd.1


'************************************************* *************************
'****
'**** Initialization
'****
'************************************************* *************************
clear

ADValue VAR WORD '<---This is your variable.
Setpoint VAR WORD '<---This is your variable.
Direction var PortB.7 '<---Direction bit to motor driver

p_gain var word
i_gain var word
d_gain var word

Setpoint=200

INCLUDE "incPIDv1.5.pbp" 'Include the PID routine.

'These variables are declared by the incPID routine but
'the user needs to assign values to them.
' pid_Kp = $0700 'Set Kp to 7.0
' pid_Ki = $0080 'Set Ki to 0.5
' pid_Kd = $0225 'Set Kd to 2.14
pid_Ti = 8 'Update I-term every 8th call to PID
pid_I_Clamp = 100 'Clamp I-term to max ±100
pid_Out_Clamp = 511 'Clamp the final output to ±511

Start:
Gosub analog 'Get speed from tacho and setpoint pot - NOT SHOWN HERE.

pid_Kp=p_gain:pid_Ki=i_gain:pid_Kd=d_gain

pid_Error = Setpoint - value 'Calculate the error

Gosub PID 'Result returned in pid_Drive

Direction = pid_Out.15 'Set direction pin accordning to sign
pid_Out = ABS pid_Out 'Convert from two's comp. to absolute
HPWM 1, pid_Out, 500 'Set PWM output

'Just for monitoring
serout2 portc.6,16468, [27,"[10;0H","P:",dec5 p_gain," I:",dec5 i_gain," D:",dec5 d_gain," PID out:",#pid_out," ",27,"[12;0H","Vout:",dec5 value," SP:",dec5 setPoint," pid_err:",dec5 pid_error, 27,"[14;0H","Direction: ",#direction]

Goto Start '...and do it again.

analog:
adcin 0,value
gosub average
adcin 2,p_gain
p_gain=p_gain<<2
adcin 3,i_gain
i_gain=i_gain>>2
adcin 4,d_gain
d_gain=d_gain<<2

'Two digital inputs for setting Setpoint level
if !but_0 then
if Setpoint>0 then
setpoint=setpoint-2
endif
endif
if !but_1 then
if Setpoint<1021 then
setpoint=setpoint+2
endif
endif
return

END


I am using 3 Analog inputs to set the P,I,D values as it is clear from the analog subroutine.

Whatever the values the pid_out stays at 511 and plays a litle at the point where Setpoint is almost equal to the Vout level.
Any inputs welcome.

Ioannis

Darrel Taylor
- 28th December 2010, 23:32
Ioannis,

I think you need to limit the pid_Out values to only positive numbers, and discard the negative values instead of converting them to positive ones.

The way you have it ... the more it tries to reduce the voltage, the higher the output goes.

After it gosubs to the PID routine ... Try this ...
IF pid_Out.15 THEN pid_Out = 0 ' limit to positive values
HPWM 1, pid_Out, 500 'Set PWM output

HenrikOlsson
- 29th December 2010, 07:17
Hi Ioannis,
I'll just second what Darrel said. For unidirectional control, ie when you can only apply more or less power, not remove power from your "plant" you can't have the output swing negative (which the ABS convert to positive again).

The example was for controlling a motor where the direction signal basically controls the polarity and the applied torque is controlled by a PWM signal corresponding to the absolute value of the PID signal. That way torque can be applied in both directions.

In your case you can only apply more or less power, you can't 'reverse' or remove 'power'. Because of that you can't allow negative 'drive'.

As fir the polarity of the direction bit, that's just a matter of matching it to the hardware I used at the time. If I reversed the polarity of the motor I'd have to reverse the "polarity" of the direction signal. Again, you don't need it in this application.

/Henrik.

Ioannis
- 29th December 2010, 10:27
Ok, thanks both. I was stupid.

Now, I 've got a wonderful oscillator!

No matter the level of P,I,D (except for 0) it does oscillate.

I suppose it is a matter of tuning, but even with the trimmer as analog inputs to the P,I,D variables, it seems hard to fine tune it.

When I think I done it, after a power cycle it start again at a rate of 2-5Hz.

Ioannis

Darrel Taylor
- 29th December 2010, 10:50
Whatever the values the pid_out stays at 511
Oh, and clamp the output to 255 for the HPWM command.

You may want to use 10-bit PWM for finer control.

Ioannis
- 29th December 2010, 11:39
Hi Darrel.
Yes I did clamp the output to 255 although it was not mentioned in my previous post.


You may want to use 10-bit PWM for finer control.

Is this possible with PBP or do I have to mess directly with the PIC registers?

My next step will be to use MiBAM, as I do not have hardware PWM on the final PIC.

Ioannis

HenrikOlsson
- 29th December 2010, 12:14
Hi,
Start with pid_Ki set to 0. Increase pid_Kp and "bump" the regulator (change the setpoint) do this a couple of times until you see it begin to overshoot or a tendency to oscillate.

Increase pid_Kd in order to damp out the overshoot.

Now you can try to increase pid_Kp even further, once again it will overshoot or start to osciallate, follow with pid_Kd.

This should give you a stable loop but it will most likely never hit the actual setpoint. Slowly increase i pid_Ki to bring the value right up to the setpoint. Increasing pid_Ki will introduce an overshoot which you might should be able to reduce by further increasing pid_Kd.

With that said it's crucial that you run the PID (read input, run PID, update output) at a constant interval. If the time between updates varies you won't get a stable output.

/Henrik.

Ioannis
- 29th December 2010, 12:31
Thanks Henrik.

What is the range for the p,i,d variables?

Or is it absolutely project dependant?

Ioannis

HenrikOlsson
- 29th December 2010, 14:04
Hi,
They are all WORD variables so 0-65535 in theory (each representing a gain of 0-255). However, there is no protection from, or detection of, overflow in the calculations that may be the result of very high gains and very large errors.

/Henrik.

Darrel Taylor
- 30th December 2010, 00:45
(10-bit PWM)
Is this possible with PBP or do I have to mess directly with the PIC registers?

My next step will be to use MiBAM, as I do not have hardware PWM on the final PIC.

You might try HPWM10 for now. It's just like HPWM ... almost.
http://www.picbasic.co.uk/forum/showthread.php?t=6300&p=37805#post37805

MIBAM is only 8-bit, but if you only have one output it'll be better to create a higher resolution interrupt driven PWM that could also be a time base for the PID loop.

Ioannis
- 30th December 2010, 09:42
Thanks for the tips.

By he way, comapring the MiBAM and Multi_SPWM which is lighter? I suppose Multi_SPWM.

On my final board, PIC will be probably the old F819, so I do not have the luxary of HPWM.

Maybe the Interrupt driven software and PID is the ultimte solution.

Currently I am dancing, err, oscillating...

Ioannis

HenrikOlsson
- 30th December 2010, 12:49
Hi Ioannis,
At what frequency are you currently running the PID-loop?

You have a fairly large timeconstant, 0.22s if I'm not entirely mistaken. Depending on the frequency you're running the loop at this might be a source for your instability. I'd try setting up a timer interrupt to get constant and predicatble timing and try running the PID-loop at something like 5Hz.

The variables pid_P, pid_I and pid_D contains the "effort" each term contributes to the total output with. Those might be interesting to "watch" as you tune the loop.

Also, you might gain quite a lot by using the velocity feed forward. For example, you can't possible get 12V output without feeding the circuit atleast a 50% dutycycle (provided the load doesn't regenerate etc). The velocity feedforward can help you provide this "baseline" output.

If I'm not mistaken you would, for an output of 12V, get a return value from the ADC of ~430 and therefor the setpoint value for a 12V output is 430. To set the velocity feedforward so that it outputs a 50% dutycycle for a setpoint of 430 you set pid_Vel_kFF to 74(dec) because 128/430=0.29 (128 being the value for 50% dutycycle) and 74/256 = 0.29 (because the gain is expressed in 1/256 units).

Finally you must also set the pid_Velocity_cmd variable to equal your setpoint value whenever you change it.

Now the PID regulator only has to worry about "external disturbances" such as power supply voltage variation, load variation and so on.

/Henrik.

Ioannis
- 30th December 2010, 14:36
Hi Henrik.

Hmm, I do not have a time base for the PID. It is in the close loop of the main. Sure is not predictable rate.

Also I noticed that using a 470uF capacitor in the place of the 10uF help a lot, so your point of using a slow like 5Hz rate is a good one.

I did not used the Velocity so far, it is a good poin also. Will try it an report.

Thanks both for the help.

Ioannis

HenrikOlsson
- 30th December 2010, 15:14
Hi,
The very first thing to tend to, IMHO, is to make the update rate as stable as possible - it is very important for stability. If you haven't already seen it, here's a pretty good article (http://igor.chudov.com/manuals/Servo-Tuning/PID-without-a-PhD.pdf) on digital PID filter implementation. It's a good read even if you're not actually writing the PID filter code - just using it.

Keep at it and keep us posted!

Ioannis
- 30th December 2010, 21:45
I recall downloading that article, but did not read it. Will do.

My pre-PID implementation was based on a loose control loop like this one:



if out_value < (setpoint - 1) then
pwm_signal=pwm_signal+1
endif

if out_value > (setpoint + 1) then
pwm_signal=pwm_signal-1
endif


It is a very simple idea that gives also a hysteresis of +/-2 points. So if the out_value is within the "dead band" nothing happens.

The down side is that, depending on the overall loop it may take too long to reach the setpoint and also may do some oscillations also if there is delay getting the correct analog feedback.

My first impressions using the PID is that has the potential to be very fast and accurate, despite the fact I did not managed to make it work reliable yet.

Will come back after correcting the setup.

Ioannis

Rufinus
- 25th January 2011, 13:30
First of all, i would like to thank Mr. Olsson for his great work.
Currently i'm working with the AC heater, driven by the SSR. This heater is very inertial and slow, in particular dropping the temperature.

In order to drive it, i implemented some sort of rude PWM, which looks as following:

---------------------------------------------------------
pid_Out_Clamp=200
' i also set the pid_Out value to 0 in case of direction flag is raised (pid_Sign = pid_Out.15 thing)

loop:
period=2000 'period of PWM in mS
time_on=pid_Out*10 ' i feed to pid filter the temperature, not the ADC value, therefore multiplication in order to scale to the period. Or this is wrong and i should use pid_Out value directly?'
HIGH portc.3
PAUSE time_on
time_off=period-time_on
LOW portc.3
PAUSE time_off
LCDOUT ...
GOTO loop
-----------------------------------------------------------------

Now, i have run a test and the filter seems to work, except it gives an overshoot on the beginning, but then stabilises 1-2 degrees below target.
Now i would have to tune the gains and i tried to understand the explanation of the HEX format, but unfortunately it is not completely clear for me. Are you using this representation in order to have floating point values?

I just can't get the inverse conversion algoritm from, let's say, 2.55 to HEX.
As i undersand, in this case it would be $02..<-- here should come 0.55 part.
So, using your example: "FF represents 1/256*255 or 0.996. If you'd feed it 102 from the ADConverter it would be the same as $0066 or a gain of 0.258."

1/256*x=0.55 --> x=0.55/(1/256) -->140 --> convert to HEX -->8C
Final result: $028C, or decimal 652 <--- is it correct??

HenrikOlsson
- 25th January 2011, 17:08
Hi,
The gains are just numerical values like any other variable. Assigning values to the gain variables using hex doesn't, in itself, allow "floating point" but it makes it easier to visualise what the "real" gain is. Or perhaps not... ;-)

Lets look at the proportional term:

Lets say you set pid_Kp to $0280 (or 640 in decimal if you like). Now you calculate pid_Error to, lets say, 125 and send it to the filter. The filter will then calculate the proportional term to 125 */ 640 which is the same as 125*640/256=312. Or, put another way 640/256=2.5 and 125*2.5=312.

If you set pid_kP to 102 (or $66) you'll get a gain 102/256=0.398 and an output, in the example above of 125*102/256=49 which is basically the same as 125*0.398.

If you know you want a gain of 3.7 then simply multiply that value by 256 to get the value. 3.7*256=947. pid_Kp=947 or pid_Kp=$03B3. Where, in this case $03 means 3 and $B3 means $B3/$FF or 179/256=0.7 ie a gain of 3.7

With the same error as above: 125*947/256=462 which is the same as 125*3.7=462.

So, using HEX doesn't make it floating point per se, it's just another way of assigning values to the variables. Say you wanted a gain of 4, then pid_kP=$0400 is easier to understand then pid_Kp=1024 but they both result in the exact same thing.

/Henrik.

Rufinus
- 25th January 2011, 20:17
Thanks for the prompt reply! Well, now it is clear for me, that it is clear for you :)
And i also got now how it works. But for myself, i would use DEC values, they are much obvious, IMHO of course.

But i do have one more question: what would be a good point to start with pid_I_Clamp?

In your example code you had for pid_Out_Clamp 511 and pid_I_Clamp 100.
If i have pid_Out_Clamp 200, would, say, 40 be a good starting point for pid_I_Clamp?
Also for the sampling frequency you have ser every eight's, don't you think is to high in my case, with the period of 2 seconds?
I ask it, because i just want to realy reduce the possible variables to Kp,d,i, before i start tuning them.

HenrikOlsson
- 25th January 2011, 21:20
Hi,
Sure, use decimal if it makes more sense to you!

Try with pid_I_Clamp at 40 or even lower to begin with. If you then get to a point where the temperature never reaches the setpoint you need to look into if it is because the I-term is saturating or you just need to increase the I-gain.

As for the integrator sampling time I'd try setting that to roughly correspond to the settling time of whatever it is you're controlling. Ie. if run the system "open loop" and have a stable temperature and then increase the PWM dutycycle a couple of steps, how long does it take before the temperature settles at the new temperature? Set the integrator "sample divisor" so that the actual time roughly corresponds to the settling time.

/Henrik.

HankMcSpank
- 7th March 2011, 01:22
Cool Henrik, I think I may have a use for this :)

Ok, this is a whole new area to me, here's what I think I've grasped wrt how this works ....

You have a desired value (setpoint), you take an incoming ADC reading & subtract it from your desired value....the PID sub routine then gets called & it returns a value of pid_out ?

Am I right so far?!!

Ok, what to do with pid_out?

For my intended use, I want to control a 256 step digital SPI pot (which alters the magnitude of an analog signal - an AGC in essence)...I have a 'desired value' ADC sample (a simple 8 bit ADC sample of the wiper from a std analogue pot ittingt between VCC & Gnd) that I want the signal to 'hit', but obviously the digital pot needs to either increment or decrement towards getting the signal to meet the desired value - I'm wondering how I translate the "Direction = pid_Out.15" to be increment/decrementof thedigital pot ...or does it not matter - ie will the value of pid_out just be a value that I can write direct to the digital pot?

What do I need to tweak with this bit...



Main_Loop:
gosub adc
pid_Error = Setpoint - ADValue 'Calculate the error
Gosub PID 'Result returned in pid_Drive
Direction = pid_Out.15 'Set direction pin accordning to sign
pid_Out = ABS pid_Out 'Convert from two's comp. to absolute
how to control the digital pot here?!!!
Pause 10 'Wait....
Goto Main_Loop '...and do it again.


I’m controlling my digital pot like thus…



write_VR1:
LOW CS
SSPBUF = VR1_Select 'put the command byte into SSPBUF
gosub letclear
SSPBUF = VR1_Position 'put the data byte into SSPBUF
gosub letclear
high CS
RETURN

letclear:
IF PIR1.3 = 0 Then letclear ' wait for SPI interupt flag
PauseUs 10 ' 25uS fudge factor
PIR1.3 = 0 ' clear buffer full status
Return


I’m figuring it’s just a case of something like GOSUB write_VR1 & then within there having the data byte be pid_out?

Hand holdng warmly accepted!

HenrikOlsson
- 7th March 2011, 17:22
Hello,

You have a desired value (setpoint), you take an incoming ADC reading & subtract it from your desired value....the PID sub routine then gets called & it returns a value of pid_out ?

Am I right so far?!!
Yes, or the other way around depening on the polarity of the feedback signal in relation to the polarity of the drive signal. So
pid_Error = Setpoint - ActualValue
or
pid_Error = ActualValue - SetPoint

Ok, what to do with pid_out?
That is really up to you ;-)

I'm wondering how I translate the "Direction = pid_Out.15" to be increment/decrementof thedigital pot ...or does it not matter - ie will the value of pid_out just be a value that I can write direct to the digital pot?
Yes, from my understanding of your application your input signal (setpoint) ranges from 0 to 255 and you want the output of the filter to range from 0 to 255 as well. The direction bit in this case is set when the filter wants negative output, not decreasing but negative, something you can't get with your setup.

So, what you need to do is to clamp the output to 0 with something like:

If pid_Out.15 THEN pid_Out = 0
Also, to clamp the output to a maximum of 255 you should set pid_Out_Clamp to 255. Or, which is actually better because it gives the filter more "resolution", set it to 1024 and then divide the final output by 4 before writing it to the pot.

Good luck!
/Henrik.

Lawrence
- 27th March 2011, 20:03
I am doing a mini project for a PID control using PIC18F452 or PIC18F877 but I am not good in C code as I am electricial trained.

Below is my project requirement:
Hardware interfaces :


(1) 4 potentiometers as inputs to set the values of Kp, Ki , Kd and desired speed setting via analogue channels
(2) A dc permanent magnet motor to act as the targeted process to be controlled.


(3) A driver circuit to power the motor to its desired speed , using PWM output control from the PIC controller ( unidirectional control will be sufficient )
(4) A speed sensor to provide speed feed-back signal to the PIC controller ( a tacho-generator mechanically coupled to the motor shaft will be the easiest interface)
Software codes in C :


(1) Speed of the dc motor to be adjustable by the setting of the speed potentiometer.

(2) A simple PID algorithm to enable the motor speed response characteristics to be adjusted via the settings of Kp, Ki and Kd.
I had already done a simple driver circuit to control a motor and it coupled with another motor which provide the output voltage. Can you help me how to kick off to start my c code using pic19F452 for the PID requirement and also help to interface the circuit with my controller with the 4 potential meter intergated?

Ioannis
- 27th March 2011, 23:44
Maybe the best suggestion is tolook for a C lanquage forum.

We do Basic here (Pic Basic by Melabs).

Ioannis

Macgman2000
- 7th November 2011, 18:49
Hello Henrik, All,

I have read through this thread regarding servo PID motor control that Henrik wrote. I need to modify the code for the direction part of the code. My h-bridge has 3 i/o. PWM drive, Portd.0 and Portd.1.

The allowable states are:
portd.0 = 1 and portd.1 = 0
portd.1 = 0 and portd.1 = 1

I see that Henrik uses 1 bit to control direction. can I simply replace his code
"Direction = pid_Out.15 'Set direction pin accordning to sign"

with.....

if pid_out.15 = 1 then
portd.0 = 1
portd.1 = 0
else
portd.0 = 0
portd.1 = 1
endif

I am concerned about messing up timing by introducing these lines of code. Will this affect the integrity of the original code?

Best Regards,
Nick

HenrikOlsson
- 7th November 2011, 19:16
Hi Nick,
Sure, you can do that - it won't have any effect on the PID code itself and it won't have any effect on the timing of the loop if you're not running the loop at extremely high frequencies.

However, your pins will be in an invalid state for a short period of time since you set each individual bit separately. Ie when going from 10 to 01 you first clear the high bit, now the output is 00, then you set the low bit to make the output 01. This is likely not a problem but depending on your particular hardware it's possible that 00 and/or 11 has some either undefined function or perhaps they brake the motor by shorting the two lower or two higher switches of the bridge.

If this prooves to be a concern then you can either fix it with hardware, a single inverter is all you need and you save a PIC pin. If adding hardware is not an option then look into writing all PortD-bits in one go, perhaps something like:

IF PID_Out.15 = 1 THEN
PortD = (PortD & %11111100) + 1
ELSE
PortD = (PortD & %11111100) + 2
ENDIF
I'm sure there are other ways of doing it too.

/Henrik.

EDIT: Aaarh, now the bloody editor keeps messing with my code again, removing the %-sign AND two digits in the binary value following it...

Macgman2000
- 7th November 2011, 19:47
Good point. I will make the suggested change. In your main code you have HPWM 1, pid_out, 10000 .... I know that pid_out is declared a word (2 bytes) and frequency is 10Khz? The HPWM in the manual says it works with 1 byte for duty cycle....can you explain this part of it? How does feeding a word work with HPWM?

Best Regards,
Nick

HenrikOlsson
- 7th November 2011, 19:59
Uhh, I guess it doesn't really...you're the first to notice!
That's an oversight on my behalf as far as the example goes. However, if you clamp the output to +/-255 using pid_Out_Clamp=255 I think it'll work as shown. I think PBP is smart enough to only get the low byte of the word in this case.

All internal calculations in the PID filter are based on word-sized variables so pid_Out should and must be a WORD.

/Henrik.

Macgman2000
- 8th November 2011, 20:18
Hello Henrik,

I just thought you should know that the code works perfectly in my servo application with the mods. I need to tweak constants / gains. Making small incremental changes in position is smooth. Making large changes causes some jerkiness and overshoot but no oscillations!


Best Regards,
Nick

HenrikOlsson
- 9th November 2011, 07:29
Hi Nick,
It's always nice to hear that things work out.
Large step inputs are always "hard" on the filter. It's definitely possible to tune the overshoot out, (lowering I and increasing D should help) but that has other drawbacks. In a servomotor application it's common to have a trajectory planner (think ramp up speed, run, ramp down) before the filter. Basically this "divides" a large step into several small ones making the overshoot you see with a large step input considerably smaller.

/Henrik.

Macgman2000
- 9th November 2011, 16:48
I see...basically no different than a CNC profile step shape going into the motor driver. I will work on the ramp up/down function and see if I can improve things a bit.

I need to control 2 servo motors each with feedback pot. Is it as simple (yet inefficient) as two separate routines one for each servo on the same MCU? or is there a more cleavor way of doing it? Maybe using Flags to switch between the two servo's running through 1 PID routine (not 2 routines)?

Nick

HenrikOlsson
- 9th November 2011, 17:48
Hi Nick,
Exactly, a trapetzoidal or triangular profile works nicely though an S-shaped profile is even better - but harder to compute.

Regarding multiple motor:
You need one regulator for each motor. The reason is that each regulator has memory of the past AND tries to predict the future. If you "switch" the filter between the two motors the filter will get confused and you're going to get very bad results. However, for obvious reasons it's not possible to include the file more than once but you could create a copy of it and append a _2 or whatever to each variable name thru out the code, that should do it.... Not ALL variables needs to be separate though but lets not get into those details now.

I DID make a special version some time back, for Malcolms terarium controller, it allows you to specify "any" number of regulators. It may lack some of the feature added to latest version of the filter but if you do a bit of searching on the forum you should be able to dig it up. If not let me know and I'll see if I can find it.

/Henrik.

jmgelba
- 24th November 2011, 15:34
I have just read this whole thread and its very interesting. I plan to implement this on a buck converter. One question though is I'd like to be able to vary the output as required and providea ramp up time to the set output. To do this I would just have to change the setpoint on the fly, correct?

HenrikOlsson
- 24th November 2011, 18:22
That's correct, when tuned properly it will try it's best to follow the input. Just remember that the setpoint in itself is not the input of the filter, you feed the error (ie difference between setpoint and actual value) to the filter thru the PID_Error variable.

However, I'd imagine that regulating the output of switch mode converter requires quite a bit of bandwidth in the filter....

/Henrik.

jmgelba
- 27th November 2011, 21:13
As short a cycle time as possible is desirable but as the load is the same number of LED's all the time, the current change over time is pretty slow, unless manually changed by the user, it really only changes with the temp rise of the LED's. Input voltage is regulated so that that is fairly stable.

jmgelba
- 5th March 2012, 23:50
Getting back to trying this out finally.

So, I am converting a DC/DC converter that uses a very basic method of current control. I read the ADC value and compare it to the target ADC. If its below, I add +1 to the output HPWM, if its above the target ADC value I -1 to the HPWM value. This works ok if there are no upsets to input voltage or any rapid current changes on the output. It will oscillate quite badly on random occasions too. I think that is ripple on the ADC pin.

So I have just tried this for the first time and I'm getting some very unstable results. It is flickering quite badly. I'm sure this is a matter of tuning. Do you have any advice?
If I turn up the input voltage I also get to a point where it will no longer back off the output and try to maintain the correct current level, I assume this is a symptom of running into the lower level of the clamp?

jmgelba
- 6th March 2012, 01:09
I set pid_Out_Clamp = 240 which is max duty cycle. At that max duty cycle current at the LED with an input voltage of 27.5V should never go above 2.5A, but it does. The set target is an ADC of 480. At those figures the following it the duty cycle:


0000- 240 000 000 000 255 240 240 240 0008- 240 240 240 240 240 240 240 240
0010- 240 240 240 240 240 240 240 240
0018- 240 240 240 240 240 177 240 240
0020- 240 240 240 240 240 240 240 240
0028- 240 240 240 240 240 240 240 240
0030- 240 240 240 240 240 240 240 240
0038- 240 240 030 240 240 240 240 049
0040- 240 240 240 240 240 240 240 240
0048- 203 240 240 240 240 116 110 240
0050- 007 240 240 240 240 081 240 240
0058- 240 240 240 240 240 240 240 240
0060- 158 240 021 129 240 240 240 240
0068- 240 240 240 075 240 240 240 240
0070- 240 240 240 240 149 240 240 240
0078- 240 190 240 240 041 240 240 240
0080- 240 112 240 240 240 240 240 240
0088- 240 147 240 240 203 240 240 240
0090- 240 240 096 240 240 240 240 240
0098- 240 240 240 240 141 240 240 240
00a0- 240 240 240 240 240 126 240 240
00a8- 240 240 240 240 240 240 240 240
00b0- 240 240 240 240 240 240 240 240
00b8- 240 240 240 240 240 240 240 102
00c0- 240 240 240 240 240 082 240 240
00c8- 240 188 240 240 240 240 129 240
00d0- 240 131 240 240 240 240 240 240
00d8- 240 192 240 240 240 240 240 240
00e0- 240 108 240 240 240 240 116 240
00e8- 240 224 240 240 240 013 240 079
00f0- 240 240 240 240 240 240 240 240
00f8- 240 240 240 240 240 240 042 240




And this is the ADC reading: (multiply these numbers by 4 to get the 10 bit actual value 166 = 664 etc etc.)


0000- 165 000 000 000 255 166 166 166 0008- 165 165 166 166 166 165 166 166
0010- 166 166 165 165 166 166 166 165
0018- 165 165 166 166 166 166 165 165
0020- 166 166 166 165 165 166 166 166
0028- 165 165 166 166 166 166 165 165
0030- 166 166 166 166 165 165 165 166
0038- 166 166 166 166 166 165 165 166
0040- 166 166 166 166 166 165 166 166
0048- 166 166 165 166 165 165 166 166
0050- 166 166 166 166 166 166 165 165
0058- 165 165 165 165 166 166 166 166
0060- 166 166 166 165 165 166 165 165
0068- 165 166 166 165 166 166 166 166
0070- 166 166 166 165 166 165 165 166
0078- 166 166 166 166 166 166 165 166
0080- 166 166 166 166 166 166 166 166
0088- 166 166 165 165 165 166 165 166
0090- 165 165 165 165 166 166 165 165
0098- 165 166 166 165 165 165 166 166
00a0- 166 166 165 166 166 166 166 165
00a8- 166 166 165 166 166 166 165 165
00b0- 166 165 166 166 165 165 165 165
00b8- 166 166 165 166 166 165 166 165
00c0- 165 166 165 165 166 166 165 166
00c8- 165 165 166 165 165 166 166 165
00d0- 166 166 165 166 166 165 166 166
00d8- 166 165 165 166 166 166 166 165
00e0- 165 166 166 166 166 166 166 166
00e8- 166 165 165 166 165 165 166 165
00f0- 165 165 166 166 166 165 166 166
00f8- 166 165 166 166 165 166 166 166




And finally, this is the result of pid_Error = Setpoint - CHANNEL1

[CODE0000- 077 000 000 000 255 076 077 076 0008- 077 077 076 076 077 077 076 075
0010- 075 077 076 076 076 077 077 076
0018- 076 077 077 076 076 077 077 077
0020- 077 076 076 077 077 076 076 077
0028- 077 076 076 075 077 076 077 077
0030- 077 076 077 077 077 076 076 077
0038- 076 077 077 076 075 077 076 076
0040- 077 077 076 076 077 077 076 077
0048- 077 076 077 077 076 077 077 076
0050- 075 075 076 076 077 077 076 077
0058- 076 076 077 076 075 077 077 076
0060- 075 076 076 075 077 076 076 077
0068- 076 077 077 076 077 076 076 077
0070- 076 076 075 076 075 076 076 075
0078- 075 076 076 077 076 076 077 076
0080- 076 077 076 076 077 076 075 077
0088- 076 076 075 076 076 077 076 075
0090- 076 076 077 077 076 077 077 076
0098- 076 077 075 077 076 075 077 076
00a0- 075 076 076 077 077 076 076 077
00a8- 076 077 076 076 077 077 076 077
00b0- 077 077 077 078 076 076 076 076
00b8- 077 076 076 076 076 077 077 077
00c0- 077 076 076 077 077 077 077 077
00c8- 076 076 076 077 077 077 076 076
00d0- 076 076 077 076 077 077 077 077
00d8- 077 077 076 076 076 076 077 077
00e0- 076 077 077 077 077 077 077 076
00e8- 076 076 076 076 077 076 077 077
00f0- 077 076 076 076 076 077 077 077
00f8- 077 077 077 077 077 077 076 076


[/CODE]

So, if the max ADC value is supposed to be 480, why is it clamping at a duty cycle of 240 giving a ADC value 664?

jmgelba
- 6th March 2012, 05:24
Well I'm not sure if I'm making progress but here a few things I've noticed.

First, I cannot get this to maintain a stable output current at all. It will vary along with the input voltage.

Second, The flickering is, depending on input voltage, the pid_out going from the clamp limit (240) to what I assume to be a pid derived number. Depending on the input voltage, that number is anywhere from 0 - 240, however the range of input voltage is tiny and nowhere near the setpoint. See the code below:



0000- 240 000 240 035 240 036 240 035
0008- 240 036 240 036 240 037 240 035
0010- 240 038 240 036 240 037 240 036
0018- 240 037 240 038 240 036 240 038
0020- 240 036 240 036 240 037 240 036
0028- 240 035 240 037 240 037 240 036
0030- 240 036 240 035 240 037 240 036
0038- 240 036 240 036 240 037 240 035
0040- 240 037 240 035 240 036 240 035
0048- 240 037 240 036 240 037 240 035
0050- 240 037 240 036 240 037 240 036
0058- 240 037 240 035 240 036 240 035
0060- 240 038 240 035 240 038 240 035
0068- 240 036 240 037 240 038 240 039
0070- 240 037 240 037 240 035 240 037
0078- 240 036 240 037 240 036 240 036
0080- 240 035 240 036 240 036 240 036
0088- 240 038 240 038 240 035 240 036
0090- 240 038 240 035 240 036 240 036
0098- 240 037 240 037 240 037 240 035
00a0- 240 036 240 037 240 035 240 038
00a8- 240 036 240 036 240 036 240 036
00b0- 240 036 240 036 240 037 240 038
00b8- 240 036 240 037 240 036 240 037
00c0- 240 035 240 000 240 001 240 000
00c8- 240 001 240 004 240 007 240 009
00d0- 240 010 240 014 240 019 240 022
00d8- 240 025 240 027 240 029 240 034
00e0- 240 034 240 037 240 036 240 036
00e8- 240 036 240 036 240 036 240 037
00f0- 240 036 240 036 240 037 240 036
00f8- 240 037 240 035 240 036 240 036




Here is the code:


CLEAR

Define OSC 8
OSCCON=110000
DEFINE ADC_BITS 10
DEFINE ADC_CLOCK 3
DEFINE ADC_SAMPLEUS 50


DEFINE CCP1_REG PORTC 'Hpwm 1 pin port
DEFINE CCP1_BIT 2 'Hpwm 1 pin bit


CHANNEL1 var word
SETPOINT VAR WORD
SETPOINT = 480


INCLUDE "incPIDv1.5.pbp" 'Include the PID routine.
ADCON1 = 001011
ADCON2 = 001010


TRISA = 111111
TRISB = 000000
TRISC = 110001
CMCON = 7
UCON.3 = 0
UCFG.3 = 1
portc.1 = 0


alpha var word
CHANNEL14 var byte
PCBTEMP var word
CURRENT var word
LEDS var byte
POWER var byte
LEDS = 0
POWER = 0
pcbtemp = 0
CURRENT = 0
alpha = 0
channel1 = 0
channel14 = 0


'ADValue VAR WORD '<---This is your variable.
'Setpoint VAR WORD '<---This is your variable.
'Direction var PortB.7 '<---Direction bit to motor driver

'These variables are declared by the incPID routine but
'the user needs to assign values to them.
pid_Kp = $0100 'Set Kp to 7.0
pid_Ki = $0020 'Set Ki to 0.5
pid_Kd = $0010 'Set Kd to 2.14
pid_Ti = 8 'Update I-term every 8th call to PID
pid_I_Clamp = 25 'Clamp I-term to max ±100
pid_Out_Clamp = 240 'Clamp the final output to ±511


LOOP1:

portb.1 = 0
portb.2 = 0
portb.3 = 0
portb.4 = 0
portb.5 = 1

Gosub GetAD 'Get speed from tacho - NOT SHOWN HERE.

pid_Error = Setpoint - CHANNEL1 'Calculate the error
Gosub PID 'Result returned in pid_Drive
IF pid_Out.15 THEN pid_Out = 0 ' limit to positive values

pid_Out = ABS pid_Out 'Convert from two's comp. to absolute
HPWM 1, pid_Out, 30000 'Set PWM output

PORTB.0 = 1
PORTC.7 = 1
PORTC.6 = 1
PORTC.1 = 1
PORTA.6 = 1
PAUSE 10


GOTO LOOP1




GetAD:
ADCIN 0, CHANNEL1
alpha = alpha+1
'channel14 = channel1/4
WRITE alpha, pid_Out 'channel14
RETURN

jmgelba
- 6th March 2012, 06:08
Couple more observations. It seems that there is a 50% duty cycle between a full on clamp and whatever the pid value should be. This is not a ripple issue as I can change the rate by changing the value of a pause statement at the end of the loop. With no pause, the flash rate is around 1mS. This on off at 50% duty cycle is shown in the above posts showing the memory dumps.

So the question now is, why is there an off period equal to the on period? If the output is higher than the setpoint, we get this 50% cycle, if its lower, we either get a 100% pid value for a very very small window or it clamps to zero and the pid value at a 50% duty cycle.

HenrikOlsson
- 6th March 2012, 08:20
Hi,
OK, lots of information here and I'm having a bit of a hard time following it all but here are a couple of thoughts.

Since you have the pid_OutClamp set to 240 and a proportional gain of 1 it means that the output will only "swing" when the CHANNEL1 variable is above 240 (because SetPoint is hardcoded to 480). If it's anything below 240 then the error * Kp is more than the clamp value so the output saturates. For example, if the CHANNEL1 value is 235 the error will be 480-235=245, the clamp is 240 so that's what you'll get.

If you do a step change of the PWM dutycycle, (ie. if you go from 20% to 80% dutycycle) how fast does the current in the system change?
I mean, you have a PAUSE 10 in there meaning you're only running the loop at 100Hz, it also looks like you're writing to EEPROM in the GetAD routine, which also takes around 10ms per byte so then we're down to around 50Hz. I'm not sure but 50Hz sounds awfully slow for a current control loop. You say that you've tried without any PAUSE at all but I still think you have around 10ms delay due to the WRITE instruction in the GetAD routine.

Not sure I understand what you mean with there is a 50% duty cycle between a full on clamp and whatever the pid value should be. Can you clarify?

May I suggest you try to implement this on something slower to begin with? Try a temp-sensor tightly coupled to lightbulb driven by the PWM signal for example - I think it'll make it a lot easier to begin with.

/Henrik.

jmgelba
- 6th March 2012, 15:05
Hello Henrik,

Thank you for your reply. Sorry, last night was a brain dump and a "thinking out loud" of what was going on.

Your first point about proportional control, I was playing around with numbers to see what the effect was. So, for my case then, this number should be 0.5 or lower, correct? This would mean the output would swing when CHANNEL1 was above 480. (An ADC of 480 means 2.10A at the output.)

Ok, so lets recompile this with a 0.5 Kp and setpoint = 480.

I know that 27.5Vin with a duty cycle of 240 = 2.10A (ADCof 480) at the output. What I actually see if I WRITE the duty cycle values is exactly half the time (memory locations) at 240 and half the time at a duty cycle less than 240. Why is it clamping at 240 for half the time when it should be there all the time? If I raise the input voltage, there should be no clamping at 240 yet the higher the input voltage goes, there is still clamping at 240 for half the time but the other numbers will go down to 0. This means that eventually the output is flashing on and off at 50% duty cycle.

Current change response is quite fast. 20% - 80% change will be taken care of in ~1.2mS. However, the load change is very slow. These are 50W LED's and their current only changes due to self heating. They are on a huge big fan cooled heat sink. All this pid filter should have to maintain a very similar duty cycle, slowly dropping it over time. The lower the duty cycle, the cooler the LED, thus the less current, thus increased duty cycle etc etc. It takes about 1 minute for the LED to get to a temperature where the pid filter should start changing the duty cycle.

The ONLY thing I can think of here that is upsetting the system is ripple from the 30KHz switching circuits. I could add a lot of capacitance to the ADC pin to slow down the response and remove any ripples.

The control loop seems to be running a bit faster than what you have mentioned. I only have the WRITE in there for diagnostics, and run any changes twice, once with and once without the WRITE command. I was using the pause at the end of the loop to see what was happening with the 50% clamping at 240. If I make that pause 1S long, the duty cycle is 240 for 1 second and 0 for one second. If I take it out, the duty cycle is 240 for ~1mS and 0 for ~1mS. Thus the loop is running at ~1Khz. I cant see the on/off, I can only hear the inductors making noise.

I hope this clears up some things!

jmgelba
- 6th March 2012, 15:14
Is there a way to have setpoint = 460 to 480 to create a "softer" current limit where some ripple at the output would be ok?
Also, what about having the clamp set at 960, then dividing that number by 4 and feeding it to the HPWM?

HenrikOlsson
- 6th March 2012, 18:49
Hi,
For simplicity, let's concentrate on the proportional gain alone (no Ki or Kd) to start with, OK?

Let's then assume the following:
* You have a proportional gain of 1, ie pid_Kp=$0100
* You're setpoint value is 480
* You have 2.1A flowing in your circuit resulting in an ADC value of 480

Now, when you calculate the error, pid_Error = SetPoint - ADC the error is 0 and therefor pid_Out will be 0. Since you're feeding pid_Out straight to the PWM module the dutycycle will be set to 0.

Now, because the dutycycle is 0 the current drops, rapidly. By the time you sample the current again it has dropped to 0 or close to it (I'm guessing a bit), so the ADC value is 0 and pid_Error will be 480. Because you have a proportional gain of 1 pid_Out would be 480 but it gets clamped to 240. That's where your bouncing output comes from.


If your 'idle' point requires a dutycycle of 240 then you need to set that up first. THEN you sample the current and run the error thru the PID-loop and ADD the pid_Out to the 'idle' dutycyle. For example, 240 is nominal dutycycle value, when the ADC returns 475 it means the current is on the low side, the error is 5 and pid_Out becomes 5 which you add to your 'idle' resulting in a dutycycle value of 245. If the ADC value is 490 (too much current) the error will be -10, the the pid_Out will be -10, which you add to your 'idle value' resulting in a dutycycle of 230, lowering the current.

Again, the above ilustrates what happens with a proportional gain of 1 only, when you add in the integral and derivative terms it'll act a little different.

There is no built in way to have it clamp in a soft way as you describe, you have to do that "manually" if you need.

/Henrik.

Ioannis
- 21st November 2012, 09:25
How possible is it to make (and how, any ideas) the P-I-D parameters set automatically, like auto-tune?

I have seen some chinese temp. controllers that say they can do auto-tune according to the device they control.

Ioannis

HenrikOlsson
- 21st November 2012, 12:52
There are various ways for doing autotuning. Basicallly you step the setpoint and analyze the system response regarding risetime, overshoot, amplitude and frequency of any oscillation and so on.
I've got the question before and did some research into the matter but to be honest the math involved is beyond me. Right now I re-Googled PID Autotune and it came up with a link to a autotune library for Arduino (http://brettbeauregard.com/blog/2012/01/arduino-pid-autotune-library/) - I may need to have a look at that!

If you're up for it, feel free to take a stab at it!

/Henrik.

Ioannis
- 21st November 2012, 14:00
Thanks Henrik.

I will look at that link, although c and similar is not my strong.

What you proposed is I think the obvious way to approach the problem. I was thinking more or less the sam.

It seems that chinese are doing it much faster though. My REV200 Siemens temp controller is fast in evaluating the room too!

Ioannis

andywpg
- 17th April 2013, 02:56
I am attempting to adapt Henrik's PID routine to my smoker temperature controller project. Basically, a fan provides air to the charcoal based on a temperature sensor in the chamber.

I set up a simulation using it, and it seems to work pretty cool, but I know I'm going to have to run on the real smoker to get it fully tuned up (I still have 3 feet of snow in my yard, so it may be a bit).

I have set it so that, if the output of the PID routine is negative, it sets the HPWM output to 0 (fan off) since I can't go negative.

Question 1: Why is the PID output clamp 511 in the original, when the PWM can only go to 255? Or am I missing something? When I set my simulation up so that it would drive to 100%, it was sending 511 - so I have set the PID output clamp to 255.

Question 2: Since I am dealing with charcoal here, its not a terribly fast heat source, I'm thinking that I should check the temperature every 5 seconds and only update the integral every minute or so (12 passes). Anyone have any thoughts on this?

Thanks Henrik for this WONDERFUL routine!

HenrikOlsson
- 17th April 2013, 06:29
Hi,
1) That is indeed an issue with the example code which went unobserved for a very long time, someone noticed it and mentioned it a couple of posts back in the thread. Unfortunately the forum doesn't allow me to fix it in the original post. Anyway, you are 100% right, if you're using the HPWM command then you should set the output clamp to 255. With that said the standard CCP module creating the PWM output can provide resolution of up to 10bits depending on frequency, you just can't "access" it using the HPWM command.

2) Sounds like a good start. Getting the tuning values right can be tricky, especially so on slow systems since it takes so damn long to see if you got it right or not. I expect this particular "plant" to be extra tricky due to the somewhat unrepeatable/unpredictable "performance" of a charcoal bed - I imagine.

One thing you might consider is to run the heater/fan/furnace whatever "open loop" until the temperature starts to approach the setpoint and only then switch on the PID. A more elegant but also a bit more complicated way is to create a ramping action in a way that the setpoint, instead of being bumped from ambient to "working temp" is ramped up at a rate that the "plant" can actually follow.

If you're concerned that a dutycycle of 0% won't provide enough oxygen to keep the fire alive then simply don't allow it to go below a certain value to keep the fan running at all times.

/Henrik.

andywpg
- 17th April 2013, 14:25
Hi,

One thing you might consider is to run the heater/fan/furnace whatever "open loop" until the temperature starts to approach the setpoint and only then switch on the PID. A more elegant but also a bit more complicated way is to create a ramping action in a way that the setpoint, instead of being bumped from ambient to "working temp" is ramped up at a rate that the "plant" can actually follow.


I had actually planned to run the fan in manual mode until I am near the setpoint, then allow the PID loop to take over in auto mode.

The other question I had is on the reset. In the example, it is set to .5. What causes the integral ramp to get steeper? Would that be an increase in the number? What kind of range of numbers are you expecting on the reset .1 to 1 or even higher than that?

BTW, while running it in simulation last night, I discovered that a derivative of 0 causes unpredictable results - to remove the derivative, you have to set it to 1.

In my reading and conversations with people who deal with PID (one of which was an instructor at a local community college) I am told that temperature control only requires process and integral, not derivative.

Thanks again,

Andy

andywpg
- 17th April 2013, 14:44
If anyone is interested, here is what I used to run the simulation (my controller has an LCD)


#CONFIG
__config _CONFIG1, _INTRC_OSC_NOCLKOUT & _WDT_OFF & _MCLRE_OFF & _LVP_OFF & _CP_OFF
#ENDCONFIG

DEFINE OSC 8 'LETS PBP KNOW THE OSCILLATOR IS RUNNING AT 8MHz
DEFINE NO_CLEARWDT 1 'NO WATCHDOG TIMER - FOR NOW - WILL DO OUR OWN CLRWDT


OSCCON = %01110001 '8 MHz INTERNAL OSCILLATOR, INTERNAL OSCILLATOR IS USED FOR SYSTEM CLOCK


DISABLE 'NO PBP INTERRUPTS NO PBP DEBUG

' Set LCD Data port
DEFINE LCD_DREG PORTB
' Set starting Data bit (0 or 4) if 4-bit bus
DEFINE LCD_DBIT 0
' Set LCD Register Select port
DEFINE LCD_RSREG PORTB
' Set LCD Register Select bit
DEFINE LCD_RSBIT 4
' Set LCD Enable port
DEFINE LCD_EREG PORTB
' Set LCD Enable bit
DEFINE LCD_EBIT 5
' Set LCD bus size (4 or 8 bits)
DEFINE LCD_BITS 4
' Set number of lines on LCD
DEFINE LCD_LINES 2
' Set command delay time in us
DEFINE LCD_COMMANDUS 1500
' Set data delay time in us
DEFINE LCD_DATAUS 44



'// Set up hardware registers, ADC etc here (not shown) //

ADValue VAR WORD '<---This is your variable.
SetPoint VAR WORD '<---This is your variable.
COUNTER VAR BYTE
RUN_IND VAR BYTE




INCLUDE "incPID.pbp" 'Include the PID routine.

'These variables are declared by the incPID routine but
'the user needs to assign values to them.
pid_Kp = $0700 'Set Kp to 7.0 GAIN
pid_Ki = $0080 'Set Ki to 0.5 RESET
pid_Kd = $0001 'Set Kd to 0 (Derivative not necessary)
pid_Ti = 8 'Update I-term every 8th call to PID
pid_I_Clamp = 100 'Clamp I-term to max ±100
pid_Out_Clamp = 255 'Clamp the final output to 0 to 255
Setpoint = 230 '<---Set desired position.
RUN_IND = 1

START:
FOR ADVALUE = 225 TO 235 'TEST A BUNCH OF VALUES
FOR COUNTER = 1 TO 10
pid_Error = Setpoint - ADValue 'Calculate the error
Gosub PID 'Result returned in pid_Drive
if pid_Out.15 then pid_out = 0 'IF NEGATIVE THEN SHUT OFF FAN
pid_Out = ABS pid_Out 'Convert from two's comp. to absolute
' HPWM 1, pid_Out, 10000 'Set PWM output

LCDOUT $FE, 1, "AD=", DEC ADVALUE, " PO=", SDEC PID_OUT
LCDOUT $FE, $C0, "RUN #", DEC COUNTER, " SETP ", DEC SETPOINT
Pause 1000 'Wait....
NEXT COUNTER
NEXT ADVALUE
IF RUN_IND = 1 THEN
RUN_IND = 0
GOTO START 'DO IT AGAIN SO THAT ITS AS IF THE TEMPERATURE DROPPED
ENDIF
LCDOUT $FE, 1, "DONE!"
DO
LOOP 'ENDLESS LOOP

END

HenrikOlsson
- 17th April 2013, 17:57
Hi,
I'm sure you're correct that you don't need derivative. I'm surprised though that you get unpredictable results when you set it to zero, I'll have to look into that. What exactly is it doing?
Anyway setting it to 1 is pretty close to 0 (1/256 to be precise.) So it shouldn't have any effect on the actual control loop in your case.

The PID_Ki isn't the "reset time" (at least I don't think it's comparable to that), it's the integral gain. To get more integral action (steeper ramp) you increase the gain.

Here's how it works, basically:
Each time you GOSUB PID the integral term takes the error and adds it to an accumulator. It then increments a counter and compares the counter value to PID_Ti, if the counter value is less than PID_Ti it's done. If the counter is equal to PID_Ti it takes the (value in the accumulator) * (integral gain/256) / PID_Ti, it then adds the result to the output and resets the accumulator as to not overflow it. I think you can compare the PID_Ti value to what's sometimes called "reset time". (It does a couple of other things as well but lets not go into that right now).

As to a range of values for the integral gain..... all I can say really is that it's usually a lot less than the proportional gain, which in turn is usually a lot less than the derivative gain (which you don't use here).

Hope it helps.
/Henrik.

andywpg
- 18th April 2013, 00:09
Hi,
I'm sure you're correct that you don't need derivative. I'm surprised though that you get unpredictable results when you set it to zero, I'll have to look into that. What exactly is it doing?

All this was observed using the code I posted above.

With the derivative set to one, and the loop at 216 (degrees) it usually starts with an output to the PWM of 150 or so. As the sample gets closer to the setpoint (230) it ramps down - as it should.

With derivative set to 0, the PWM starts at about 20, and goes down from there - I can't remember exactly where, but at some point (long before its at the setpoint) its at ZERO - with a charcoal fire, that means its never getting to the setpoint. Strangely enough, on the SECOND run through the numbers, it starts working properly - not sure why. Maybe its my fault.

It seems to work great with the derivative set to 1, though, acts exactly as it should.




Hope it helps.


Very much so. Now, if only this damn snow would go away........

Thanks again,

Andy

andymuller
- 17th February 2014, 01:50
Dear Mr. Henrik,

Would you please modify your PID routine for using on the other kinds of closed loop control, such as pressure control?

I want to implement a pressure transducer as feedback sensor and drive a water-pump by using of a frequency inverter to achieve a desired pressure in pipes. And of course, to make a stable level of pressure in pipes when people use water.

Deeply appreciating in advance.

Would you please help?

Regards,

P.S. I am beginner.

HenrikOlsson
- 17th February 2014, 07:33
Hi,
I don't know how many times I've written this but here it goes again:
The PID filter doesn't know what you are trying to control/regulate and it doesn't matter, all it does is crunch numbers. These numbers can represent pressure, lightlevel, voltage, current, position, velocity, torque, temperature, humidity, pH, whatever. It's just numbers and math as far as the PID filter is concerned and it'll close a loop around "anything" you want.

With that said, PID isn't the only type of regulator and may not always be the best method but that's not the point.

/Henrik.

Ioannis
- 17th February 2014, 08:00
...And of course, to make a stable level of pressure in pipes when people use water.
...
P.S. I am beginner.

1. If you are a beginner, this may be a difficult project.

2. On a water pipe network you have to place your sensor on the far most distant point. But given that the pipes are not 100% rigid, you understand that near your pump, pressure will be much higher than the last water outlet.

Add to that, you have to find optimal values for the P, I and D, you see that it is not as easy as a-b-c.

Ioannis

andymuller
- 18th February 2014, 07:45
1. If you are a beginner, this may be a difficult project.

2. O....

.... that it is not as easy as a-b-c.

Ioannis

Hello Ioannis,

Thank you very much in advance for your quick response and of course you have put your time to reply here. I appreciate.

I am beginner in PIC's world but I know what the PID is what the ziegler-nichols method says ;) Thank you buddy for your kidding ...haaa haaaa haaaa :wink: :biggrin: I agree with you: it is not easy such as counting a-b-c.... wonderful. lol
But your second mentioned point is a good point and thinkable because as long as sensor be closer to pump, the fluctuations be more and more, logically.

Regards,

P.S. I saw your website, I wish you all the best and good luck, dude.

andymuller
- 18th February 2014, 07:52
Hello Henrik,

Thank you very much in advance for your kind response.

After I sent my post here, I searched more at topic and I found another work of you here. The version is 1.5. If it be your last version or last has been published version?

Best regards & good luck

HenrikOlsson
- 18th February 2014, 09:13
Hi Andy,
Yes, version 1.5 posted here (http://www.picbasic.co.uk/forum/showthread.php?t=5874&p=89457#post89457) is the latest published version.

/Henrik.

andymuller
- 18th February 2014, 17:26
Hi Andy,
Yes, version 1.5 ...is the latest published version.

/Henrik.

Thanks a million, dude.

Good luck

Ioannis
- 16th June 2018, 11:25
I suppose this is for Henrik.

When the ADC read value reaches the Setpoint, should'n the pid_out goto zero instead of staying for ever in a, relatively low, value?

Ioannis

HenrikOlsson
- 16th June 2018, 16:29
As far as this PID code goes this is by far the most common question I've received over the years....

If you have no integral gain then yes the output should (and will) be zero when the error is (and has been) zero for at least one cycle (depending on the derivative part of the regulator).

But most of the time you're using a PID regulator because you NEED the I and D parts as well so no, the output should NOT (necessarily) go to zero just because the error does.

When the error reaches zero the total output of the regulator will be whatever the I-term has contributed. If you want/need the output to be zero when the error is then you don't want/need the I-term. Lets take the old analogy of a car in cruise control going down the road, the setpoint is 70km/h and the throttle is 15%. The the car aproaches a hill which causes it to slow down (the error increases) so the proportinal term adds "effort" to the output in order to compensat but because the hill is steep it's not enough to drive (no pun inteneded) the error to zero so little by little the I-term adds "effort" to the output which makes the car produce more power to compensate to for the steep climb.

After a short while the car is back at 70km/h despite it being in this steep hill. Now everyone one that has asked me this question expects the output of the regulator to become zero because there is no error. But what do you think happens then?

/Henrik.

Ioannis
- 16th June 2018, 19:52
Of course this makes sense.

Thanks,
Ioannis

Ioannis
- 29th September 2018, 19:59
Many time I tried the PID include on different projects but never with success.

This time I am determined to make it work.

The new setup has a 3 phase blower that sucks air from a container, a pressure sensor, a 3-phase inverter to control the motor and a PIC to set the setpoint and measure the pressure.

The pressure is getting close but never on target. I managed to make it stable enough but does not reach the setpoint.

The PID setting so far are as:

pid_Kp = $0250
pid_Ki = $f000
pid_Kd = $ff00
pid_Ti = 8 'Update I-term every 8th call to PID
pid_I_Clamp = 100 'Clamp I-term to max ±100
pid_Out_Clamp = 1023

Increasing the Kp term makes the loop unstable.

Seems the system need a gain but that leads to a non stable loop.

Ioannis

HenrikOlsson
- 30th September 2018, 11:45
You have quite high integral gain I'm surprised the system doesn't become unstable with that.
I'd try setting Ki and Kd to 0, then increase Kp until you're starting to get oscillation then back Kp off a bit. At this point I suspect that you're not quite reaching the setpoint so start increasing Ki. The intergral term will help "catch up" the part that the proportional alone can't do.


How often do you run the loop?

Ioannis
- 30th September 2018, 21:19
Hi Henrik. Thanks for your attention.

I started as you said, with zero the terms Ki and Kd.

The point of oscillation was a bit higher than the $250. About $0300.

Then I increased Ki but never reached set point, only oscillation point.

The posted values are the maximum that can be obtained. But there is a substantial difference to the set point.

Ioannis

Dave
- 1st October 2018, 14:47
Ioannis, What is your loop time period for calling the PID routine? I have been using a routine similar to Henrick's for years in many different projects and I have to admit, Your Integral and Derivative values seem quite high. Do you have a Proportional clamp term? Also is the Integral term being clamped?

Ioannis
- 1st October 2018, 14:58
Hi Dave.

Do you imply that high values make it hard to reach setting point?

The I clamp is in the list of the parameters. It is set to 100. Setting it higher makes oscillation worse to control.

As for the Proportional I do not clamp it.

The loop was tested from 2 to 20ms with no difference in behaviour (besides being a bit slower at the 20ms of course).

Ioannis

HenrikOlsson
- 1st October 2018, 18:07
I think it's the integral clamp that's the root cause.
When the actual value is getting closer to the target, the output from the proportional term gets smaller and the integral term needs to "build up" in order to to "aproach" the target. But with the integral clamp set to less than 10% of the full output swing there's not much it can do.

Try reducing the integral gain by a factor of 100 or there abouts, then increase the intergral limit to 1000.

If your output is zero and you instantly set it to 100%, how long would you say it takes to reach full pressure?
When the pressure no longer increases, what's the value you read from the sensor that is then used to calculate the error that you send to the PID routine?
At full pressure, how much higher is that number compared to the highest allowed setpoint (how much margin is there)?

Not to complicate stuff further but this souds like a good application for using the feedforward feature. It helps provide a "baseline" output proportional to the setpoint (instead of proportional to the error) which in turn makes the job for actual regulator easier.

/Henrik.

EDIT: I don't think you need the derivative term, try setting that to 0 to start with. And finally, it's important that the time between calls to the PID routine (and the time between reading the feedback) is constant (ie driven by a TMR interrupt for example).

Ioannis
- 1st October 2018, 19:50
Thank you Henrik for the reply.

Well, for the testing I did not had very precise call to the PID routine. It is based on a tide loop of 2 to 20 ms (Pause 2 etc) without having any other tasks to deviate from the loop time. At least considerably.

In the mean time, I tested the PID profile of the inverter that I drive and after playing with the settings seems very stable. Since I have it running smooth, I will now try to make it work through PIC.

The loop can be stable and it takes about 3-5 seconds to reach the setting point with the inverter.

Will follow your suggestions and try to implement also the feedforward feature and will report the time it takes to respond.

Tricky but very interesting thing that PID filter.

If you wonder why do the PID on the PIC instead of using the ready made solution of the Inverter, the answer is that inverters may change on the system in production or service and I prefer to have it under my circuit control.

Thanks again,

Ioannis

mark_s
- 1st October 2018, 21:22
Henrik,

I'm using your great routine in a step/dir servo driver. Everything works fine. But have been wondering about the "pid_Ti" constant

What are your thoughts on the Up-date i-term? I have kept it at 8. My pid update rate is approx 1950hz. So the I term is updating at 1950/8 = 244hz.

Thanks

@Ioannis, this video gives a great visual of all the factors including loop time on the output response

https://www.youtube.com/watch?v=fusr9eTceEo

HenrikOlsson
- 2nd October 2018, 06:25
Mark,

It provides some filtering to the input of the integrator and slows down its response some. The integrator does run on every call but the output of it is only added to the total output every n'th call to PID and at that time it's the average of the accumulated error over the last n calls (times the gain) that gets added. So I believe it should/could help with an otherwise "ringing" or "hunting" response where the integrator drives the target above the setpoint, then back below the setpoint and back up etc.

Step and direction servo, cool! That's the exact application for my own use as well :-) Care to share some details?

One thing I've done in my servo is to allow the user to disable the I-term when the commanded velocity is >0. This is because when moving there will always be a following error which will build up I-term which will cause overshoot at the end of the move. If the intergrator is only allowed to accumulate when the commanded velocity is zero it will only work to drive the steady state error at the end of a move away. It will come at a cost of a slightly larger following error but feedforward can help with that.

/Henrik.

mark_s
- 2nd October 2018, 18:13
Thanks for your reply. I was thinking it replaced the "dt" term found in most pid equations.

Here is my compete project. It's for learning purposes, for safety it would need to be modified to use in a real machine. Like opto inputs, over current protection and a system fault I/O. The Kp and Ki are adjustable with potentiometers. Kd is set to 3, as commented above not really used in this system. It uses a pic18f4431, but can be adapted for,4331,2431 and 2331

Henrik, if you see any that could be improved please comment. I'm working on a automated coil winder, using pid to maintain tension on the magnet wire. The tensionor is working in a mock up. What I want to figure out next is a serial input to update variables from a user interface.

Jerson
- 3rd October 2018, 07:53
Dear Henrik et al.

I am showing here how I implement PID in my 8bit process controllers. I have deliberately shown it as pseudo code for ease of understanding.
Typical C conventions are used. += is add to, != is not equal to

PB is the proportional band in which you will like PID action to take place. Outside the band, the output is either on or off at all times depending on which side it is of the Setpoint (SV) PV is the present value of the process variable

Integral time and Derivative time are between 0 to 3600 units of time (rate at which PID is run). I run my routine for temperature control every second.