Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ cooling_tests

# Editor folders
.vscode/

# MATLAB analysis outputs (regenerated locally)
results/
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ test: $(TST_BIN)

clean:
> rm -f $(SIM_BIN) $(TST_BIN) *.exe *.o *.obj *.gcda *.gcno *.gcov

# Export the deterministic C simulation as data for MATLAB.
.PHONY: trace
trace: $(SIM_BIN)
> mkdir -p results
> ./$(SIM_BIN) --csv results/cooling_trace.csv
34 changes: 30 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,36 @@ A new invalid reading during recovery restarts the counter. The HIGH fallback is

The simulation prints the step, heating rate, measured temperature, sensor status, fan state, PWM command, and fault indicator. The thermal model is intentionally simple; it is not a physically calibrated engine model.

## MATLAB-based validation (R2024a)

The C simulation can export a 20-step trace for independent checking and
visualization in MATLAB. The C controller is unchanged; MATLAB serves as an
offline reference model, not as an ECU runtime component.

1. From **Git Bash** at the repository root, run `make trace`. This generates
`results/cooling_trace.csv` (ignored by Git).
2. In **MATLAB**, change to the repository folder and run:

```matlab
addpath('matlab')
analyze_cooling
```

The MATLAB script reads the CSV, calculates a separate FSM/fault-recovery
reference, checks every recorded state and output, and saves a four-panel
figure to `results/cooling_analysis.png`. MATLAB R2024a is supported; no
Simulink or other add-on is required for this workflow.

The reference model checks the supplied 20-step test scenario, not all
possible input sequences. This is **offline cross-checking**, not real-time
co-simulation, hardware-in-the-loop testing, or functional-safety evidence.

### MATLAB validation results

The following plots show temperature evolution, injected sensor faults, PWM commands, and fault recovery.

![Engine Cooling Controller - MATLAB Validation](docs/cooling_analysis.png)

## Project structure

```text
Expand Down Expand Up @@ -165,7 +195,3 @@ This is a **PC-based educational simulation**, not a production-ready ECU implem
- Calls to `cooling_update()` are simulated steps, not a real-time scheduler with timing guarantees.
- No CAN communication, diagnostics protocol, or physical fan-failure monitoring is implemented.
- The project does not implement or claim AUTOSAR or ISO 26262 compliance.

## License

MIT — see [LICENSE](LICENSE).
Binary file added docs/cooling_analysis.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
139 changes: 139 additions & 0 deletions matlab/analyze_cooling.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
function analyze_cooling(csvPath)
%ANALYZE_COOLING Plot and independently check Engine Cooling Controller output.
% Compatible with MATLAB R2024a; no optional toolboxes required.
% Run from the repository root:
% addpath('matlab'); analyze_cooling

root = fileparts(fileparts(mfilename('fullpath')));
if nargin < 1
csvPath = fullfile(root, 'results', 'cooling_trace.csv');
end
assert(isfile(csvPath), ...
'CSV not found. In Git Bash, run: make trace');
data = readtable(csvPath);

required = {'step','heat_rate_c_per_step','plant_temp_c', ...
'measured_temp_c','signal_valid','fan_state', ...
'pwm_pct','sensor_fault','fault_led','valid_streak'};
assert(all(ismember(required, data.Properties.VariableNames)), ...
'CSV is missing one or more expected columns.');
assert(height(data) == 20 && isequal(data.step, (1:20)'), ...
'Expected 20 consecutive simulation steps.');

% Build the expected result separately from the C implementation.
[refState, refFault, refStreak] = reference_controller( ...
data.measured_temp_c, data.signal_valid);
assert(isequal(data.fan_state, refState), ...
'C state sequence differs from the MATLAB reference model.');
assert(isequal(data.sensor_fault, refFault), ...
'C sensor fault flags differ from the MATLAB reference model.');
assert(isequal(data.valid_streak, refStreak), ...
'C recovery counters differ from the MATLAB reference model.');
assert(all(data.pwm_pct == 50 .* data.fan_state), ...
'PWM does not match OFF/LOW/HIGH mapping.');
assert(isequal(data.fault_led, data.sensor_fault), ...
'Fault LED must match sensor_fault.');
assert(any(data.signal_valid == 0) && ...
any(data.measured_temp_c < -40 | data.measured_temp_c > 150), ...
'Both signal and range fault injections must be exercised.');
assert(all(data.fan_state(data.sensor_fault == 1) == 2), ...
'Sensor fault must command HIGH cooling.');

fprintf('[PASS] 20 C states match independent MATLAB FSM\n');
fprintf('[PASS] Sensor fault flags and three-sample recovery match\n');
fprintf('[PASS] PWM stages and fault LED agree with controller outputs\n');
fprintf('[PASS] Both fault injection scenarios are present\n');

fig = figure('Name','Engine Cooling Controller Validation', ...
'Color','w','Position',[100 80 1150 900]);
layout = tiledlayout(fig,4,1,'TileSpacing','compact','Padding','compact');
title(layout,'Engine Cooling Controller | C trace validated by MATLAB');

nexttile;
plot(data.step,data.plant_temp_c,'-o','LineWidth',1.5,'MarkerSize',4);
hold on;
yline(95,':','LOW OFF 95 C');
yline(100,'--','LOW ON 100 C');
yline(105,':','HIGH OFF 105 C');
yline(110,'--','HIGH ON 110 C');
ylabel('Plant temp (C)'); grid on;
title('Actual simulated plant temperature (not the injected sensor reading)');

nexttile;
plot(data.step,data.measured_temp_c,'-o','LineWidth',1.4,'MarkerSize',4);
hold on;
bad = data.signal_valid == 0 | ...
data.measured_temp_c < -40 | data.measured_temp_c > 150;
scatter(data.step(bad),data.measured_temp_c(bad),70,'r','filled');
ylabel('Sensor (C)'); grid on;
title('Measurement, including injected faults at steps 12 and 13');

nexttile;
stairs(data.step,data.pwm_pct,'LineWidth',2);
ylim([-10 110]); yticks([0 50 100]);
ylabel('PWM (%)'); grid on;
title('Simulated PWM command (0 / 50 / 100 percent)');

nexttile;
stairs(data.step,data.sensor_fault,'LineWidth',2);
ylim([-0.15 1.15]); yticks([0 1]);
ylabel('Fault'); xlabel('Simulation step'); grid on;
title('Sensor fault flag and recovery');

output = fullfile(root,'results','cooling_analysis.png');
exportgraphics(layout, output, 'Resolution', 160);
fprintf('[PASS] Plot saved: %s\n', output);
end

function [states, faults, streaks] = reference_controller(temps, signalValid)
% Independent reference behavior based on the documented requirements.
n = numel(temps);
states = zeros(n,1); faults = zeros(n,1); streaks = zeros(n,1);
current = 0; fault = false; streak = 0;

for k = 1:n
temp = temps(k);
good = signalValid(k) ~= 0 && temp >= -40 && temp <= 150;
if ~good
current = 2; fault = true; streak = 0;
else
if fault
streak = streak + 1;
if streak < 3
current = 2;
states(k) = current;
faults(k) = 1;
streaks(k) = streak;
continue;
end
fault = false;
streak = 0;
end
switch current
case 0 % OFF
if temp >= 110
current = 2;
elseif temp >= 100
current = 1;
end
case 1 % LOW
if temp >= 110
current = 2;
elseif temp <= 95
current = 0;
end
case 2 % HIGH
if temp <= 95
current = 0;
elseif temp <= 105
current = 1;
end
otherwise
error('Unexpected reference state');
end
end
states(k) = current;
faults(k) = double(fault);
streaks(k) = streak;
end
end
Loading