Course: ELE709 — Real-Time and Embedded Systems, Toronto Metropolitan University
A multithreaded real-time PID controller implemented in C targeting a DC motor position control system. Built on a dedicated real-time hardware platform (dlab), the controller runs a dedicated POSIX control thread synchronized via semaphores, implements a filtered-derivative PID with anti-windup, and was validated under artificial CPU load to characterize timing robustness.
- Full PID controller with filtered derivative (backward-difference + first-order IIR) and integral anti-windup
- Dedicated real-time control thread driven by hardware timer interrupts via POSIX semaphore synchronization
- Interactive runtime menu for tuning Kp, Ti, Td, N, sampling frequency, and reference input without recompiling
- Step and square-wave reference input generation with configurable magnitude, frequency, and duty cycle
- System load characterization suite: sequential vs. parallel thread timing benchmarks and parallel matrix multiplication
- Artificial CPU load generator to test PID robustness under stressed system conditions
The controller is built around a producer-consumer threading model:
- The hardware timer ISR (inside
dlab_def.h) signalsdata_availat each sample period - The ControlThread blocks on
sem_wait(&data_avail), reads the encoder, computes the PID output, and writes to the D/A converter — all within one sample period - The main thread handles the user menu and is entirely decoupled from the control loop
This structure ensures the control loop timing is driven by hardware, not by OS scheduling of the main thread.
Hardware Timer ISR
|
| sem_post(&data_avail)
v
ControlThread <--- sem_wait (blocks between samples)
ReadEncoder()
PID Compute
DtoA(VtoD(uk))
|
Main Thread (menu, parameter updates)
The PID algorithm uses:
- Integral: forward-Euler accumulation with gain
1/Ti - Derivative: backward-difference filtered by a first-order IIR with coefficient
Nto suppress high-frequency noise - Control law:
uk = Kp * (ek + integral + Td * derivative)
| File | Description |
|---|---|
src/pid.c |
Full PID controller with filtered derivative and anti-windup. Interactive menu. Hardware I/O via dlab. |
src/pc.c |
Proportional-only controller. Baseline for comparing P vs PID response. |
src/load.c |
CPU load generator: 3 threads continuously allocating memory and computing square roots. Used to stress the system during PID runs. |
src/lab32.c |
Sequential thread timing benchmark: measures per-operation execution time (+, -, *, /) with 500M iterations each, threads run one at a time. |
src/lab33.c |
Parallel thread timing benchmark: same operations launched simultaneously to measure parallelism effects on execution time. |
src/lab35.c |
Parallel matrix multiplication (18x16 * 16x18): spawns one POSIX thread per output element (324 threads), each computing one C[i][j]. |
docs/Anti PID with Load.pdf |
Lab writeup: PID tuning results, step response plots, and analysis of controller performance under CPU load. |
docs/AntiPID.docx |
Supporting writeup for anti-windup design and implementation. |
Platform requirement:
pid.candpc.crequire the dlab real-time hardware board and itsdlab_def.hdriver library.load.c,lab32.c,lab33.c, andlab35.crun on any POSIX system.
Build and run the PID controller (on dlab hardware):
gcc -o pid src/pid.c -lpthread -lm -ldlab
./pidBuild and run the load generator (in a separate terminal):
gcc -o load src/load.c -lpthread -lm
./loadBuild and run the timing benchmarks:
gcc -o lab32 src/lab32.c -lpthread -lm && ./lab32
gcc -o lab33 src/lab33.c -lpthread -lm && ./lab33
gcc -o lab35 src/lab35.c -lpthread -lm && ./lab35PID runtime menu options:
| Key | Action |
|---|---|
r |
Run control loop |
p |
Set proportional gain Kp |
i |
Set integral time constant Ti |
d |
Set derivative time constant Td |
n |
Set derivative filter coefficient N |
f |
Set sampling frequency Fs (Hz) |
t |
Set total run time (s) |
u |
Set reference input (step or square wave) |
g |
Plot position response on screen |
h |
Save plot as PostScript |
q |
Exit |
Semaphore-driven control loop. Rather than using a sleep()-based timer, the control thread blocks on a semaphore that is posted by the hardware timer ISR. This eliminates OS sleep jitter and ties the loop period precisely to the hardware clock.
Filtered derivative. A pure backward-difference derivative amplifies measurement noise. The filter derivative = (Td * raw_deriv + prev_deriv) / (1 + Td / (N * dt)) is a first-order IIR that rolls off at frequency N * Fs / (2 * pi), making the derivative term practical for real motor encoder signals.
Separated P and PID implementations. pc.c (P-only) and pid.c (full PID) are kept as distinct files rather than a single configurable program. This preserves clear experimental baselines matching the lab structure — P response is characterized first, then full PID tuning builds on that.
Load generator as a separate process. load.c is intentionally a standalone binary rather than threads inside the controller. Running it as a separate process creates realistic OS-level scheduling pressure and CPU contention, which better reflects real embedded deployment conditions than in-process load threads would.
Per-element threading in lab35. Spawning one thread per matrix output element (324 threads for an 18x18 result) is pedagogically motivated — it demonstrates thread data partitioning — rather than being an optimal strategy. In practice, thread-pool or BLAS approaches would be used for matrix operations.
Languages: C
Concurrency: POSIX threads (pthreads), POSIX semaphores, producer-consumer synchronization
Control theory: PID control, anti-windup, filtered derivative, discrete-time integration
Real-time systems: Hardware timer-driven control loops, execution time benchmarking with clock_gettime
Platform: dlab real-time hardware board, POSIX (macOS/Linux)