A model-based design of a diesel turbocharger boost-pressure (air-path) controller — a discrete PI loop with feed-forward and back-calculation anti-windup, a speed×load setpoint map, speed-dependent turbo authority (so low-rev turbo lag is real), and a Stateflow safety supervisor (INIT→OFF→CRANK→RUN→FAULT). Built the full MBD way: requirements → Simulink/Stateflow model → generated C → MIL/SIL with structural coverage. The cluster below is a live, in-browser simulation of the same control law — start it, pick a drive mode, rev it in Auto or tune it in Manual, then hit a hill or break the sensor.
New here? Turn the red key to START, pick a drive mode, then (in Auto) push the throttle. Watch the controller hold boost on target as the revs and speed climb — then hit a hill or break the sensor.
What you're watching: the teal line is the boost the engine is actually getting — it should chase the dashed target. Amber is how hard the controller works the turbo valve. A red line means the safety supervisor caught a bad sensor and dropped to limp mode.
A browser simulation of the discrete control law (PI + feed-forward + back-calculation anti-windup) against a first-order air-path plant — the same algorithm and parameters as the Simulink model in the repo. Not the Simulink artifact itself.
The whole control loop on one page — what each block does, and the maths behind it. The same law runs in the cluster above.
Read it left-to-right: the Driver sets a boost Target (from a speed × load map); the Controller compares target to the measured boost and works the turbo valve (u_act). The turbo's authority grows with engine speed, so full boost only arrives once it spools — that's turbo lag. Boost feeds back at 100 Hz; a hill shows up as a load disturbance the loop rejects, and if the Sensor reads out-of-range or jumps implausibly the Supervisor latches a safe FAULT limp. The teal blocks are the controller software; the grey blocks are the driver and the physical plant.
τ·ṗ = −(p − p_amb) + K_eff·u + dPlant (air-path): a first-order lag — boost climbs toward K_eff·u above ambient; d is load (e.g. a hill). Solved at Ts = 10 ms.
K_eff = K_act·(a_min + (1−a_min)·min(rpm/n_spool, 1))Turbo authority: the turbo gets stronger with revs. Below spool-up (n_spool) it can't make full boost — modelled turbo lag.
u_ff = (p_ref − p_amb) / K_actFeed-forward: an open-loop guess of the valve position for the target, so the loop starts close and PI only trims the remainder.
u = sat(u_ff + Kp·e + I), e = p_ref − p_meas
I += Ts·(Ki·e + Kaw·(u − u_unsat))PI + anti-windup: PI removes steady-state error; back-calculation (Kaw) bleeds the integrator whenever the valve saturates, so it never winds up.
p_ref = clamp( p_min + (p_max(mode)−p_min)·load·(0.45 + 0.55·speedN) )Setpoint map: throttle (load) and revs set the target; the drive mode picks the ceiling p_max. Speed gates how much boost is actually requested.
Three layers. The HMI (grey, top) is the hand-built browser cockpit — knobs, throttle, dials. The controller (teal, inside the dashed boundary) is the part that becomes generated C via Embedded Coder, running at a fixed 100 Hz: the Stateflow supervisor, the setpoint map, the PI controller, and the sensor-rationality monitor. The plant / vehicle (grey, bottom) — air-path lag with speed-dependent turbo authority plus the engine- and vehicle-speed models — is a MIL simulation stand-in, not deployed code. The interface across the boundary is small and explicit: key_on · mode · throttle · p_meas in, u_act · mode · fault_code out.
The Verification column lists the planned MIL/SIL methods (run on a personal MATLAB). The control law itself is re-proven headlessly today by verify_control_law.js — independent of Simulink — and exercised live in the cluster above.
Latest headless run · verify_control_law.js — REQ-BP-001/002/004/005 all pass.
Interface · 100 Hz (Ts = 10 ms)
| Signal | Dir | Type · Range |
|---|---|---|
p_meas | in | single, kPa · 90 … 350 |
engine_speed | in | single, rpm · 0 … 6000 |
torque_demand | in | single, % · 0 … 100 |
key_on | in | boolean · 0 / 1 |
drive_mode | in | enum · ECO / SPORT / RACE |
u_act | out | single, — · 0 … 1 (VGT / wastegate) |
mode | out | uint8 · 0 … 4 (INIT…FAULT) |
fault_code | out | uint8 · 0 = OK, else bitfield |
| ID | Requirement | Verification |
|---|---|---|
| REQ-BP-001 Functional | In RUN, regulate measured boost to setpoint with steady-state error ≤ 2 kPa for any constant target in range. | MIL · tc_step |
| REQ-BP-002 Performance | Step response settles (±2 kPa) ≤ 1.0 s with overshoot ≤ 10 %. | MIL · tc_step |
| REQ-BP-003 Safety | On a debounced sensor fault, enter FAULT and command the safe actuator value within 100 ms. | MIL · tc_fault |
| REQ-BP-004 Robustness | Back-calculation anti-windup prevents integrator wind-up during actuator saturation. | MIL · tc_windup |
| REQ-BP-005 Interface | Conform to the interface above; outputs stay within declared ranges (u_act ∈ [0,1], mode ∈ [0,4]). | Analysis + all MIL tests |
| REQ-BP-006 Functional | Boost target is a monotonic engine-speed × demand map, clamped to [pref_min, pref_max(mode)]. | Analysis · tc_map |
| REQ-BP-007 Robustness | When commanded boost exceeds what the turbo can deliver at the current speed (turbo lag), the actuator saturates without instability or wind-up. | MIL · tc_lag |
| REQ-BP-008 Functional · HMI | Drive mode (ECO/SPORT/RACE) scales the boost ceiling, rev-limit and top speed; target and limits stay within the active mode's bounds. | Analysis · tc_modes |
| REQ-BP-009 Supervisor | On power-up begin in INIT and transition to OFF once initialisation completes. | MIL · tc_modes |
| REQ-BP-010 Supervisor | Closed loop is active only in RUN; OFF, CRANK and FAULT drive their defined open-loop actuator values. | MIL · tc_modes |
| REQ-BP-011 Supervisor | RUN → FAULT is latched; recovery to RUN requires debounced sensor_valid and key_on. | MIL · tc_fault |
The deployed controller and its setup, in MISRA-aligned C — the same algorithm running in the cluster above. Embedded Coder produces this from the Simulink model in the repo; shown here in equivalent hand form.
/* boost_params.h — one parameter set (mirrors matlab/params.m). * Controller executes at Ts = 0.01 s (100 Hz). */ #define TS 0.01F /* sample time [s] */ /* plant — MIL simulation stand-in, NOT code-generated */ #define TAU 0.30F /* air-path time constant [s] */ #define K_ACT 180.0F /* actuator effectiveness [kPa/unit] */ #define P_AMB 100.0F /* ambient / baseline pressure [kPa] */ /* PI controller + back-calculation anti-windup */ #define KP 0.010F /* proportional gain [1/kPa] */ #define KI 0.050F /* integral gain [1/(kPa*s)] */ #define KAW 2.0F /* anti-windup gain */ #define DU_MAX 0.05F /* max actuator step / sample */ #define U_MIN 0.0F #define U_MAX 1.0F /* open-loop actuator value per mode */ #define U_OFF 0.0F #define U_CRANK 0.3F #define U_SAFE 0.3F /* FAULT limp */ #define N_CRANK 600.0F /* rpm to permit RUN */ /* sensor rationality */ #define P_MIN 90.0F #define P_MAX 350.0F #define DP_MAX 60.0F /* max plausible step [kPa] */ #define T_DEBOUNCE 0.05F /* fault/recovery debounce [s] */ /* setpoint-map bounds (p_max set by drive mode) */ #define PREF_MIN 110.0F #define PREF_MAX 275.0F /* RACE ceiling; <= P_AMB + K_ACT */ #define N_REF_MAX 4000.0F /* map speed normaliser [rpm] */ /* turbo authority (lag): * K_eff = K_ACT*(A_MIN + (1-A_MIN)*min(n/N_SPOOL,1)) */ #define N_SPOOL 2200.0F #define A_MIN 0.35F /* drive-mode presets (HMI): pref_max / rev-limit / top speed * ECO : 200 kPa | 4000 rpm | 150 km/h * SPORT : 250 kPa | 5000 rpm | 205 km/h * RACE : 275 kPa | 6000 rpm | 255 km/h */
/* boost_controller_step — discrete PI + feed-forward + back-calc anti-windup. * Executed at Ts = 0.01 s. Closed loop active only in RUN mode. */ void boost_controller_step(const BoostCtrlIn *const in, BoostCtrlOut *const out) { static real32_T integ = 0.0F; /* integrator state */ const real32_T u_ff = (in->p_ref - P_AMB) / K_ACT; /* sensor rationality: range + rate, result drives the supervisor */ boolean_T range_bad = (in->p_meas < P_MIN) || (in->p_meas > P_MAX); boolean_T rate_bad = fabsf(in->p_meas - in->p_meas_prev) > DP_MAX; out->fault_code = (uint8_T)range_bad | ((uint8_T)rate_bad << 1); if (in->mode == MODE_RUN) { real32_T e = in->p_ref - in->p_meas; real32_T u_unsat = u_ff + (KP * e) + integ; /* rate limit around the feed-forward operating point */ real32_T du = u_unsat - u_ff; du = (du > DU_MAX) ? DU_MAX : ((du < -DU_MAX) ? -DU_MAX : du); real32_T u_rl = u_ff + du; /* saturate to actuator limits */ out->u_act = (u_rl > U_MAX) ? U_MAX : ((u_rl < U_MIN) ? U_MIN : u_rl); /* back-calculation anti-windup (REQ-BP-004) */ integ += TS * ((KI * e) + (KAW * (out->u_act - u_unsat))); } else { out->u_act = u_ff; /* open-loop modes; supervisor sets actual command */ integ = 0.0F; /* seed for bumpless transfer into RUN */ } }
/* boost_supervisor_step — INIT->OFF->CRANK->RUN->FAULT (Stateflow). * Closed loop runs only in RUN (REQ-BP-010); Ts = 0.01 s. */ void boost_supervisor_step(const SupIn *const in, SupState *const s) { /* debounce sensor health before latching (REQ-BP-003) */ if (in->sensor_bad) { s->db_fault += TS; s->db_valid = 0.0F; } else { s->db_valid += TS; s->db_fault = 0.0F; } boolean_T s_fault = (s->db_fault >= T_DEBOUNCE); boolean_T s_valid = (s->db_valid >= T_DEBOUNCE); switch (s->mode) { case MODE_INIT: s->integ_reset = true; s->mode = MODE_OFF; /* REQ-BP-009 */ break; case MODE_OFF: if (in->key_on && in->rpm > 0.0F) s->mode = MODE_CRANK; break; case MODE_CRANK: if (!in->key_on || in->rpm <= 0.0F) s->mode = MODE_OFF; else if (in->rpm >= N_CRANK && !s_fault) { s->mode = MODE_RUN; s->integ_reset = true; /* bumpless */ } break; case MODE_RUN: if (!in->key_on || in->rpm <= 0.0F) s->mode = MODE_OFF; else if (s_fault) s->mode = MODE_FAULT; /* latched */ break; case MODE_FAULT: if (!in->key_on || in->rpm <= 0.0F) s->mode = MODE_OFF; else if (s_valid) { /* REQ-BP-011 */ s->mode = MODE_RUN; s->integ_reset = true; } break; default: s->mode = MODE_INIT; break; } }
/* boost_controller_types.h — I/O contract (REQ-BP-005). */ typedef enum { MODE_INIT = 0, MODE_OFF, MODE_CRANK, MODE_RUN, MODE_FAULT } BoostMode; /* 0 … 4 */ typedef enum { DRIVE_ECO = 0, DRIVE_SPORT, DRIVE_RACE } DriveMode; typedef struct { real32_T p_meas; /* measured boost [kPa] 90 … 350 */ real32_T p_meas_prev; /* previous sample (rate check) */ real32_T p_ref; /* target from setpoint map */ real32_T engine_speed; /* [rpm] 0 … 6000 */ real32_T torque_demand; /* [%] 0 … 100 */ boolean_T key_on; /* ignition / run request */ DriveMode drive_mode; /* ECO / SPORT / RACE */ BoostMode mode; /* supervisor state (gates loop) */ } BoostCtrlIn; typedef struct { real32_T u_act; /* actuator cmd 0 … 1 (VGT/wastegate) */ BoostMode mode; /* current supervisor mode */ uint8_T fault_code; /* 0 = OK; bit0 range, bit1 rate */ } BoostCtrlOut;
The Simulink model and Stateflow supervisor are constructed via the Simulink API in build_model.m — reviewable and diffable in git, not an opaque binary. Requirements, design, and test plan are in the repo and track the same control law, supervisor, drive modes, and turbo-authority model shown above.
The control law is exercised live above and checked against requirement thresholds in test/test_boost_control.m and verify_control_law.js (reference checks, independent of Simulink).
The rendered .slx diagram — produced by running build_model.m on a personal MATLAB (Home / trial / MATLAB Online). Screenshot will be embedded here.
Embedded Coder output (gen_code.m) and the decision/condition/MCDC coverage report (collect_coverage.m), targeting 100 % structural coverage across the test suite.