Embedded PID Temperature Control, Part 4: The Scilab GUI

February 19, 2016 by Robert Keim

With USB communications and a Scilab graphical user interface, we can really see what the PID controller is doing.

With USB communications and a Scilab graphical user interface, we can really see what the PID controller is doing.

Supporting Information

Previous Articles in This Series

Before we get started, here is the PID control system diagram presented previously:


And here are PID-related portions of the schematic:


Much Better Than LEDs

The LED visualization scheme used in the previous article was pretty limited. The fact is, just about any visualization scheme based on nothing more than a few LEDs will be pretty weak. We need something that allows us to see exactly what our PID controller is doing—first, because it will be way more interesting, and second, because we need detailed information about the performance of the system in order to properly set the proportional, integral, and derivative gain.

For this stage of the project, then, we are going to incorporate USB capability into the EFM8 firmware and design a Scilab graphical user interface that controls the setpoint and displays, in real time, the actual measured temperatures. The “Supporting Information” section above points you to articles that provide introductory information regarding EFM8 USB functionality and Scilab. You can also scroll through my previous articles and take a look at anything that involves Scilab GUIs or USB communication.


USB Commands

The firmware has been changed so that PID operation is governed by Scilab. The EFM8 does not initiate PID functionality until Scilab tells it to, and then Scilab can halt and resume PID functionality at any time. To initiate or resume PID operation, Scilab sends ASCII “C” over the USB link, and to halt PID operation it sends ASCII “H”; both commands are just a single character without carriage return or newline or what not. The heater-drive voltage is set to 0 V whenever PID operation is halted, so keep in mind that the temperature of the heating element will gradually decrease toward room temperature during a halt condition. Scilab can also change the setpoint; this is accomplished by sending ASCII “S” followed by a one-byte binary (i.e., non-ASCII) number that represents the setpoint in degrees Celsius. These three commands are incorporated into the VCPXpress callback function.

   uint32_t API_InterruptCode;

   //get the code that indicates the reason for the interrupt
   API_InterruptCode = Get_Callback_Source();

   //if the USB connection was just opened
   if (API_InterruptCode & DEVICE_OPEN)
	   //start the first USB read procedure
	   Block_Read(USBRxPacket, USB_PACKET_SIZE, &USBBytesRcvd);
	   /*we will process the received bytes when we get
	   a callback with an RX_COMPLETE interrupt code*/

   if (API_InterruptCode & RX_COMPLETE)   //USB read complete
		   //'C' tells the EFM8 to begin or resume PID control
		   if(USBRxPacket[0] == 'C')

		   //'H' tells the EFM8 to halt PID control
		   else if(USBRxPacket[0] == 'H')

			   /*The heater-drive voltage is held at 0 V
			    * while PID control is halted.*/
			   UpdateDAC(DAC_HEATER, 0);

		   //'S' indicates that the host is sending the setpoint
		   else if(USBRxPacket[0] == 'S')
			   /*The setpoint temperature is restricted to
			    * positive integers not greater than 100 
			    * degrees C. Scilab sends the setpoint as a 
			    * normal binary number, not as ASCII characters,
			    * so that the EFM8 doesn't have to convert
			    * from ASCII to binary.*/
			   Setpoint_Temp = USBRxPacket[1];

	   //continue with the next USB read procedure
	   Block_Read(USBRxPacket, USB_PACKET_SIZE, &USBBytesRcvd);

PID Flow

The PID routine in the main while loop now begins by checking the PID_ACTIVE flag, which is initialized to FALSE. Execution remains at this point until a “C” command from Scilab causes the EFM8 to change this flag to TRUE. Another important difference is that the measured temperature is sent to Scilab at the beginning of every iteration. In previous projects, Scilab first requested data via a USB command, then the EFM8 sent the data as a response to the request. In this project, Scilab does not have to ask for data; the EFM8 sends a three-byte measured-temperature packet during every iteration, and Scilab just receives and displays the data. This reduces the USB traffic and the burden on the EFM8’s processor, and consequently we should have less trouble with the higher update rates required for improved visualization of changes in the controlled variable. The current firmware uses an update interval of two seconds, which seems to provide adequate control and visualization without overburdening the EFM8 or the Scilab GUI. The following code excerpt covers the PID portion of the main while loop:

while (1)
	/*First, we check PID_ACTIVE.The following while statement
	 * suspends PID functionality until the EFM8 is commanded by
	 * Scilab to begin or resume PID control.*/
	while(PID_ACTIVE == FALSE);

	while(TEMP_DATA_READY == FALSE);	//wait until the SPI transaction is complete

	Measured_Temp = ConvertMAX31855Data_to_TempC();

	//send measured temperature to Scilab

	Error = Setpoint_Temp - Measured_Temp;

	/*We don't want the integral error to get
	 * way too large. This is a standard problem
	 * referred to as integral windup. One solution
	 * is to simply restrict the integral error to
	 * reasonable values.*/
	Error_Integral = Error_Integral + Error;
	if(Error_Integral > 50)
		Error_Integral = 50;
	else if(Error_Integral < -50)
		Error_Integral = -50;

	Error_Derivative = Error - Previous_Error;
	Previous_Error = Error;

	PID_Output = (K_proportional*Error) + (K_integral*Error_Integral) + (K_derivative*Error_Derivative);

	/*We need to restrict the PID output to
	 * acceptable values. Here we have limited it
	 * to a maximum of 200, which corresponds
	 * to about 780 mA of heater-drive current, and
	 * a minimum of 0, because we cannot drive
	 * less than 0 A through the heating
	 * element.*/
	if(PID_Output > 200)
		PID_Output = 200;
	else if(PID_Output < 0)
		PID_Output = 0;

	//here we convert the PID output from a float to an unsigned char
	Heater_Drive = PID_Output;

	UpdateDAC(DAC_HEATER, Heater_Drive);


Though insufficient by itself, LED feedback is still a handy way to monitor the operation of the system. It is also helpful as a way to confirm that the data displayed by Scilab is consistent with what is really going on in the EFM8. To make the LED feedback more suitable for this second purpose, we will take a different approach in this stage of the project: If the measured temperature is within ±2°C of the setpoint, we turn on only the green LED. If the measured temperature is more than 2°C below the setpoint, we turn on only blue. If the measured temperature is more than 2°C above the setpoint, we turn on only red. So, green = good, blue = too cold, and red = too hot. The advantage of this scheme will be apparent when the measured temperature is oscillating around the setpoint, because the changes in LED color will be in sync with the temperature variations displayed in the GUI. The LED control code is included in the second portion of the main while loop.

	 /*LED visualization: If the measured temperature is within
	 * plus/minus 2 degrees C of the setpoint, we turn on the
	 * green LED. If the measured temperature is more than
	 * 2 degrees below the setpoint, we turn on the blue LED.
	 * If the measured temperature is more than 2 degrees
	 * above the setpoint, we turn on the red LED.*/
	if(Measured_Temp >= (Setpoint_Temp-2) && Measured_Temp <= (Setpoint_Temp+2))
		UpdateDAC(DAC_RGB_R, 0);
		UpdateDAC(DAC_RGB_B, 0);
		UpdateDAC(DAC_RGB_G, 100);

	else if(Measured_Temp < (Setpoint_Temp-2))
		UpdateDAC(DAC_RGB_R, 0);
		UpdateDAC(DAC_RGB_B, 100);
		UpdateDAC(DAC_RGB_G, 0);

	else if(Measured_Temp > (Setpoint_Temp+2))
		UpdateDAC(DAC_RGB_R, 100);
		UpdateDAC(DAC_RGB_B, 0);
		UpdateDAC(DAC_RGB_G, 0);

	/*Here we wait until the PID interval has expired,
	 * then we begin a new iteration. The interval is
	 * currently set to 2 seconds.*/
	while(PID_WAIT == TRUE);

Here is a link to download all the source and project files.


Here is what the GUI looks like when it is not active:

It was designed with the help of the GUI Builder toolbox, which you can download through Scilab’s ATOMS module manager:

Here is a link to download the GUI script (it’s just a single text file).

First you use the “Open VCP Port” button to establish a virtual COM port connection to the EFM8. Next, choose the setpoint. Scilab restricts the setpoint to integers less than or equal to 100. If you enter a value greater than 100, Scilab will automatically reduce it to 100 and display “Setpoint reduced to maximum allowable value, i.e., 100°C” in the message bar below the “Open VCP Port” button. Likewise, if you enter a non-integer, Scilab rounds it to the nearest integer and displays a message to that effect. Now you are ready to click the “Activate PID Control” button. The setpoint is not actually sent to the EFM8 until you click this button, and you cannot change the setpoint while PID control is active. This will be obvious because the setpoint text-entry box is grayed out during active PID control. To change the setpoint, you have to click “Halt PID Control,” then change it, then click “Activate PID Control” to resume PID operation.

When you are done using the GUI, first click “Halt PID Control” (unless PID control is already inactive), then click “Close VCP Port,” then close the GUI window. If you don’t follow this procedure, you might need to restart Scilab or reset the EFM8 or some such. It’s annoying, but not catastrophic.

Let’s take a quick look at some salient portions of the Scilab script. First, this is how Scilab sends the “S” (setpoint) and “C” (initiate/resume PID control) commands, after you click “Activate PID Control”:

The Setpoint value is taken from the text-entry box as follows:

When PID control is active, Scilab repeatedly checks the virtual COM port receive buffer. Once three bytes have been received, it reads the three bytes, converts them into a temperature value, and adds them to an array that contains all the measured temperature values received since the last time that “Activate PID Control” was clicked:

The plot that displays the measured temperatures also has a green dotted line that corresponds to the setpoint. The following code is used to generate the setpoint line:


In the previous article, we looked at the (LED- and oscope-based) results for a proportional-only system and a proportional-integral system. In both cases the control task was to bring the heating element from 30°C to a setpoint of 50°C. We were able to determine that 1) the P-only system never reached the setpoint and 2) the PI system did reach the setpoint, though with some amount of overshoot. Now let’s take a look at GUI-based results for the same control task. First the P-only system:

We can see that the P-only system actually gets very close to the setpoint, but without integral gain, the temperature decreases somewhat and reaches a steady-state value that is about 2°C below the setpoint. As we mentioned in the previous article, P-only systems are known for their susceptibility to significant steady-state error.

Here is the plot for the PI system:

We can see here that the PI system is actually worse than we thought. It does indeed reach the setpoint, but it causes more than just overshoot—this particular configuration actually leads to sustained (or at least long-term) oscillation around the setpoint.


The video at the end of this article (it runs at 16x normal speed) shows the LED behavior that corresponds to the plot for the PI system. In the video you will also notice an important hardware detail: the PCB we are using for this project actually has two USB connectors, one for power and one for data. A typical USB port cannot supply more than 500 mA. Thus, to get the higher current we need for the heating element, the board includes the option of taking power from a separate connector. So one of the USB cables is connected to a USB charger that can supply something like 1200 mA, and the other is connected to a USB port on the PC.

In the next article we will use our fancy new GUI to explore how different P, I, and D gains influence the performance of the system.

Next Article in Series: Embedded PID Temperature Control, Part 5: Adjusting Gains


Give this project a try for yourself! Get the BOM.

  • G
    Gaurav16 August 08, 2020

    Really good article, very informative. Learnt stuff about communications protocols easier than ever, just a small doubt as I am trying this project too.
    You mentioned the firmware of the MCU has been changed, how exactly do I go about it?

    Like. Reply
    • RK37 August 11, 2020
      All the changes are present in the code files that you can download by clicking on the button labeled
      Like. Reply