PID to control PWM


mkelly

Recommended Posts

System Configuration:

Labjack controlling relays via PWM from DAQFactory. Relays control heat wires that set the system temperature. Process variable is monitored by thermocouples.

PWM Setup is obtained via the TimerPWM.ctl example file. Modified to allow for 3 channels. Works fine when inputting a value directly from a variable text box.

// configure PWM timer:
//Set the timer/counter pin offset to 4, which will put the first timer/counter on FIO4.
AddRequest (ID,  LJ_ioPUT_CONFIG, LJ_chTIMER_COUNTER_PIN_OFFSET, 4, 0, 0)


// use system clock so works on U3 and UE9:
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_BASE, LJ_tc1MHZ_DIV, 0, 0)
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chTIMER_CLOCK_DIVISOR, 15, 0, 0)


//Enable 1 timer.  It will use FIO4.
AddRequest(ID, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 4, 0, 0)


//Configure Timer0 as 16-bit PWM.  
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 0, LJ_tmPWM16, 0, 0)
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 1, LJ_tmPWM16, 0, 0)
AddRequest(ID, LJ_ioPUT_TIMER_MODE, 2, LJ_tmPWM16, 0, 0)


//Set the PWM duty cycle.
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 0, 65535 * PWMWidth / 100, 0, 0)
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 1, 65535 * PWMWidth2 / 100, 0, 0)
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 2, 65535 * PWMWidth3 / 100, 0, 0)

//Execute the requests.
GoOne(ID)
ErrorHandler(ID)

As mentioned, this controls the heat fine if set to a value. As the relays open when the signal grounds, a 90% PWMwidth value relates to a 10% duty. So 100% would be always closed, and a 0% would be always open.

I'm trying now to have them be controlled via a PID loop. Configurations of each PID can be seen in GB_control.PNG and GT_control.png

post-8591-0-39391300-1335910879_thumb.pn

post-8591-0-17463100-1335910880_thumb.pn

If I remove Reverse Acting the output channel value shoots up to .1 and if I apply it it shoots down to 99.9

However the real issue is that these changes of PWMWidth and PWMWidth2 values have no observable affect on the PWM signal.

I'm stumped as to how to output this value to the SetupPWM sequence.

Attached as well is the ctl file I'm using for this.

HAS DAQ Rev A GA1.ctl

Link to comment
Share on other sites

The problem is that the math in PID requires that the output center on 0. My typical recommendation is to have setup the PID loop to range from -100 to 100, and then scale that to the appropriate output value, in your case 0 to 100. The scaling factor is simply: (PIDOutput + 100) / 2. You can put that right in the AddRequest() line where you have PWMWidth.

I didn't look at your file, but note that once you have the PWM setup, to change the width, you only have to do the PUT_TIMER_VALUE command.

Link to comment
Share on other sites

I set the range to be -100 to 100 and added the scaling factory into the Add request.

It looks like this works, but initially only one of my 2 PID loops looked like it was working. I did some tweaking and now it seems like they're both responding to the output. An hour or 2 till it reaches the setpoint and I'm concerned it won't slow it's ascent if the output isn't getting passed through and stays at fully open.

I didn't look at your file, but note that once you have the PWM setup, to change the width, you only have to do the PUT_TIMER_VALUE command.

If it necessary to call this command value every time I change my PWMWidth value? If yes would I have to create a sequence files that acts as the output channel? Or will it just modify the sequence value once the PWMWidth value changes?
Link to comment
Share on other sites

First part: if you fear the ascent rate won't slow, you need to adjust the PID parameters, in particular probably the D parameter as that is used to dampen rate of change.

Second part: simply changing PWMWidth should change the timer value, provided PWMWidth is a Timer channel.

Link to comment
Share on other sites

Ok I'm stumped. It really seems like there's a disconnect between the Output channel of my PID loop and the PUT_TIMER_VALUE command.

So here's a graph of my test runs. The reactor has 2 heat wraps that can control the upper and bottom half seperately

post-8591-0-02934400-1336422949_thumb.pn

Brown = Top

Magenta = Mid 1

Pink = Mid 2

Light Pink = Mid 3

Teal = Bottom

As you can see at around 1:45 I reset the PID loops. I also manually set the PWMWidth2 for the bottom heat tape to -100 (100% duty)

After around 10 minutes the PID loop sees that it's reaching the set point and ramps up to 100 (0%) duty as seen in dark red.

Well 5 minutes after that I notice that neither the bottom temperatures are continueing to climb so I manually set the the PWMWidth2 to 100 (0% duty). I also noticed that the top heat tape was not responsing, so I set PWMWidth to -100 (100% duty)

After around another 15 minutes I notice again the output from the PID (as seen in green) is not showing a response so I set the PWMWidth value to 100 (0% duty)

I'm currently waiting to see what happens once it stabalized back down. Though I'm fearful that it will not reactivate like it did prior.

So it seems to me like the error in my program is the Output channel.

Can I not set it directly to PWMWidth? Do I have to make it a channel or a sequence?

Can I just add something to the event tab on the PID loop?

Link to comment
Share on other sites

Update:

I made a new sequence called "VaryPWM"

//Set the PWM duty cycle.
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 0, 65535 * ((PWMWidth+100)/2) / 100, 0, 0)
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 1, 65535 * ((PWMWidth2+100)/2)  / 100, 0, 0)
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 2, 65535 * ((PWMWidth3+100)/2)  / 100, 0, 0)
AddRequest(ID, LJ_ioPUT_TIMER_VALUE, 3, 65535 * ((PWMWidth4+100)/2)  / 100, 0, 0)
AddRequest(ID2, LJ_ioPUT_TIMER_VALUE, 0, 65535 * ((PWMWidth5+100)/2)  / 100, 0, 0)
AddRequest(ID2, LJ_ioPUT_TIMER_VALUE, 1, 65535 * ((PWMWidth6+100)/2)  / 100, 0, 0)
AddRequest(ID2, LJ_ioPUT_TIMER_VALUE, 2, 65535 * ((PWMWidth7+100)/2)  / 100, 0, 0)
AddRequest(ID2, LJ_ioPUT_TIMER_VALUE, 3, 65535 * ((PWMWidth8+100)/2)  / 100, 0, 0)


//Execute the requests.
GoOne(ID)
ErrorHandler(ID)

GoOne(ID2)
ErrorHandler(ID2)

then I added a call to this sequence in the PID loops

VaryPWM()

and it looks like it is responding to changes in PWMWidth now.

post-8591-0-56308100-1336427536_thumb.pn

One (hopefully) final question: While my system is operating I want the PID to control the high temperature in it's control zone.

Typically this occurs on Mid 1 and Mid 3 during heat up. Once the system is operating the max point tends to move from Mid 1 to Mid 2 and Mid 3 to Bottom. Is it possible to have it pick the maximum temperature to use in the PID loop. I tried some things with the Max() function but I wasn't sure if it could do the calculation with a channel instead of a variable or something else.

Link to comment
Share on other sites

From post #6: I'm not quite sure what you mean. Do you want to use the max of 4 channels as the process variable for the PID loop? If so, do something like:

max(concat(mid1[0], mid2[0], mid3[0], bottom[0]))

Note that concat can only have 20 parameters. You are a long way from this, but I thought I'd mention it.

As for auto-tune: the relay height is how much it changes the output with each iteration. Basically, it will do this: when you start the autotune, which should be done on a stable system, it will capture the current output (OV). Then it will look at PV compared to SP. If PV > SP, it will change the OV to OV - relay height. If PV < SP, it will change the OV to OV + relay height. It will repeat this evaluation every iteration, thus causing an oscillation of PV. It then uses the timing of this oscillation to give you auto-tune parameters. This is one of two common ways of doing auto-tune and typically the preferred method when the system can't be deviated from some SP by a lot during auto-tune for physical reasons. The other common method is to trigger a big change in SP and then look at the slope and timing of the curve that follows.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.