Scilab Recipe 4: GUI Basics

While most articles on this website focus on the computational aspects of Scilab as control engineering aids, from time to time we would touch upon other features of the software, such as user interface design. As computer technology evolves, a developer has more tools and options to make her application attractive and user-friendly. A Graphical User Interface (GUI) consists of common gadgets we are accustomed to already, such as push and radio buttons, sliders, editable input fields, check boxes, etc. The designer’s task is to create and arrange them in a graphical window, and then orchestrate their functions to a particular set of requirements.

In this recipe, we discuss the process of designing a simple GUI for our PID feedback simulation. The prime function of this PIDGUI window is to allow easy adjustment of the controller parameters and saturation limits, then update the corresponding variables in Scilab workspace. The actual simulation and response plots are performed in the Xcos model. Once you grab the basics, you can build up to a more advanced GUI by your own.

Of course, one would not bother to build a GUI only to adjust the 3 parameters of a standard PID block. Figure 1 shows a more complicated Xcos PID diagram advpid.zcos used for simulation. The diagram has 2 feedback loops for comparison. The upper loop contains a simple PID block provided by Xcos. The controller used in the lower loop, on the other hand, is an advanced PID with the following additional features

  • To lessen the effect of measurement noise, derivative part is implemented as a filter with parameter N
  • Back calculation anti-windup scheme is implemented with tracking gain K_t
  • Setpoint weightings for proportional and derivative paths can be adjusted via w_p and w_d, respectively



Figure 1 Advanced PID diagram [advpid.zcos]

Together with the 3 original PID gains, there are a total of 7 adjustable parameters in the advanced PID controller. Moreover, it would be nice if we could easily adjust the limits of saturation blocks to see the integrator windup effects. It is therefore convenient to handle all these 9 parameters by a GUI window.

The final appearance for our PIDGUI application will be like in Figure 2. This window is created by executing the script pidgui.sce. The design is discussed in more details below.



Figure 2 the PIDGUI window used for parameter adjustments

GUI Design Procedure

For a typical application, GUI design proceeds as follows

  1. Create a graphical window
  2. Add control objects to the parent window at desired positions
  3. Assign properties to each control object
  4. Write callback functions so that the GUI performs the desired task
  5. Add menu (if any)
  6. Test the design

We discuss each step below, except step 5 since we do not need any menu for our simple application.

Window Creation

Before doing anything else, we want to have a parent window that contains all the control objects. A convenient way is to use function createWindow()

-->mywin = createWindow();
-->mywin.Position = [0 0 400 400];

that creates a blank window shown in Figure 3 and returns a handle mywin. The window position on the screen is set by assigning to the Position property.



Figure 3 blank window generated by createWindow()

Control Objects

Now we need to create the control objects and place them in the desired locations. From Figure 2, the object types needed for this application are: 19 texts, 7 sliders, 9 edit input fields, and 2 push buttons. These can be created by the same command uicontrol, with the style and related properties tailored to each particular control. Let us start from the simplest one: text object.

A text field is normally used to label other objects, or to provide more information to the user. In our PIDGUI application all texts are static. Some online example for uicontrol command suggests a method to create a text that reads “Label” as follows

-->uicontrol(mywin, "style", "text", "units", "normalized", "position", [0 0.5 0.5 0.5], "string", "Label");

but as you can see, it is a rather long command, and even longer if you need to assign more properties. As a beginner with myopia, I prefer to break it to several lines*. The chunk below creates the red “PID Parameters” text at the top of PIDGUI window in Figure 1.

-->pidlabel=uicontrol(mywin,"style","text");
-->pidlabel.Units="normalized";
-->pidlabel.Position = [0.2 0.88 0.4 0.05];
-->pidlabel.String = "PID parameters";
-->pidlabel.BackgroundColor = [0.8,0.8,0.8];
-->pidlabel.FontSize = 18;
-->pidlabel.ForegroundColor = [0.7,0,0];

* This method has a drawback worth mentioning. Suppose you make a mistake in typing property field name, it might not cause an instant error. Remember that Scilab commands are case-sensitive. For example, pidlabel.units is different from pidlabel.Units. Your GUI might not work correctly afterwards, and this kind of bug could cause some headache.

It is now easier to study the object creation process. The first line lauches uicontrol, with arguments mywin (parent window), and “style”,”text” to specify the object type as a static text message. The command returns a handle pidlabel for this text. Now we can assign other properties to this handle, such as “normalized” means the total height and width is normalized to 1. So any dimension is specified as fraction of 1, say, 0.2 equals 20% of total width or height.

Position property is a 1 x 4 vector with format [startx starty width height], where startx and starty indicates the x-y coordinate of lower left corner of the object

String is the text message we want to display, in this case it is “PID parameters.” Interestingly, Latex commands can be embedded in a text message by putting them between a pair of dollar-sign symbols.

Other fields in the handle are quite obvious. Try adjusting them to see the changes.

For the PIDGUI in Figure 2, the 7 controller parameters are arranged in rows of control objects. All rows follow the same pattern. So it suffices to explain only the first row for K_p control. Also, the rightmost text information is not relevant to the discussion.
To generate the left text label, the slider, and edit control in the K_p parameter row, use the uicontrol commands with styles adjusted to them; i.e., for the left text label

-->kplabel=uicontrol(mywin,"style","text");
-->kplabel.Units="normalized";
-->kplabel.Position = [0.02 0.8 0.05 0.05];
-->kplabel.String = "$K_p$";
-->kplabel.BackgroundColor = [0.8,0.8,0.8];

the slider

-->kp = 100; 
-->sl_kp=uicontrol(mywin, "style","slider");
-->sl_kp.Min=0;
-->sl_kp.Max=1000;
-->sl_kp.Value=kp;
-->sl_kp.Units="normalized";
-->sl_kp.Position=[0.1 0.8 0.4 0.05];
-->sl_kp.Callback="updateslider_kp";

and the edit box

-->eb_kp=uicontrol(mywin,"style","edit");
-->eb_kp.String = msprintf('%5.3f',kp);
-->eb_kp.Value = kp;
-->eb_kp.Units="normalized";
-->eb_kp.Position=[0.52 0.8 0.15 0.05];
-->eb_kp.Callback = "updateedit_kp";

At this point our PIDGUI window should look like the one in Figure 4. Do not click on the slider or edit box yet because we have not written Callback functions for them.



Figure 4 PIDGUI with only a row for K_p parameter

Our task is to synchronize the data between the slider and edit box. Whenever the user adjusts the slider, the value in the edit box must be updated. On the other hand, if the user prefers to type a value in the edit box, the slider must adjust itself to match. For simplicity of discussion, let us limit the range K_p to between 0 – 1000.

Let us write a callback function for the slider first. Name it updateslider_kp (if you wish to change the name, do so accordingly in the sl_kp.Callback=”func_name”; line above.

The slider callback function is invoked whenever the user adjusts it. When such event happens, we want to transfer its value to the edit object value, and string displayed in the box. So our first attempt is

-->function updateslider_kp(sl_kp)
-->   eb_kp.Value = sl_kp.Value;
-->   eb_kp.String = msprintf('%5.3f',eb_kp.Value);
-->endfunction

Now we can move the slider without error. But the edit box string does not change!
What goes wrong?

Try moving the slider to about middle and check its value.

-->sl_kp.Value
 ans  =
    480.

Then read the edit object value

-->eb_kp.Value
 ans  =
    100.

This means the edit object is not updated by the callback function. It did take me a while to figure out. The problem is with variable type in a function. the slider callback function just updates to a copy of eb_kp, not the real one. And that copy is destroyed after the function ends.

We need to tell the function that eb_kp is global by adding a line at the beginning of the callback function.

-->function updateslider_kp(sl_kp)
-->   global eb_kp;  // declare eb_kp as a global variable
-->   eb_kp.Value = sl_kp.Value;
-->   eb_kp.String = msprintf('%5.3f',eb_kp.Value);
-->endfunction
Warning : redefining function: updateslider_kp         . Use funcprot(0) to avoid this message

Ignore the warning message. Move the slider again. This time we can see the update in the edit box.

Similarly, a callback function for edit object is written as follows

-->function updateedit_kp(eb_kp)
-->    global sl_kp;  // declare sl_kp as global
-->    eb_kp.Value = eval(eb_kp.String);
-->    if (eb_kp.Value < 0)
-->        disp('Kp value below range. Set to minimum');
-->        eb_kp.Value = 0;
-->        eb_kp.String = msprintf('%5.3f',eb_kp.Value);
-->    elseif (eb_kp.Value > 1000)
-->        disp('Kp value above range. Set to maximum');
-->        eb_kp.Value = 1000;
-->        eb_kp.String = msprintf('%5.3f',eb_kp.Value);            
-->    end
-->    sl_kp.Value = eb_kp.Value;
-->endfunction

Note that we limit the input value to 0 – 1000 range before updating the slider. Input some value, say, 250, to see the slider moves to 1/4 of its total length.

Get the idea? Good. Implement this for all 7 controller parameters. The last row is for saturation limits. They do not have slider controls so the callback functions are even simpler. The only objects left are the [Update] and [Restore] buttons. The former is used to transfer all data in all control objects to corresponding variables in Scilab workspace, and the latter just does the opposite.

Since the mechanics of two buttons are quite similar, here we choose to discuss only the [Update] button. To create the object, issue this code

-->updatebutton=uicontrol(mywin,"style","pushbutton");
-->updatebutton.Units = "normalized";
-->updatebutton.Position = [0.05 0.01 0.2 0.08];
-->updatebutton.String = "Update";
-->updatebutton.BackgroundColor=[0.9 0.9 0.9];
-->updatebutton.Callback = "updateparm";
-->updatebutton.Relief="raised";

together with the following callback function, assuming that controls for all parameters have been implemented

-->function updateparm(updatebutton)
-->  global kp ki kd N kt wp wd llim ulim;
-->  kp = eb_kp.Value;
-->  ki = eb_ki.Value;
-->  kd = eb_kd.Value;
-->  N = eb_N.Value;
-->  kt = eb_kt.Value;
-->  wp = eb_wp.Value;
-->  wd = eb_wd.Value;
-->  llim = eb_llim.Value;
-->  ulim = eb_ulim.Value;
-->endfunction

Similar to the case explained earlier, the variables being written to must be declared as global.

Testing PIDGUI

Assuming you have finished all the task to make the graphical window in Figure 2 work as desired, now it is time to launch our PIDGUI

-->exec('pidgui.sce',-1);

The user interface should appear, with some default values for the parameters and their ranges. You can edit the script file to put in your choice of values. Note also that running the script file closes all other graphic windows. If that is undesirable, remove the command xdel(winsid()); at the top.

A nicer way to override the parameter values is to create another setup file with your plant and parameters tailored to that plant. The script advpid.sce contains the following commands that create a plant and parameters tuned by ZNFD method.

s = poly(0,'s');
// plant used in Astrom and Hagglund book
P = syslin('c',1/(s+1)^3);
 
// pid gains for above plant
// tuned by ziegler nichols frequency response method
ku = 8;   // ultimate gain
tu = 3.5;  // ultimate period
kp = 0.6*ku;
ki = kp/(0.5*tu);
kd = 0.125*kp*tu;
N = 10;
//
kt = 1.2;  // back calculation gain for anti-windup

Launch it

-->exec('advpid.sce',-1);

and import parameter values into PIDGUI by clicking the [Restore] button.

Now launch the Xcos model advpid.zcos in Figure 1. Click Simulation-Start. We would see a response comparison between the upper loop (standard PID) and lower loop (advanced PID) . They do not differ much at this time because the saturation limits are large. Try reducing them to, say, a limit for 12-bit DAC. In the Saturation Limits section of PIDGUI, put -2047 and 2047 to the lower and upper edit boxes, respectively. Run the simulation again. You ‘d see the result shown in Figure 5.



Figure 5 PIDGUI in action

The rest is left for your own experiment. Start with the proportional and derivative setpoint weights. How would they affect the response?

Conclusion

PIDGUI is not a standalone application. It only aids parameter adjustments for an Xcos model. The purpose is to introduce Scilab GUI features to the reader, who from now would understand the concept and be able to create a more sophisticated GUI window by her own. There is always something to explore in Scilab. So enjoy!

Supplement

Scilab and Xcos files used in this article

Download all as zip file: pidgui.zip

Comments

comments

Comments are closed.