The Proportional-Integral-Derivative (PID) controller is a fundamental block of several control systems due to its simplicity and effectiveness. Whether you’re a professional control engineer or a student jumping into embedded systems, understanding how to implement a PID controller in C is a must. This post will take you through the architecture, implementation, and simulation of a PID controller in C, providing a starting point for your projects.
Implementing a PID Controller in C
A PID controller calculates the error value as the difference between a desired setpoint and a measured process variable, then produces a control command based on terms that are proportional to such error, to its integral, and its derivative.
The PID controller proposed is implemented using a structure in C, making it reusable. This method allows multiple instances of the controller to operate simultaneously, each maintaining its own state.
Architecture of the Program
The architecture consists of the following components:
- Object Model: An object with a mass m = 10 kg slides on a surface, subject to viscous friction with a coefficient k = 0.5 N·s/m, and is pushed by a force F. This object represents our plan and it is introduced in the program solely with the purpose of simulating the PID controller.
- PID Controller: The core logic that adjusts the force applied to the object to achieve the desired position.
Object Model Description:
- Mass (m): 10 kg
- Damping Coefficient (k): 0.5 N·s/m
- Force (F): The applied force in Newton, limited to the range [-100, 100] N
The differential equation governing the object’s motion is:
![]()
Where z is the position of the object in meters.

C Code Breakdown
PID Structure
The PID controller structure in C includes variables for proportional, integral, and derivative gains, as well as state variables for maintaining the integral of the error and previous error for derivative calculation.
struct PID
{
float Kp; // Proportional gain constant
float Ki; // Integral gain constant
float Kd; // Derivative gain constant
float Kaw; // Anti-windup gain constant
float T_C; // Time constant for derivative filtering
float T; // Time step
float max; // Max command
float min; // Min command
float max_rate; // Max rate of change of the command
float integral; // Integral term
float err_prev; // Previous error
float deriv_prev; // Previous derivative
float command_sat_prev;// Previous saturated command
float command_prev; // Previous command
};
PID_Step Function
This function represents a single step of the PID controller, intended to run on a microcontroller in a task scheduled every 100 ms. Key features include:
- Filtered Derivative: Smoothens the derivative term to reduce noise.
- Command Saturation: Limits the command to a specified range to prevent.
- Rate Limiter: Restricts the rate of change of the command to avoid sudden changes.
- Anti-Windup Technique: Uses back-calculation to prevent the integral term from winding up.
The PID_Step function adjusts the force
applied to the object based on the error between the desired and actual positions.
float PID_Step(struct PID *pid, float measurement, float setpoint)
{
/* This function implements a PID controller.
*
* Inputs:
* measurement: current measurement of the process variable
* setpoint: desired value of the process variable
* pid: a pointer to a PID struct containing the controller parameters
*
* Returns:
* command_sat: the control output of the PID controller (saturated based on max. min, max_rate)
*/
float err;
float command;
float command_sat;
float deriv_filt;
/* Error calculation */
err = setpoint - measurement;
/* Integral term calculation - including anti-windup */
pid->integral += pid->Ki*err*pid->T + pid->Kaw*(pid->command_sat_prev - pid->command_prev)*pid->T;
/* Derivative term calculation using filtered derivative method */
deriv_filt = (err - pid->err_prev + pid->T_C*pid->deriv_prev)/(pid->T + pid->T_C);
pid->err_prev = err;
pid->deriv_prev = deriv_filt;
/* Summing the 3 terms */
command = pid->Kp*err + pid->integral + pid->Kd*deriv_filt;
/* Remember command at previous step */
pid->command_prev = command;
/* Saturate command */
if (command > pid->max)
{
command_sat = pid->max;
}
else if (command < pid->min)
{
command_sat = pid->min;
}
else
{
command_sat = command;
}
/* Apply rate limiter */
if (command_sat > pid->command_sat_prev + pid->max_rate*pid->T)
{
command_sat = pid->command_sat_prev + pid->max_rate*pid->T;
}
else if (command_sat < pid->command_sat_prev - pid->max_rate*pid->T)
{
command_sat = pid->command_sat_prev - pid->max_rate*pid->T;
}
else
{
/* No action */
}
/* Remember saturated command at previous step */
pid->command_sat_prev = command_sat;
return command_sat;
}
Object Structure
The Object structure holds the physical properties and current state of the object being controlled. This includes its mass, damping coefficient, position, and velocity.
struct Object
{
float m; // Mass of the object
float k; // Damping constant
float F_max; // Max force applied to the object
float F_min; // Min force applied to the object
float T; // Time step
float v; // Velocity of the object
float z; // Position of the object
};
Object_Step Function
Implemented using the Euler forward method, the Object_Step function updates the object’s velocity and position. The previous step’s velocity
is used to calculate the new acceleration
, which is then integrated twice to update
and
.
float Object_Step(struct Object *obj, float F){
/* This function updates the position of an object in 1D based on the applied force F and
* the object's mass, viscous damping coefficient k, max/min forces, and time step T.
*
* Inputs:
* F: the force applied to the object
* obj: a pointer to an object struct containing its properties (mass, damping, etc.)
*
* Returns:
* z: the position of the object in meters
*/
/* Declare variables for the derivative dv/dt and the saturated force command */
float dv_dt;
float F_sat;
/* Apply saturation to the input force */
if (F > obj->F_max)
{
F_sat = obj->F_max;
}
else if (F < obj->F_min)
{
F_sat = obj->F_min;
}
else
{
F_sat = F;
}
/* Calculate the derivative dv/dt using the input force and the object's velocity and properties */
dv_dt = (F_sat - obj->k*obj->v)/obj->m;
/* Update the velocity and position of the object by integrating the derivative using the time step T */
obj->v += dv_dt*obj->T;
obj->z += obj->v*obj->T;
/* Return the updated position of the object */
return obj->z;
}
Simulation – Testing the PID Controller in C
The simulation is managed by the main function, which performs the following steps:
- Create Structures: Initializes structures for two PID controllers and two objects.
- Iterate 1200 Times: Simulates 120 seconds, with each step representing 0.1 seconds.
- Generate Setpoints: Defines setpoints of 100 m and 50 m for the first 60 seconds, then changes to 200 m and 150 m.
- Run PID_Step: Updates the force
using the previous step’s position
. - Run Object_Step: Updates the object’s position and velocity using the computed force
.
Here’s the code for the main function:
int main()
{
// Current simulation time
float t = 0;
// Iteration counter
int i = 0;
// Setpoint and output of the first control loop
float command1 = 0;
float stp1 = 100;
float z1 = 0;
// Setpoint and output of the second control loop
float command2 = 0;
float stp2 = 50;
float z2 = 0;
// PID controller parameters for the first control loop
struct PID pid1 = {1, 0.1, 5, 0.1, 1, TIME_STEP, 100, -100, 40, 0, 0, 0, 0, 0};
// Object parameters for the first control loop
struct Object obj1 = {10, 0.5, 100, -100, TIME_STEP, 0, 0};
// PID controller parameters for the second control loop
struct PID pid2 = {1.8, 0.3, 7, 0.3, 1, TIME_STEP, 100, -100, 40, 0, 0, 0, 0, 0};
// Object parameters for the second control loop
struct Object obj2 = {10, 0.5, 100, -100, TIME_STEP, 0, 0};
// Open a file for logging simulation data
FILE *file = fopen("data_PID_C.txt", "w");
/* Implement iteration using a while loop */
while(i < LENGTH)
{
/* Change setpoint at t = 60 seconds */
if (t < 60)
{
stp1 = 100;
stp2 = 50;
}
else
{
stp1 = 200;
stp2 = 150;
}
// Execute the first control loop
command1 = PID_Step(&pid1, z1, stp1);
z1 = Object_Step(&obj1, command1);
// Execute the second control loop
command2 = PID_Step(&pid2, z2, stp2);
z2 = Object_Step(&obj2, command2);
// Log the current time and control loop values to the file
fprintf(file, "%f %f %f %f %f %f %f\n", t, command1, z1, stp1, command2, z2, stp2);
// Increment the time and iteration counter
t = t + TIME_STEP;
i = i + 1;
}
// Close the file and exit the program
fclose(file);
exit(0);
}
The figure below shows the results of this simulation:

Advantages of Using Structures for a PID Controller in C
Employing structures in C for PID controllers offers several benefits:
- Reusability: Each PID controller instance maintains its own state, allowing multiple controllers to operate independently.
- Flexibility: Easily create and manage different control loops by defining new structures.
- Maintainability: Structured code is easier to read, debug, and extend, making it ideal for complex control systems.
This structured approach ensures that these PID controllers are suitable for various applications in embedded systems.
Practical Applications and Further Reading
PID controllers are ubiquitous in control systems, finding applications in areas such as:
- Automotive Systems: Cruise control, engine management.
- Industrial Automation: Temperature control, robotic arms.
- Consumer Electronics: Drones, cameras.
For those interested in diving deeper into the code, the complete implementation is available on GitHub. This repository provides all the necessary files to understand and experiment with the implementation of the PID controller in C.
Conclusion
Learning how to implement PID controllers in C is a key skill for those involved in implementing control algorithms for embedded systems. By following this guide, you should now have a solid understanding of the architecture, implementation, and simulation of PID controllers in C. Explore the provided code, experiment with different setups, and have fun!
Feel free to ask any questions in the comments, and if you want to dive deeper into the world of PID Controllers here’s my video course:
PID Control – Things I Wish I Knew When I Graduated





I have tried to translate your example C code into a Julia/Pluto notebook. This translation should be just a matter of reformatting for differences in language syntax and indeed I ended up with something that behaves as a PID controller. However, my translation does not replicate your plots while using the same tuning parameters. It’s very likely a silly typo, but I cannot find it.
Could you please confirm that the output plots are generated from the code as listed.
Hi Mark, thanks for your interest in my work.
Yes, the plot is from the code at https://github.com/simorxb/PID-C-Struct, I double check now just in case. Could it be the timestep that is different? Just trying to guess the obvious…
I don’t understand why the `err – pid->err_prev` term in your filtered D component is _not_ multiplied by `pid->T`. I wrote an issue about this on github: https://github.com/simorxb/PID-C-Struct/issues/1
Any help to make me understand would be appreciated.
Hi Gauthier!
I implemented the filtered derivative described in this LinkedIn post:
https://www.linkedin.com/posts/simone-bertoni-control-eng_controlsystems-embeddedsystems-softwareengineering-activity-6991292661135507456-_Pfr?utm_source=share&utm_medium=member_desktop
I hope this clarifies but please let me know if not.