Shop OBEX P1 Docs P2 Docs Learn Events
Andy Lindsay's PID.spin and Beau's PWM object — Parallax Forums

Andy Lindsay's PID.spin and Beau's PWM object

AkkarinAkkarin Posts: 43
edited 2014-04-08 23:45 in Propeller 1
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.
PWM heater.jpg


''=======[ 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?
1024 x 516 - 38K

Comments

  • ReachReach Posts: 107
    edited 2014-04-07 04:58
    I believe the PWM object takes a number from 0-100 so 350 is out of scope, but I could be wrong. I would start by testing each object separate. Try setting the PWM to 50 to see if your electronics is working correctly. If so then come back and look at the other parts of the code. You can change the PWM object to accept higher numbers 0-1000 for example.
  • AkkarinAkkarin Posts: 43
    edited 2014-04-07 05:22
    Hi Reach,

    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.
  • Beau SchwabeBeau Schwabe Posts: 6,566
    edited 2014-04-07 06:19
    Akkarin,

    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?
  • ReachReach Posts: 107
    edited 2014-04-07 06:21
    Lets check things off

    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.
  • AkkarinAkkarin Posts: 43
    edited 2014-04-07 07:02
    @Beau: I attached the propeller tool archive

    @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.
  • ReachReach Posts: 107
    edited 2014-04-07 20:14
    Something that looks odd in the PID object is this
    PUB init (_Kp, _Ki, _Kd, setPoint, offset, maxArea, minArea)
    
    
      ' Start floating point object.
      f.start
    
    
      ' Set values of kp, ki, etc.
      longmove(@kp, @_Kp, 7)
    
    
      ' Clear values.
      tp := cnt
      P := I  := D := 0.0
      E := Ep := 0.0
      calculate(f.FRound(sp)) [b]<--------------------------here [/b] 
    
    
    

    I am not sure why its being calculated and changed to a float. Perhaps commenting it out may help.
  • AkkarinAkkarin Posts: 43
    edited 2014-04-08 02:45
    Alright, I played with the set points, Ks, and the PUB Init method of the PID object. It makes no difference if I have commented out that line Reach refers to or not. I am sure now that it does not work with my scaled integer value for the temp reading, e.g. 345 for 34.5°C. With two digit set points it works well. I now limit PIDout between 0 and 100 because values outside that range cause unexpected behavior of the PWM object. So right now I'm satisfied but I swear (and a colleague of mine has seen it as well) that yesterday PIDout was around 85 (or any other 2 digit value) and the PWM didn't work.
  • Beau SchwabeBeau Schwabe Posts: 6,566
    edited 2014-04-08 09:23
    I'm pretty sure it's not the PWM object and I don't think there is a floating point issue with the PID object as it converts back to integer for the output.

    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.
  • AkkarinAkkarin Posts: 43
    edited 2014-04-08 23:45
    Hi Beau,

    thank you for your effort.
    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.

    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).
    ''=======[ Constants ]=============================================CON
    '' +++++++[ Clock Frequency ]++++++++++++++++++++++++++++++++++++++++++++
      _clkmode = xtal1 + pll16x             'Standard clock mode * crystal frequency = 80 MHz
      _xinfreq = 5_000_000
    
    '' +++++++[DS1621 address ]++++++++++++++++++++++++++++++++++++++++++++
      Addr = 01_0000
    
    ''=======[ Variables ]=============================================
    VAR
      long tempvar,TH,TL
      long PIDin,PIDout
      long PIDstack[64]
    
    ''=======[ 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
      cognew(PIDloop,@PIDstack)
    
    
      ''Initialise some variables
      '**************************
      TL := 34
      TH := 34
      DS1621.writeConfig(28,Addr,2)
      DS1621.writeTL(28,Addr,TL)
      DS1621.writeTH(28,Addr,TH)
      DS1621.startconversion(28,Addr)
      waitcnt(clkfreq/100 + CNT)
    
    
      ''INIT PID
      '**********
      PID.init(5.5,1.0,0.05,32.0,0.0,90.0,-10.0)  'init (_Kp, _Ki, _Kd, setPoint, offset, maxArea, minArea)
    
    
      ''Main loop
      '**********  
      REPEAT
        pst.str(string("Temp: "))
        pst.dec(long[@tempvar])
        pst.newline
        pst.str(string("PIDout: "))   
        pst.dec(long[@PIDout])
        pst.newline
        waitcnt(clkfreq/4 + CNT)
    
    
    ''=======[ Additional Methods ]=============================================
    PRI PIDloop 'runs it its own cog
      repeat
        waitcnt(clkfreq/8 + CNT)
        long[@tempvar] := DS1621.readTempC(28,Addr)
        long[@PIDout] := PID.calculate(tempvar)
        PWM.Duty(9,0#>long[@PIDout]<#100,1000)
        
    
    ''=======[ DAT / Assembler ]=============================================
    DAT           org
    ''Global Variables
    
Sign In or Register to comment.