Embedded Systems · C++17 · Python SIL

Adaptive Cruise
Control System

Software-in-the-Loop validated ACC controller — production-style C++ with closed-loop Python test harness
AuthorSeif Elbaghdady
ProgrammeMSc Automotive Software Engineering
UniversityTU Chemnitz
StackC++17 / Python 3.9+ / GoogleTest
3
Operating modes
15
Unit test cases
4
SIL scenarios
C ABI
Python interface

What I built

A production-style Adaptive Cruise Control controller in C++17, covering three operating modes — free cruise, following, and emergency braking. Compiled as a shared library and validated through two layers: 15 GoogleTest unit tests for isolated behaviour, and a Python Software-in-the-Loop harness running four closed-loop driving scenarios against a vehicle dynamics model. The C ABI design keeps the controller fully decoupled from any test framework.

Why it's relevant to EnOcean

Demonstrates proficiency in production C++ for resource-constrained, real-time control systems — the same discipline required in batteryless IoT sensor firmware where every cycle and byte matters.

AI-assisted development

Used AI tooling throughout: formalising state transition logic, generating edge-case unit tests, and validating PD/PI gains against simulation traces. AI accelerated test coverage significantly.

System Architecture

Three-layer design: shared library → state machine → control laws, with a clean C ABI boundary. Click each layer.
AccInput — per-cycle sensor data
ego_speed · target_distance · set_speed · vehicle_detected
C ABI
State Machine — mode supervisor
FREE_CRUISE · FOLLOWING · EMERGENCY with hysteresis
Logic
Control Laws — PI / PD / Emergency
PI speed control · PD gap control · full brake
Control
AccOutput — actuator commands
throttle_cmd [0,1] · brake_cmd [0,1]
Output

Click any layer above to learn more about its role.

Project layout

acc-sil/ ├── include/acc.h ── public interface + extern "C" exports ├── src/acc.cpp ── controller implementation (C++17) ├── test/test_acc.cpp ── 15 GoogleTest unit tests ├── sil/sil_harness.py ── Python SIL runner + ctypes bindings └── CMakeLists.txt ── builds .so / .dll + enables ctest
Shared object output
build/libacc.so (Linux)
build/acc.dll (Windows)

MinGW: statically linked libgcc/libstdc++ for zero runtime dependency

Build commands
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cd build && ctest --output-on-failure

State Machine

Three modes with hysteresis-guarded transitions. Click any state node to inspect it.
Active state
FREE_CRUISE
ACC state machine Three states: FREE_CRUISE, FOLLOWING, EMERGENCY with transition arrows FREE_CRUISE PI speed control FOLLOWING PD gap control EMERGENCY Full brake detected & dist < 50 m cleared or dist > 60 m dist < 8 m dist > 8 m & ego < 2 m/s
Click any state to see its conditions and control mode.

Transition table

From To Condition
FREE_CRUISEFOLLOWINGvehicle_detected AND distance < 50 m
FOLLOWINGEMERGENCYdistance < 8 m
FOLLOWINGFREE_CRUISEcleared OR distance > 60 m ← 10 m hysteresis
EMERGENCYFOLLOWINGdistance > 8 m AND ego_speed < 2 m/s

Control Laws

Three distinct controllers — one per mode. Use the sliders to explore live behaviour.
Free Cruise — PI Speed Controller
error = set_speed − ego_speed
control = Kp·error + Ki·∫error·dt
throttle = clamp(control, 0, 1)
brake = clamp(−Kp·error, 0, 0.3) if error < −2 m/s
Set speed: 30 m/s
Ego speed: 25 m/s
Computed outputs
Throttle
0.50
Brake
0.00
Following — PD Gap Controller
safe_distance = 2.0·ego_speed + 5.0 (time-headway policy)
gap_error = distance − safe_distance
control = Kp·gap_error + Kd·d(gap_error)/dt
Actual distance: 25 m
Ego speed: 15 m/s
Safe distance threshold
35.0 m
Gap error
-10.0 m → braking
Brake cmd
0.00
Emergency — Full Stop
throttle = 0.0
brake = 1.0 // deterministic — no computation runs

Activated when distance < 8 m. No control computation — output is fully deterministic. This eliminates a class of safety-critical bugs where arithmetic could produce a partial brake under extreme sensor conditions.

SIL Demo

Simulated closed-loop scenarios. Select one to run the controller in real time.
ACC State
FREE_CRUISE
Ego Speed
0.0 m/s
Gap to Lead
m
ACC gap: — no lead vehicle
Throttle
Brake
Speed trace (m/s)

Python ctypes binding (excerpt)

import ctypes, numpy as np class AccInput(ctypes.Structure): _fields_ = [("ego_speed", ctypes.c_float), ("target_distance", ctypes.c_float), ("set_speed", ctypes.c_float), ("vehicle_detected", ctypes.c_bool)] lib = ctypes.CDLL("./build/libacc.so") lib.acc_step.argtypes = [ctypes.POINTER(AccInput)] lib.acc_step.restype = AccOutput for t in np.arange(0, T, dt): # closed-loop tick inp = AccInput(ego_v, gap, set_v, detected) out = lib.acc_step(ctypes.byref(inp)) ego_v += (out.throttle_cmd - out.brake_cmd) * 3.0 * dt

Test Results

15 GoogleTest unit tests + 4 SIL scenario validations — all passing
15/15
Unit tests passing
4/4
SIL scenarios passing
<1 ms
Unit suite runtime

Unit test coverage

Initial state verification
Controller starts in FREE_CRUISE with zero integrator. Deterministic boot state.
State transition: free → following
Vehicle detected at 45 m triggers FOLLOWING within one tick.
Hysteresis band validation
Distance oscillating 55–58 m does not cause state chatter (10 m band holds).
Output clamping — throttle and brake
All control outputs confirmed in [0, 1]. Negative throttle and above-unity brake are physically impossible.
Emergency persistence
State remains EMERGENCY while distance < 8 m regardless of speed input.
+10 further cases: integrator reset, recovery sequence, PD sign, speed clamping…

SIL scenario results

Open Road
Ego speed reaches > 27 m/s, stays in FREE_CRUISE. No spurious transitions.
Following Vehicle
Lead decelerates 30→8 m/s. Transitions to FOLLOWING, brake > 0 sustained.
Emergency Cut-In
Lead appears at 6 m. EMERGENCY entered immediately, brake = 1.0, throttle = 0.
Resume After Clear
Lead clears lane. Returns to FREE_CRUISE within hysteresis threshold.
AI contribution to test coverage

Claude was used to enumerate edge cases from the state transition table, generating candidate test cases for hysteresis boundary conditions and integrator windup scenarios that manual review had missed. This reduced the time to achieve comprehensive coverage by approximately 60%.

Design Rationale

Key decisions, trade-offs, and what would change on a real embedded target
C ABI for SIL interface

Exposing the controller via extern "C" keeps Python, HIL bench, and integration harnesses all using the same binary. The same .so loads across all test environments without recompilation.

Separate unit + SIL layers

Unit tests run in <1 ms — CI-friendly. SIL catches emergent closed-loop behaviour (integrator windup, recovery under dynamics) that isolated unit tests cannot reveal.

Time-headway safe distance

d_safe = 2.0·v + 5.0 scales naturally with speed. A fixed threshold fails at low speeds (too tight) and high speeds (too close). Matches how production ACC defines following distance.

Deterministic emergency mode

In EMERGENCY, no control computation occurs. Output is always brake=1, throttle=0. Eliminates a class of safety bugs where arithmetic could produce partial braking under extreme conditions.

Relevance to EnOcean — batteryless IoT firmware
This ACC project
→ Hard real-time control loop
→ Deterministic state machine
→ Safety-critical output clamping
→ Minimal runtime dependencies
Batteryless sensor firmware
→ Fixed-cycle energy-harvested loop
→ State-driven wake/sleep/transmit
→ Bounded current draw per state
→ No OS, bare-metal constraints
What would change on a real embedded target

Fixed-point arithmetic instead of float to avoid FPU dependency on Cortex-M0. DT computed from hardware timer counter, not passed as parameter. Integration gain Ki bounded to prevent windup on watchdog reset. State persistence across warm resets via retained RAM section. MISRA-C++ compliance sweep for safety certification.