cadwallion / temper

PID-based temperature control library
http://cadwallion.com/blog/2013/02/18/introducing-temper/
MIT License
11 stars 1 forks source link

pid questions #1

Open prussiap opened 10 years ago

prussiap commented 10 years ago

Hi, I'm in the process of building a sous-vide Pi system and I was hoping to use your PID library to avoid re-inventing the wheel. I have a few questions though:

  1. How would you implement the auto-tuning function? If I choose your library I could probably provide a PR of our attempt (hints welcome! ).
  2. I've been considering using http://www.over-engineered.com/projects/sous-vide-pid-controller/ the open-loop stem method to calculate a T td and K. combined with some sort of closed-loop challenge (aka adding ice) to get the actual Kp, Ki, Kd values? Any thoughts?
  3. I will be using a solid state relay and a 5 Qt mechanical crockpot. I'm unsure on how to convert the output from the PID into something useable to switch the water-bath on/off. In your example you opted for PWM which might be too fast for my method I can't imagine the water heating or cooling in seconds (but closer to a minute maybe?). Could you help elucidate this process a bit?

In any case your project is much appreciated. I look forward to being able to contribute.

cadwallion commented 10 years ago

Greetings!

1) While I haven't attempted an autotuning method for Temper (I have done manual tunings for the three systems I have used temper and it's sibling, mruby-pid, on), I imagine an automated Z-N open loop tuning algorithm could be applied with Temper without too much effort. Any attempts/patches are welcome.

2) That is a perfectly reasonable way to handle manual tuning and the translation into the kP, kI, and kD.

3) I really should update the example with SSR output, as that's what I have been using for brewing (40A SSRs controlling 240VAC heating elements). While the window of cooling/heating may not be fast, you still want to keep a reasonably narrow window of on/off cycles. I use a 5000ms window, and translate the output value into milliseconds on/off. Here's an example:

class HeatingElement
    attr_accessor :pulse_width, :pulse_range, :name, :adapter

    def initialize adapter, options = {}
      @pulse_range = options[:pulse_range] || 5000
      @on = false
      @pulse_range_end = (Time.now.to_i * 1000) + @pulse_range
      @adapter = adapter
      @pulse_width = 0
      @name = options[:name]
    end

    def pulse
      set_pulse_time
      update_pulse_range if pulse_exceeds_range?

      if pulse_within_width?
        on!
      else
        off!
      end
    end

    def set_pulse_time
      @pulse_time = (Time.now.to_i * 1000)
    end

    def pulse_within_width?
      @pulse_time <= pulse_end
    end

    def pulse_exceeds_range?
      @pulse_time > @pulse_range_end
    end

    def update_pulse_range
      @pulse_range_end += @pulse_range
    end

    def pulse_end
      @pulse_range_end - (@pulse_range - @pulse_width)
    end

    def on!
      @adapter.on
    end

    def off!
      @adapter.off
    end

    def on?
      @adapter.on?
    end

    def off?
      !on?
    end
  end

  class TempControl
    attr_reader :input, :output, :pid
    def initialize options = {}
      @pulse_range = options[:pulse_range] || 5000
      @input = TempSensor.new
      @output = HeatingElement.new adapter: GPIO.new(pin: 17)
      configure_automatic_control options
    end

    def configure_automatic_control options
      @target = options[:target]
      @pid = Temper::PID.new maximum: @pulse_range
      @pid.tune 44, 165, 4
      @pid.setpoint = @target
    end

    def read_input
      reading = input.read
      @last_reading = reading if reading
    end

    def calculate_power_level
      if read_input
        set_pulse_width pid.control @last_reading
      end
    end

    def set_pulse_width width
      if width
        output.pulse_width = width
      end
    end

    def control_cycle
      calculate_power_level
      output.pulse
    end
  end

  # creates temperature control with a 152.0F target
  control = TempControl.new target: 152.0
  loop do
    control.control_cycle
  end

What the above does is creates a TempControl object that interfaces with the temp sensor, feeds the input into the PID object, and feeds the PID output to the HeatingElement. The HeatingElement takes the PID output and converts it into the timeframe in milliseconds of being on, relative to the current time. When it exceeds the pulse width, it shuts off. When it exceeds the pulse range (the five second window), it resets the range timestamps. This allows multiple sampling within the 5 second window but does not change the pulse width in the middle of a window.

I just extracted this from one of my applications, so if it's a bit rough or you need clarification, feel free to ask.

prussiap commented 10 years ago

Hi, Thank you for the feedback. I've been working through the code and haven't had much free time. So there seem to be a few important tricks (other then the math)

  1. Timing the interval at which you get your present value (temp)
  2. Timing your 5000ms pulse width so that your relay is on and off
  3. And for me timing all that with whatever interrupts might be created by pushing buttons and sending data to a UI.

In other news though how did you do your manual tuning ? Do you have any sample code of what you did ? Just curious if we should start there before doing an autotune. I have some time this weekend so my plan was to experiment.

prussiap commented 10 years ago

hola, So i'm re-visiting this after a little time off. I've actually been using my sous-vide for the last 7months now and am wanting to improve on several factors. One of them is the tuning. Your tuning method just looks at the difference between the last time point and this one correct?

I'm noticing because my heating element is so efficient and rapid (rice cooker with no insulation) that It is almost impossible to find a low stepping point and then to step up one notch and not plateau. This has caused the tuning for the rice cooker to jump over the desired temp by a few degrees and to not predict it properly.

Have you explored the tuning of your PID? If so what experiments and what code have you been using for it. It might be of interest to collaborate on some of this. Also you mentioned your SSR changes which I used when I first started but was wondering if you had any new changed or modifications that have worked for you.

I'm going to slowly refactor what I have now and I would like to provide a PR with any new changes that might be useful to you

Thanks and I hope this msg finds you well.