Andy Lindsay's PID.spin and Beau's PWM object
Akkarin
Posts: 43
Hi,
I have encountered a strange behavior.
I use the PWM in duty mode on Port 9 with a period of 1000 microseconds. I use the PWM to control the base of an BC550 NPN transistor in order to control the current through a resistor which acts as an heater and sits on top of an DS1621 thermometer IC, see schematic. So I can build little temperature control loop. I know of the thermostat feature of the DS1621 and have tested it, but now it's only about the PID.
As you can see, I set the set point to 340 (meaning 34.0°C) and feed the output of PID.calculate() directly into PWM.duty() but it doesn't work. The PST reading is:
So, PIDout should set the duty cycle to 85% but nothing happens. I measure the current through R2 with a multimeter and it reads 0 mA (with 85% it should read about 82mA). But when I type 85 directly into PWM.duty(9,85,1000) it works just fine, current is 82.5mA.
In PID.calculate the RESULT ist converted to integer with fRound, see listing.
Could this be a conversion problem from float to integer? Because when I enter a float e.g. 50.5 into PWM.duty(9,50.5,1000) it doesn't work either. Is there another way to convert "out" to an integer?
I have encountered a strange behavior.
I use the PWM in duty mode on Port 9 with a period of 1000 microseconds. I use the PWM to control the base of an BC550 NPN transistor in order to control the current through a resistor which acts as an heater and sits on top of an DS1621 thermometer IC, see schematic. So I can build little temperature control loop. I know of the thermostat feature of the DS1621 and have tested it, but now it's only about the PID.
''=======[ Constants ]============================================= CON '' +++++++[ Clock Frequency ]++++++++++++++++++++++++++++++++++++++++++++ _clkmode = xtal1 + pll16x 'Standard clock mode * crystal frequency = 80 MHz _xinfreq = 5_000_000 Addr = _0000 'DS1621 address ''=======[ Variables ]============================================= VAR long tempvar,TH,TL long PIDin,PIDout ''=======[ Objects ]============================================= OBJ pst : "Parallax Serial Terminal" DS1621: "DS1621Obj.spin" PWM : "PWM_32_v4.spin" PID : "PID.spin" ''=======[ Main Method ]============================================= PUB main ''DESCRIPTION: ''PARMS: ''RETURNS: ''Port directions; DON'T change!!! ''Outputs ''Start some drivers and new cogs '******************************** PST.Start(115200) PWM.start ''Initialise some variables '************************** TL := 34 TH := 34 ''set up DS1621 '******************* DS1621.writeConfig(28,Addr,2) DS1621.writeTL(28,Addr,TL) DS1621.writeTH(28,Addr,TH) DS1621.startconversion(28,Addr) waitcnt(clkfreq/100 + CNT) ''INIT PWM and PID '********* 'PWM.Duty(9,50,1000) 'Duty(Pin,DutyCycle,Period in microseconds) PID.init(1.0,0.1,0.05,340.0,0.0,100.0,0.0) 'init (_Kp, _Ki, _Kd, setPoint, offset, maxArea, minArea) ''Main loop '********** REPEAT pst.str(string("Temp: ")) tempvar := DS1621.readTempC(28,Addr) 'tempvar = temperature*10 + 0 or 5 -> 235 = 23.5°C pst.dec(tempvar) [B]PIDout := PID.calculate(tempvar)[/B] pst.newline pst.str(string("PIDout: ")) pst.dec(PIDout) pst.newline [B]PWM.Duty(9,PIDout,1000)[/B] waitcnt(clkfreq/4 + CNT) =======[ DAT / Assembler ]============================================= DAT org ''Global Variables SOURCENAME byte "DS1621 Test.spin",0 SOURCEVERSION byte "V1 Rev. 0",0 SOURCEDATE byte "03.04.2014",0
As you can see, I set the set point to 340 (meaning 34.0°C) and feed the output of PID.calculate() directly into PWM.duty() but it doesn't work. The PST reading is:
Temp: 265 PIDout: 85
So, PIDout should set the duty cycle to 85% but nothing happens. I measure the current through R2 with a multimeter and it reads 0 mA (with 85% it should read about 82mA). But when I type 85 directly into PWM.duty(9,85,1000) it works just fine, current is 82.5mA.
In PID.calculate the RESULT ist converted to integer with fRound, see listing.
PUB calculate(in) : out '' Returns PID (out)put based on (in)put and stored values. ' Calculate Δt = [cnt(now) - cnt(previous)] / clkfreq t := cnt ' t(now) = cnt dt := t - tp ' Δcnt = t - tp dt := f.fDiv(f.FFloat(dt), f.FFloat(clkfreq)) ' Δt = Δcnt ÷ clkfreq ' Calculate Error = set point - input in := f.FFloat(in) ' Integer → Float E := f.FSub(sp,in) ' E = sp - in ' Proportional P := f.FMul(E, kp) ' P = kp × E ' Integral (with trapezoidal rule) keeps a running total of the ' area under the error vs. time plot. Et := f.FDiv(f.FAdd(E, Ep), 2.0) ' Trapezoid heights da := f.FMul(Et, dt) ' Δarea a := f.FAdd(a, da) ' area = area + Δarea if f.Fcmp(a, Amax) == 1 ' Limit error area a := Amax if f.Fcmp(a, Amin) == -1 a := Amin I := f.FMul(ki, a) ' I = ki × a ' Derivative de := f.FSub(E, Ep) ' ΔE = (now) - E(old) dedt := f.FDiv(de, dt) ' ΔE/Δt D := f.FMul(kd, dedt) ' D = kd × ΔE/Δt ' (out)put = P + I + D + offset out := f.FAdd(P, I) ' P + I out := f.FAdd(out, D) ' P + I + D out := f.Fadd(out, off) ' P + I + D + offset [B] out := f.fRound(out) ' → integer result[/B] ' Values for next iteration Ep := E ' E(old) = E(now) tp := t ' t(old) = t(now)
Could this be a conversion problem from float to integer? Because when I enter a float e.g. 50.5 into PWM.duty(9,50.5,1000) it doesn't work either. Is there another way to convert "out" to an integer?
Comments
the PWM is working I tested it by entering values from 0-100. I also think that 350 would be out of scope, but PIDout in my tests was between 0-100.
I think you answered your own question as far as an issue from floating to integer, but without seeing all of your code it is difficult to know for sure. Can you attach the complete code of your project?
Are you sure the PID out is a integer? I dont see the F.math object being used? and why have you made the max/min area 0, try setting these to 100 and -100 or some value. This allows the integral to grow.
Ill need these objects to further assist so give me time to download them again.
@Reach: I altered my code such that I forget about the LSbyte (.0 or .5) of the temp reading, so I now can enter values between 0 and 100 to the PID object as well. I also set the min/max area to +-50. Without the Ki it now seems to work somehow at least I get some current going through the resistor. With Ki different than 0 I get a PIDout that is still below 100 but the PWM output is 0 (at least no current goes through the resistor). I still don't understand why a PIDout of 50 or 85 results in 0 output iof the PWM object. Tomorrow I will do further testing and try to adjust the K values, offset and so on.
I am not sure why its being calculated and changed to a float. Perhaps commenting it out may help.
Since I didn't have a DS1621, I hard coded values for 'tempvar' ...
A value of 25 (about room temp) returned or settled to 50 for 'PIDout' , however a value of 40 (Hot summer day) returned a -34.
If you are getting any negative numbers, that's probably what is messing the PWM object up.
I don't know for sure, but a PID loop is usually setup to report the amount of error, in which case 0 would be center (no error) or 50% duty cycle ... a positive returned value from the PID would increase the Duty to something greater than 50% while a negative value from the PID should decrease the Duty to something less than 50%. <-or the inversion of that.
thank you for your effort.
The negative value for PIDout here comes from the 40 being greater than the set point passed to the PID object. Thats why I now limit PIDout from 0 to 100 because of course it makes no sense to have a negative duty cycle. I also have limited the minArea to -10 so that the error that is accumulated doesn't get too big and therefore slows the PID down. I really don't know why it didn't work from the beginning but now it seems OK. I'm sorry for wasting your time. (But I will try to replicate the error when I have the time )
For documentation purposes, here my newest program (the sub-object were not changed).