Thresholding for PID loops


Recommended Posts

We work in fuel-cells. In our present application, we have a set of fuel-cell stacks, each comprised of N-cells, which must be monitored as they are loaded. As the stacks are loaded, their cell-voltages drop. If the lowest cell-voltage of a particular stack drops below a certain threshold, I need to be able to activate a PID loop which will reduce the load on that stack until the lowest cell-voltage returns to the set-point value (i.e. the threshold).

We are obtaining the cell-voltage data through a custom-designed Modbus peripheral. Data acquisition is performed by creating a set of channels which use the "Read Holding U16" I/O type. DAQFactory is apparently smart enough to request the data all in one Modbus command, asking for an array of data.

It seems to me that the best way to approach this is as follows:

  • Create a watch-dog sequence for each stack which monitors the minimum cell-voltage: seqStack1WD, seqStack2WD, etc. Each watch-dog sequence will update a V-channel for the sake of logging and data persistence.
  • Create a PID loop for each stack: pidStack1Load, pidStack2Load, etc.
  • If seqStackNWD sees a cell-voltage drop below a certain threshold (minus hysteresis), it will activate pidStackNLoad. If seqStackNWD sees the minimum cell-voltage rise back above this threshold (plus hysteresis), it will deactivate pidStackNLoad.

Here are my questions:

(1) Does this sound like a good way to go about things?

(2) How to I coordinate each seqStackNWD sequence with its set of cell-voltage data? What I mean is, the cell-voltages are being grabbed every second. Is there a way to have seqStackNWD fire each time the cell-voltages have been completely updated?

Regards,

Brian

Link to comment
Share on other sites

1) kind of. I'm not sure why you need a PID loop. PID loops are often overused, and a simple slow thermostat or P only loop would suffice. But I don't know your system.

2) there are two ways to coordinate it: a) use a channel event, B) trigger the polling from the sequence. I personally would do #2 in this case as you can better make it scale-able. But first, a question: are you using a single Modbus peripheral to query all your stacks, or one for each stack? If a single one, does it need to be queried in a single query for all stacks, or would a single query for each stack be ok? With that I can better address how to properly do this.

Link to comment
Share on other sites

Maybe I don't need a PID loop, but I don't know what my other options are. In reality, we'd probably only need a PI loop. What do you mean by a "thermostat"? The point is this: if a cell-voltage of stack N drops below a certain threshold (minus hysteresis) I need something to automatically throttle the current being drawn from stack N until this cell-voltage comes back to the threshold. Once at the threshold, it would be best to regulate the current limit to maintain the minimum cell-voltage at the threshold. This sounds like a PID loop.

A Modbus peripheral is needed per stack to grab this cell-voltage data. Thus, to obtain the cell-voltages of stack N, I need to address a Modbus message to the board assigned to it.

Concerning the options:

(1) What do you mean by channel event? There were some mentions in the user guide, but nothing substantial.

(2) I was thinking of this also. Since I need to have a watch-dog sequence of some sort, perhaps it is better to make the polling depend on the sequence instead of vice versa.

I have another question concerning PIDs. How does one implement override type of behavior? For instance, let's say I have not only a minimum cell-voltage threshold but also a maximum stack current threshold. Stack N is currently being throttled because of its weakest cell, but the current from this stack has not breached the maximum current threshold. Then, imagine I change the maximum current threshold using a variable value component, and the stack suddenly has a current greater than the maximum. Obviously a control should kick in to throttle the stack even more, but now I have two competing control loops trying to change the same control variable.

I solved this in the past (in a custom programmed system) by having an array of dependent variables being watched, and whichever became the most divergent became the dependent variable. Its sort of like a multiplexed array of error signals, and the worst is the one passed through to become the focus of the control loop. I could imagine a sequence which monitors a set of dependent variables, and which assigns the worst-case divergence of all of them into a virtual channel, and this virtual channel becomes the focus of the control loop.

Regards,

Brian

Link to comment
Share on other sites

Yeah, that sounds like a PID loop. It was unclear if you were just trying to avoid out of range, or actually do control all the time. A thermostat is a simple on/off. Turn on at 65 degrees, turn off at 70 (or whatever units you want). As for options, if you have separate Modbus devices per stack, I'd let the sequence control the polling. You might even consider putting the PI calculations inside the loop too, but that is a bit more advanced. Anyhow, I would create one master sequence with the logic for a stack. It would actually be a function, something like:

function stackLoop(stackNum)

// lots of code here

and then create a separate sequence for each stack with a single line of script:

stackLoop(N)

where N is the stack number this sequence corresponds too. You can then run these sequences (don't run the stackLoop sequence directly, its a function) and they'll run in separate threads, concurrently with each other, but will all run the same script (save the stack number).

Then you just need to write the script in stackLoop to use the stackNum passed in.

The script probably has this format:

execute("beginpid(stack_" + stackNum + ")")
while(1)
channel.readGroup("stack_" + stackNum)
// put logic here
delay(1)
endwhile[/CODE]

First, the PID:

1) I recommend leaving the PID running at all times. To keep it from driving itself haywire, set its setpoint to the current PV every iteration of the loop. When you want it to start acting, leave the setpoint in one spot (the minimum voltage probably plus some threshold I'd imagine)

2) Don't wire the PID directly to an output. Have it change a variable. You can then decide how and when to use this value inside your stackLoop. I'd use a single array for both SP and one for output, so in an autostart sequence do:

global PID_SP = fill(0, N)

global PID_Output = fill(0,N)

where N is the number of stacks (or some value larger) and 0 is the default. You can then address these in each PID loop by stack number. PID_SP[X], or in stackLoop: PID_SP[stackNum]. If you number your stacks from 1, you'll need to do N+1 in the fill().

3) for the readGroup() to work, you'll need to put all the input (input ONLY, not output) channels for a stack under the same group name, stack_N where N is the stack number. You are also going to want to name your channels using some sort of common nomenclature, such as SN_Voltage where N is the stack number, so S1_Voltage. That way you can access these channels from within stackloop using evaluate() and execute() and your stackNum variable.

The logic is up to you at that point. Hopefully this gives you some ideas on how to do this in a scalable way. By doing so, you can make a change to the stackLoop function and it will apply across all stacks.

BTW: you might consider putting the contents of the loop in another sequence function, so stackLoop becomes just:

[CODE]execute("beginpid(stack_" + stackNum + ")")
while(1)
stackLoopInternal(stackNum)
endwhile[/CODE]

and stackLoopInternal is a function just like stackLoop, but with only the contents of the loop. Make sure you don't forget the delay() in stackLoopInternal.

The reason for this is that changes you make to stackLoopInternal will apply as soon as you save them without having to stop and restart all your sequences. Changes to stackLoop, however, would require a restart of the calling sequence because a sequence (or sequence function) that is running won't see changes.

Link to comment
Share on other sites

I understand what you are saying here. Thinking more on the problems to be solved control-wise, I think it will be best for me to implement my own control loops instead of using the built-in PIDs. Fortunately, I've done this sort of stuff before, so I'm pretty sure it's achievable.

One last question: how and where do I implement a "function"? Could you point out a section in the user guide to me?

Regards,

Brian

Link to comment
Share on other sites

Its in section 5.17. I recommend simply reading (or at least skimming) the entire chapter 5, even if you are already a programmer.

I doubt you'll have any problem implementing what you want in script control wise. DAQFactory scripting is powerful enough to do it for sure. Using the built in PID loop feature simply makes it easier for non-programmers, but like just about everything in DAQFactory, you can always drop down to script and get more control over the process.

Link to comment
Share on other sites

Archived

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