Update
This commit is contained in:
@@ -63,7 +63,7 @@ std::optional<ScenarioKind> choose_scenario(sf::RenderWindow& window, const sf::
|
||||
const std::array<const char*, 4> descriptions{
|
||||
"Four-stroke twin, motorcycle test and dyno",
|
||||
"125 cc single, expansion chamber, motorcycle test and dyno",
|
||||
"2.0 litre inline-four, no turbo yet, five-speed wagon and dyno",
|
||||
"2.0 litre inline-four, GT17 turbo, ECU boost control and five-speed wagon",
|
||||
"Coupled turbine/compressor, shaft inertia and pressure diagnostics"};
|
||||
int selected = 0;
|
||||
while (window.isOpen()) {
|
||||
@@ -330,7 +330,7 @@ void draw_dashboard(sf::RenderWindow& window, const sf::Font* font, const Scenar
|
||||
if (state.turbo_enabled) {
|
||||
const auto& t = state.turbo;
|
||||
std::ostringstream text;
|
||||
text << std::fixed << std::setprecision(0) << "VZ21-size: " << t.rpm << " rpm "
|
||||
text << std::fixed << std::setprecision(0) << (state.cylinder_count==4 ? "GT17-size: " : "VZ21-size: ") << t.rpm << " rpm "
|
||||
<< std::setprecision(2) << "CPR " << t.compressor_pressure_ratio
|
||||
<< " Charge " << t.charge_pressure/1000 << " kPa / " << std::setprecision(0) << t.charge_temperature << " K"
|
||||
<< "\nTurbine inlet " << t.turbine_inlet_pressure/1000 << " kPa Outlet " << t.turbine_outlet_pressure/1000 << " kPa"
|
||||
@@ -416,9 +416,15 @@ void draw_twin_scene(sf::RenderWindow& window, const sf::Font* font, const Scena
|
||||
|
||||
void draw_b205_scene(sf::RenderWindow& window, const sf::Font* font, const ScenarioSnapshot& s) {
|
||||
window.clear({9, 12, 17});
|
||||
draw_label(window, font, "SAAB B205 | 1985 cc inline-four | firing 1-3-4-2 | no turbo yet",
|
||||
draw_label(window, font, s.turbo_enabled ? "SAAB B205 | GT17 turbo | ECU wastegate control" : "SAAB B205 | naturally aspirated baseline",
|
||||
{260, 181}, sf::Color::White, 16);
|
||||
draw_pipe(window, s, 0, 215, 25, 110);
|
||||
if (s.turbo_enabled) {
|
||||
draw_pipe(window, s, 10, 320, 25, 70);
|
||||
draw_volume(window, float(s.turbo.charge_pressure), {{75, 300}, {35, 38}});
|
||||
draw_graph_line(window, {90, 300}, {25, 215}, {80,145,200});
|
||||
draw_label(window, font, "COMP", {75, 345}, sf::Color::White, 11);
|
||||
}
|
||||
draw_volume(window, s.intake_pressure, {{120, 195}, {65, 65}});
|
||||
draw_label(window, font, "PLENUM", {120, 270}, sf::Color::White, 12);
|
||||
draw_graph_line(window, {190, 211}, {820, 211}, {80, 145, 200});
|
||||
@@ -436,8 +442,15 @@ void draw_b205_scene(sf::RenderWindow& window, const sf::Font* font, const Scena
|
||||
{x, 445}, {180,190,205}, 12);
|
||||
}
|
||||
draw_volume(window, s.exhaust_pressure, {{850, 393}, {65, 48}});
|
||||
draw_pipe(window, s, 9, 420, 920, 1240);
|
||||
draw_label(window, font, "COLLECTOR / OPEN EXHAUST", {930, 390}, sf::Color::White, 12);
|
||||
draw_pipe(window, s, 9, 420, 920, s.turbo_enabled ? 990 : 1240);
|
||||
if (s.turbo_enabled) {
|
||||
draw_volume(window, float(s.turbo.turbine_inlet_pressure), {{995, 397}, {55, 45}});
|
||||
draw_pipe(window, s, 11, 420, 1055, 1240);
|
||||
draw_label(window, font, "GT17", {1000, 450}, sf::Color::White, 12);
|
||||
draw_label(window, font, "Boost target " + std::to_string(int(s.boost_target/1000)) + " kPa gauge | WG duty " + std::to_string(int(100*s.wastegate_duty)) + "%",
|
||||
{25, 515}, sf::Color::White, 14);
|
||||
}
|
||||
draw_label(window, font, s.turbo_enabled ? "COLLECTOR / GT17 / EXHAUST" : "COLLECTOR / OPEN EXHAUST", {930, 375}, sf::Color::White, 12);
|
||||
draw_label(window, font, "9-5 wagon baseline: 1675 kg with driver, five-speed manual, 4.05 final drive",
|
||||
{25, 550}, sf::Color::White, 14);
|
||||
draw_label(window, font, "Engine dimensions/firing from Saab WIS. Manifolds, cams, losses and ECU are estimates.",
|
||||
@@ -465,6 +478,7 @@ int main(int argc, char** argv) {
|
||||
ScenarioKind selected_kind=ScenarioKind::Twin;
|
||||
bool scenario_from_args=false;
|
||||
bool turbo_from_args=false;
|
||||
std::string turbo_name;
|
||||
TwoStrokeConfig two_stroke_config;
|
||||
OutdoorScene selected_scene=OutdoorScene::Wall;
|
||||
for(int i=1;i<argc;++i){
|
||||
@@ -477,8 +491,9 @@ int main(int argc, char** argv) {
|
||||
(value=="two-stroke" ? ScenarioKind::TwoStroke : ScenarioKind::Twin);
|
||||
scenario_from_args=true;
|
||||
}else if(option=="--turbo"){
|
||||
if(value!="none"&&value!="vz21"){std::cerr<<"Choose none or vz21 turbo\n";return 2;}
|
||||
turbo_from_args=value=="vz21";
|
||||
if(value!="none"&&value!="vz21"&&value!="gt17"){std::cerr<<"Choose none, vz21 or gt17 turbo\n";return 2;}
|
||||
turbo_name=value;
|
||||
turbo_from_args=value!="none";
|
||||
}else if(option=="--exhaust"){
|
||||
if(value=="racing") two_stroke_config.exhaust=TwoStrokeExhaust::Racing;
|
||||
else if(value=="straight") two_stroke_config.exhaust=TwoStrokeExhaust::Straight;
|
||||
@@ -490,7 +505,7 @@ int main(int argc, char** argv) {
|
||||
else if(value=="woodland")selected_scene=OutdoorScene::Woodland;
|
||||
else {std::cerr<<"Choose dry, field, wall or woodland\n";return 2;}
|
||||
}
|
||||
}else{std::cerr<<"Usage: fluidsim_desktop [--scenario twin|two-stroke|b205] [--scene dry|field|wall|woodland] [--exhaust racing|straight] [--turbo none|vz21]\n";return 2;}
|
||||
}else{std::cerr<<"Usage: fluidsim_desktop [--scenario twin|two-stroke|b205] [--scene dry|field|wall|woodland] [--exhaust racing|straight] [--turbo none|vz21|gt17]\n";return 2;}
|
||||
}
|
||||
const sf::Vector2u initial_size = scenario_from_args ? sf::Vector2u{window_width, window_height}
|
||||
: sf::Vector2u{720, 606};
|
||||
@@ -519,14 +534,17 @@ int main(int argc, char** argv) {
|
||||
window.setView(sf::View(sf::FloatRect({0, 0}, {float(window_width), float(window_height)})));
|
||||
}
|
||||
if(turbo_from_args) {
|
||||
if(selected_kind!=ScenarioKind::Twin && selected_kind!=ScenarioKind::TurboTwin) {
|
||||
std::cerr<<"The VZ21 turbo test uses the twin scenario\n";return 2;
|
||||
if(turbo_name=="gt17" && selected_kind==ScenarioKind::SaabB205) {
|
||||
// Saab preset selects its GT17 calibration.
|
||||
} else if(turbo_name=="vz21" && (selected_kind==ScenarioKind::Twin || selected_kind==ScenarioKind::TurboTwin)) {
|
||||
selected_kind=ScenarioKind::TurboTwin;
|
||||
} else {
|
||||
std::cerr<<"Use gt17 with b205 or vz21 with twin\n";return 2;
|
||||
}
|
||||
selected_kind=ScenarioKind::TurboTwin;
|
||||
}
|
||||
window.setTitle(selected_kind == ScenarioKind::TurboTwin ? "FluidSim - twin + VZ21 turbo" : selected_kind == ScenarioKind::SaabB205 ? "FluidSim - Saab B205 / 9-5 wagon" :
|
||||
(selected_kind == ScenarioKind::TwoStroke ? "FluidSim - YZ125 two-stroke" : "FluidSim - parallel twin"));
|
||||
DesktopScenario scenario(selected_kind, two_stroke_config);
|
||||
DesktopScenario scenario(selected_kind, two_stroke_config, turbo_name!="none");
|
||||
scenario.set_audio_scene(selected_scene);
|
||||
SimulationRingBuffer ring(ring_capacity);
|
||||
for (std::size_t i = 0; i < preroll_samples; ++i) {
|
||||
|
||||
21
src/ecu.cpp
21
src/ecu.cpp
@@ -3,6 +3,27 @@
|
||||
#include <cmath>
|
||||
|
||||
namespace fluidsim::port {
|
||||
void Ecu::control_boost(float dt, float pedal, float rpm, float manifold, float charge, float ambient) {
|
||||
boost_target_ = boost_duty_ = 0;
|
||||
if (!boost_control_enabled) { boost_integral_ = 0; boost_cut_ = false; return; }
|
||||
if (!std::isfinite(dt) || dt <= 0 || !std::isfinite(pedal) || !std::isfinite(rpm) ||
|
||||
!std::isfinite(manifold) || !std::isfinite(charge) || !std::isfinite(ambient) ||
|
||||
manifold <= 0 || charge <= 0 || ambient <= 0) {
|
||||
boost_integral_ = 0; boost_cut_ = true; return;
|
||||
}
|
||||
const float boost = manifold-ambient;
|
||||
filtered_boost_ += (boost-filtered_boost_)*(-std::expm1(-dt/.05f));
|
||||
if (std::max(manifold, charge)-ambient > maximum_boost+30000) boost_cut_ = true;
|
||||
else if (std::max(manifold, charge)-ambient < maximum_boost+10000) boost_cut_ = false;
|
||||
boost_target_ = maximum_boost*std::clamp((pedal-.5f)/.5f, 0.f, 1.f)*std::clamp((rpm-1500)/1500, 0.f, 1.f);
|
||||
if (boost_target_ <= 0 || fuel_cut()) { boost_integral_ = 0; return; }
|
||||
const float error = boost_target_-filtered_boost_;
|
||||
const float proposed = boost_integral_+boost_ki*error*dt;
|
||||
const float command = .3f+boost_kp*error+proposed;
|
||||
if ((command >= 0 && command <= .85f) || (command > .85f && error < 0) || (command < 0 && error > 0))
|
||||
boost_integral_ = proposed;
|
||||
boost_duty_ = std::clamp(.3f+boost_kp*error+boost_integral_, 0.f, .85f);
|
||||
}
|
||||
void Ecu::step(float dt, float pedal, float rpm) {
|
||||
fault_ = !std::isfinite(dt) || dt <= 0.0f || !std::isfinite(pedal) || !std::isfinite(rpm) || rpm < 0.0f;
|
||||
if (fault_) { target_ = 0.0f; idle_ = false; return; }
|
||||
|
||||
15
src/main.cpp
15
src/main.cpp
@@ -17,6 +17,7 @@ int main(int argc, char** argv) {
|
||||
std::string scenario_name = "twin";
|
||||
TwoStrokeConfig two_stroke_config;
|
||||
bool turbo_enabled = false;
|
||||
std::string turbo_name;
|
||||
for (int i = 1; i + 1 < argc; ++i) {
|
||||
const std::string option = argv[i];
|
||||
if (option == "--seconds") duration_seconds = std::max(0.0f, std::strtof(argv[++i], nullptr));
|
||||
@@ -24,8 +25,9 @@ int main(int argc, char** argv) {
|
||||
else if (option == "--scenario") scenario_name = argv[++i];
|
||||
else if (option == "--turbo") {
|
||||
const std::string value = argv[++i];
|
||||
if (value != "none" && value != "vz21") { std::cerr << "Choose none or vz21 turbo\n"; return 2; }
|
||||
turbo_enabled = value == "vz21";
|
||||
if (value != "none" && value != "vz21" && value != "gt17") { std::cerr << "Choose none, vz21 or gt17 turbo\n"; return 2; }
|
||||
turbo_name = value;
|
||||
turbo_enabled = value != "none";
|
||||
}
|
||||
else if (option == "--exhaust") {
|
||||
const std::string value = argv[++i];
|
||||
@@ -35,14 +37,19 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
if (turbo_enabled && scenario_name != "twin") { std::cerr << "The turbo test preset uses --scenario twin\n"; return 2; }
|
||||
if (turbo_name.empty() && scenario_name == "b205") turbo_enabled = true;
|
||||
if ((!turbo_name.empty() && turbo_name != "none" &&
|
||||
!((turbo_name == "gt17" && scenario_name == "b205") || (turbo_name == "vz21" && scenario_name == "twin"))) ||
|
||||
(turbo_enabled && scenario_name != "twin" && scenario_name != "b205")) {
|
||||
std::cerr << "Use gt17 with b205 or vz21 with twin\n"; return 2;
|
||||
}
|
||||
constexpr int sample_rate = 44100;
|
||||
const auto total_samples = static_cast<std::uint64_t>(duration_seconds * sample_rate);
|
||||
const auto report_interval = static_cast<std::uint64_t>(sample_rate / 10);
|
||||
if (scenario_name == "twin" || scenario_name == "b205") {
|
||||
FourStrokeScenario scenario(sample_rate, scenario_name == "b205" ? FourStrokePreset::SaabB205 : FourStrokePreset::Twin, turbo_enabled);
|
||||
scenario.throttle = throttle;
|
||||
std::cout << (scenario_name == "b205" ? "FluidSimCpp | Saab B205 / 9-5 wagon (no turbo) | throttle " : "FluidSimCpp | 600 cc parallel twin | throttle ") << std::fixed << std::setprecision(0)
|
||||
std::cout << (scenario_name == "b205" ? "FluidSimCpp | Saab B205 / 9-5 wagon | throttle " : "FluidSimCpp | 600 cc parallel twin | throttle ") << std::fixed << std::setprecision(0)
|
||||
<< throttle * 100.0f << "%\n";
|
||||
for (std::uint64_t i = 0; i < total_samples; ++i) {
|
||||
scenario.process();
|
||||
|
||||
@@ -81,7 +81,8 @@ FourStrokeScenario::FourStrokeScenario(int sample_rate, FourStrokePreset selecte
|
||||
solver_.add_component(intake_plenum);
|
||||
solver_.add_component(exhaust_collector);
|
||||
if (turbo_enabled) {
|
||||
turbo = std::make_unique<Turbocharger>();
|
||||
turbo = std::make_unique<Turbocharger>(saab ? TurboConfig::gt17() : TurboConfig{});
|
||||
ecu.boost_control_enabled = saab;
|
||||
const int fresh = 2+2*count, exhaust_exit = fresh+1;
|
||||
boundaries_.add_orifice(intake_atmosphere.port, fresh, true, intake_boundary_area_);
|
||||
turbo->connect(boundaries_, pipes, fresh, false, 0, true, 1+2*count, false, exhaust_exit, true);
|
||||
@@ -109,6 +110,10 @@ AudioFrame FourStrokeScenario::process() {
|
||||
const std::lock_guard guard(mutex_);
|
||||
step_motorcycle(crankshaft, dt_, preset == FourStrokePreset::SaabB205 ? 200.f : 30.f);
|
||||
ecu.step(dt_, throttle, crankshaft.omega * 60 / (2*pi));
|
||||
if (turbo) {
|
||||
ecu.control_boost(dt_, throttle, crankshaft.omega * 60/(2*pi), intake_plenum.pressure(), turbo->compressor_outlet.pressure());
|
||||
turbo->set_wastegate_duty(ecu.wastegate_duty());
|
||||
}
|
||||
const auto engines = cylinders();
|
||||
for (int i = 0; i < cylinder_count(); ++i) {
|
||||
auto& c = *engines[i];
|
||||
@@ -180,6 +185,8 @@ ScenarioSnapshot FourStrokeScenario::snapshot() const {
|
||||
const std::lock_guard<std::mutex> guard(mutex_);
|
||||
ScenarioSnapshot result;
|
||||
result.turbo_enabled = bool(turbo);
|
||||
result.boost_target = ecu.boost_target();
|
||||
result.wastegate_duty = ecu.wastegate_duty();
|
||||
if (turbo) result.turbo = turbo->snapshot();
|
||||
result.solver_substeps = solver_.substep_count();
|
||||
result.boundary_iterations = boundaries_.nonlinear_iterations;
|
||||
@@ -240,7 +247,7 @@ std::vector<Pipe1D> FourStrokeScenario::create_pipes(FourStrokePreset preset, bo
|
||||
result.emplace_back(area_for_diameter(saab ? .032f : .0275f), saab ? .25f : .12f, 10);
|
||||
for (int i = 0; i < count; ++i)
|
||||
result.emplace_back(area_for_diameter(saab ? .034f : .028f), saab ? .28f : .20f, saab ? 10 : 6);
|
||||
result.emplace_back(area_for_diameter(saab ? .060f : .028f), turbo_enabled ? .12f : (saab ? 1.4f : .40f), saab ? 40 : 10);
|
||||
result.emplace_back(area_for_diameter(saab ? .060f : .028f), turbo_enabled ? .12f : (saab ? 1.4f : .40f), saab ? (turbo_enabled ? 8 : 40) : 10);
|
||||
if (turbo_enabled) {
|
||||
result.emplace_back(area_for_diameter(saab ? .060f : .0275f), .10f, 8);
|
||||
result.emplace_back(area_for_diameter(saab ? .060f : .032f), .60f, 24);
|
||||
|
||||
@@ -22,6 +22,26 @@ TurboConfig checked(TurboConfig c) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
TurboConfig TurboConfig::gt17() {
|
||||
TurboConfig c;
|
||||
c.shaft_inertia = 2.0e-5;
|
||||
c.compressor_radius = .026;
|
||||
c.turbine_radius = .019;
|
||||
c.compressor_efficiency = .70;
|
||||
c.turbine_peak_efficiency = .70;
|
||||
c.compressor_effective_area = 420e-6;
|
||||
c.turbine_effective_area = 350e-6;
|
||||
c.bearing_viscous = 2e-7;
|
||||
c.windage = 5e-11;
|
||||
c.gate_diaphragm_area = .001;
|
||||
c.gate_spring_preload = 25;
|
||||
c.gate_diameter = .024;
|
||||
c.gate_maximum_lift = .007;
|
||||
c.compressor_inlet_volume = c.compressor_outlet_volume = 100e-6f;
|
||||
c.turbine_inlet_volume = c.turbine_outlet_volume = 100e-6f;
|
||||
c.connection_diameters = {.045f, .045f, .040f, .050f};
|
||||
return c;
|
||||
}
|
||||
Turbocharger::Turbocharger(TurboConfig configuration)
|
||||
: config(checked(configuration)),
|
||||
compressor_inlet(config.compressor_inlet_volume), compressor_outlet(config.compressor_outlet_volume),
|
||||
@@ -114,7 +134,7 @@ void Turbocharger::update_state(float timestep) {
|
||||
|
||||
const double tp = turbine_inlet.pressure(), tout = turbine_outlet.pressure();
|
||||
const double can_force = std::max(0., double(compressor_outlet.pressure())-config.reference_pressure)*
|
||||
config.gate_diaphragm_area;
|
||||
config.gate_diaphragm_area*(1-gate_duty_);
|
||||
const double equilibrium_lift = (can_force-config.gate_spring_preload)/config.gate_spring_stiffness;
|
||||
// Overdamped spring-can linkage: b*dx/dt + k*x = F-Fpreload, with seats.
|
||||
wastegate_lift_ = std::clamp(equilibrium_lift+(wastegate_lift_-equilibrium_lift)*
|
||||
|
||||
Reference in New Issue
Block a user