models/fullDiveHarness.slx wraps the Scuba_Diver plant (subsystem reference) but currently drives it with constants (Inflate=0, Purge=0, V_lung=2.75 L), so the diver just sinks until the depth>45 m stop trips. The task: design a 1-hour dive trajectory and a closed-loop controller that flies the diver through it using the three plant inputs — Inflate BCD (0–1), Purge/Deflate BCD (0–1), V_lung (m³) — with feedback from the five plant outputs: depth, vel, acc, Fbcd, Vbcd.
Control philosophy (per user direction): mimic a real diver. The BCD is touched only during depth changes — feedforward-fill it to a known neutral volume for the target depth, then leave it alone. At constant depth, all fine control comes from breathing modulation (shifting mean lung volume). BCD valves stay shut during holds except rare re-trim bursts when breathing authority runs out.
| Item | Value | Source |
|---|---|---|
| Body net buoyancy | +25.6 N (V=0.065 m³, ρ_body=985) | +scuba/DiverBody.ssc |
| Weights | 4 kg → −39.2 N | Mass PB block in Scuba_Diver.slx |
| Tank gas weight | 98.47 mol × 0.029 kg/mol → −28 N at start, decays with consumption | Gas Tank block |
| Lung buoyancy | ρg·V_lung → +27.7 N at 2.75 L; ±0.75 L trim ⇒ ±7.7 N breathing authority | Lungs.ssc |
| BCD buoyancy | ρg·Vbcd, bladder max 15 L (+150 N) | BCDBladder.ssc |
| Net, empty BCD, start | ≈ −14 N (sinks); V_bcd ≈ 1.39 L for neutral at mean lung volume | computed |
| Inflate valve | n_dot = cmd·ΔP/2e4, ΔP ≈ 10 bar (IP offset) → ~50 mol/s at cmd=1 — enormous gain; cmd must stay ≪ 1 |
BCDInflateValve.ssc, 1st-stage IP_offset=10e5 Pa |
| Purge valve | n_dot = cmd·(P_bcd−P_amb+5000)/1e4 ≈ 0.5 mol/s max, one-way |
PurgeValve.ssc |
| Harness | initial depth 0.5 m; Stop Simulation when depth<0.1 or >45 m; StopTime=3900 s; ode23t | fullDiveHarness.slx |
Key dynamics:
- The plant is open-loop unstable in depth (Boyle effect — descend → bladder compresses → buoyancy drops → sink faster), so feedback is mandatory; but a volume servo on Vbcd inherently neutralizes the Boyle instability — holding the bladder volume constant makes buoyancy depth-independent.
- Neutral V_bcd is depth-independent in this plant (no wetsuit compression modeled): the volume needed for zero net force is the same at every depth; only the moles required to hold it change. The depth→V_bcd feedforward table will therefore be nearly flat today, but the structure directly supports wetsuit compression later (which makes it genuinely depth-dependent) and makes the diver-like behavior explicit: add air on descent, vent on ascent, to keep bladder volume at the neutral value.
- Tank depletion (~28 N over the dive, ≈0.5 N/min) is a slow disturbance: breathing trim absorbs it between occasional BCD re-trims.
- Terminal sink rate with empty BCD ≈ 0.45 m/s (27 m/min).
- Descent ≤ ~18 m/min; ascent ≤ 9–10 m/min (30 ft/min); 3–5 min safety stop at 5 m.
- BCD used in short bursts during depth changes to re-establish neutral trim; breath control (lung volume) for all fine adjustments (<1 m) around neutral buoyancy. Properly trimmed divers rarely touch the inflator at constant depth.
Sources: DAN ascent rates, PADI safety stop history, buoyancy technique, scuba-tutor BCD use, breath control.
- Profile: multilevel 18 m → 12 m (typical recreational dive).
- Implementation: standard blocks + one MATLAB Function for valve allocation (no Stateflow).
- BCD used sparingly: feedforward depth→V_bcd lookup table; breathing modulation is the primary controller at constant depth.
1-D Lookup Table driven by a Clock, linear interpolation, followed by a Rate Limiter (rising ≤ 0.3 m/s descent, falling ≤ 0.15 m/s ascent). Breakpoints (t in s, depth in m):
| Phase | t (min) | depth |
|---|---|---|
| Start | 0 | 0.5 |
| Descent (~9 m/min) | 0 → 2 | 0.5 → 18 |
| Bottom 1 | 2 → 25 | 18 |
| Step up (~3 m/min) | 25 → 27 | 18 → 12 |
| Bottom 2 (multilevel) | 27 → 45 | 12 |
| Ascent (~2.3 m/min ≤ 9 m/min limit) | 45 → 48 | 12 → 5 |
| Safety stop | 48 → 53 | 5 |
| Final slow ascent (~1 m/min) | 53 → 58 | 5 → 0 |
The run ends naturally at ~58 min when depth < 0.1 m trips the existing Stop Simulation logic. A second lookup table holds the profile slope (piecewise-constant v_ff, desired velocity feedforward), avoiding a Derivative block. v_ff ≠ 0 also serves as the "transition phase" flag that arms the BCD.
All inside a new Dive Controller subsystem in fullDiveHarness.slx, replacing the three constants:
Clock ─→ [Lookup: depth_ref] ─→ Rate Limiter ─→ depth_ref
└→ [Lookup: v_ff]
── BCD path (coarse, active only in transitions / rare re-trims) ──
[Lookup: depth_ref → V_bcd_neutral] (feedforward table, calibrated)
V_bcd_ref = V_bcd_neutral(depth_ref)
+ K_vff·v_ff (descent bias: less volume going down,
more going up; zero during holds)
+ V_trim_slow (slow integrator, see re-trim logic)
[MATLAB Fcn valveAllocation]: e_V = V_bcd_ref − Vbcd
armed when |v_ff| > 0 OR |depth_ref − depth| > 1 m OR re-trim burst requested
deadband ±0.15 L; inflate = sat(k_inf·e_V, 0, ~0.01); deflate = sat(−k_def·e_V, 0, 1)
disarmed → both outputs exactly 0 (valves shut through entire holds)
── Breathing path (fine, primary controller at constant depth) ──
trim = −(Kp_b·(depth − depth_ref) + Kd_b·vel) (PD; deeper than ref → inhale more)
trim saturated to ±0.75e-3 m³
V_lung = V_mean(2.75e-3) + trim + (0.25e-3)·sin(2π·0.2·t) (12 breaths/min, 0.5 L tidal)
V_lung saturated to [1.5e-3, 4.5e-3] m³
── Re-trim logic (inside valveAllocation MATLAB Fcn) ──
If breathing trim stays > 80 % saturated for > 15 s during a hold
(tank getting lighter, or table miscalibration), integrate V_trim_slow a small step
and request a short BCD burst to re-center the trim — then go silent again.
During a hold, the BCD servo has already placed Vbcd at (near-)neutral volume; bladder volume is constant while valves are shut only if depth is constant — and depth is held by the breathing PD loop, whose ±7.7 N authority dominates residual trim errors (deadband ±0.15 L ⇒ ≤ ±1.5 N residual; depletion ≈ 0.5 N/min accumulates slowly). If depth drifts despite breathing (trim saturated), the re-trim logic fires one burst. The Boyle instability only matters when depth moves ≥ metres — exactly when the BCD servo is armed.
V_bcd_neutral(depth) computed analytically in scripts/initDiveController.m from the force balance at mean lung volume:
V_bcd_neutral = (m_weights·g + n_tank0·M_gas·g − F_body_net − ρg·V_mean) / (ρg) ≈ 1.39e-3 m³, tabulated over depth breakpoints [0 … 45] m (flat today; placeholder for wetsuit compression). Verify/adjust by simulation trim runs at fixed depths if the analytic value leaves a residual.
Inputs: V_bcd_ref, Vbcd, v_ff, depth_err, trim (breathing trim, for saturation detection), clock. Outputs: inflate, deflate, V_trim_slow (fed back via Unit Delay or kept as persistent). Discrete logic (arming, burst timer, slow integrator) uses persistent variables; block sample time small but discrete (e.g. 0.1 s) so ode23t doesn't chatter on the switching.
- Never inflate and deflate simultaneously.
inf_max ≈ 0.01: the inflate valve passes ~50 mol/s at cmd=1 (ΔP≈10 bar), so commands must be scaled way down for realistic ~L/s fill rates. Purge authority is much lower; deflate may use full 0–1.
- Breathing PD: Kp_b ≈ 1.5e-3 m³/m, Kd_b ≈ 3e-3 m³/(m/s) (velocity damping is what stabilizes the loop).
- K_vff ≈ 1e-3 m³/(m/s) (≈1 L extra vent at full 0.15 m/s ascent bias — small).
- Valve servo: k_inf ≈ 5 (cmd per m³ error, capped 0.01), k_def ≈ 300.
- Re-trim: step V_trim_slow by 0.1e-3 m³ per event.
- Create
Dive Trajectorysubsystem (root): Clock → two 1-D Lookup Tables (depth_ref, v_ff) → Rate Limiter on depth_ref. Outputs:depth_ref,v_ff. - Create
Dive Controllersubsystem (root): inputsdepth_ref, v_ff, depth, vel, acc, Vbcd, Fbcd; outputsInflate, Deflate, V_lung.- 1-D Lookup Table
V_bcd_neutral(depth_ref)+ Gain(K_vff)·v_ff + Sum → V_bcd_ref, saturated [0, 15e-3] m³. - MATLAB Function block
valveAllocationas in §2 (discrete sample time 0.1 s). - Breathing path: Sum/Gain (PD) → Saturation(±0.75e-3) → Sum with V_mean constant and Sine Wave → Saturation [1.5e-3, 4.5e-3] → V_lung.
- 1-D Lookup Table
- Rewire harness root: delete the three Constant blocks (blk_124, blk_125, blk_122); connect Scuba Diver outputs depth/vel/acc/Vbcd/Fbcd to the controller, controller outputs to Inflate_BCD/Purge_BCD/V_lung. Keep existing stop logic and scopes; add scopes/To Workspace for depth vs depth_ref, Vbcd vs V_bcd_ref, valve commands, V_lung/trim.
- Parameters: profile breakpoints, feedforward table, and gains defined as workspace variables in a new
scripts/initDiveController.m(could later migrate intodata/scubaParams.sldd). - Validate structure:
model_check(unconnected ports/lines) on the harness.
- Run the full dive:
sim('fullDiveHarness')(or Play). Success criteria:- Depth tracks the profile: |depth − depth_ref| < 1 m during holds; no stop-condition trip before ~57 min; sim ends via surface stop (~58 min) or StopTime.
- Ascent ≤ 0.15 m/s sustained; descent ≤ 0.3 m/s.
- Safety stop held at 5 ± 1 m for 3+ min.
- Diver-realism criterion: BCD valves closed (both commands = 0) for > 95 % of each constant-depth hold; breathing trim visibly doing the fine control; only occasional short re-trim bursts as the tank lightens.
- Never inflate and deflate simultaneously; Vbcd within [0, 15 L]; V_lung within [1.5, 4.5] L.
- Plot results: depth vs ref, vel, Vbcd vs V_bcd_ref, inflate/deflate commands, V_lung and trim over the hour. Expect: inflate bursts during descent (Boyle), deflate bursts during ascents, silence during holds; breathing trim slowly drifting positive-to-negative as the tank lightens between re-trims.
- Robustness spot-checks: rerun with ±1 kg on Weights — feedforward table is now wrong; confirm re-trim logic converges and holds still work (breathing + occasional bursts).
- If ode23t struggles with valve switching, smooth the burst edges (first-order filter τ≈0.5 s on commands) — the discrete 0.1 s MATLAB Fcn should already prevent chattering.
| File | Change |
|---|---|
models/fullDiveHarness.slx |
Add Dive Trajectory + Dive Controller subsystems, rewire root (already has uncommitted modifications — build on current state, don't revert) |
scripts/initDiveController.m |
New — profile breakpoints, feedforward V_bcd table, controller gains, breathing params |
models/Scuba_Diver.slx |
No changes (plant untouched) |
- No changes to Simscape components or the plant model.
- No Stateflow (per user choice); no test-suite work; no dashboard.
- Inflate valve gain: the model instance uses
R_open = 5e6 Pa·s/mol(not the 2e4 default inBCDInflateValve.ssc), so cmd=1 gives ~0.2 mol/s (~1.7 L/s at 18 m) — already realistic inflator flow. Allocation uses the full range:k_inf = 300,inf_max = 0.6,k_def = 300. - Lung dynamics lag: first-order lag (
tau_lung = 0.5 s, integrator-based with IC = V_mean) between the breathing sum and the V_lung output. Physically motivated (lung volume can't step) and required — it breaks the algebraic loop between the volume-commanded Lungs component and the plant outputs. - Memory blocks on valve commands: the Simscape DAE reports direct feedthrough from valve commands to outputs, which formed an algebraic loop through the discrete MATLAB Function. Two Memory blocks on inflate/deflate outputs break it.
- Re-trim detection filters the trim signal: raw
trim_fracoscillates with tidal breathing and kept resetting the saturation timer. The MATLAB Function low-passes it (tau 5 s), triggers at |trimF| > 0.6 sustained 15 s, stepsV_trim_slowby 0.2 mL (> deadband so the burst acts), valve window 5 s. - Model InitFcn =
initDiveControllerso parameters load automatically.
- Dive completes: surfaces at 57.6 min via the depth < 0.1 m stop; max depth 18.32 m (0.32 m descent overshoot).
- Hold tracking |err|: 18 m mean 0.08 / max 0.19 m; 12 m mean 0.25 / max 0.34 m; 5 m stop mean 0.20 / max 0.41 m.
- Rates: max descent 0.21 m/s (≤ 0.3), max ascent 0.11 m/s (≤ 0.15).
- BCD valves closed ≥ 99% of every hold (duty 0 / 0.29 / 0.83 %); never inflate+deflate simultaneously; fine control visibly carried by breathing (V_lung mean drifts down as tank lightens, reset by occasional re-trim bursts at ~36 and ~48 min).
- Vbcd 0.02–1.63 L; V_lung 1.79–3.62 L (within physiological limits).
- Robustness: with the feedforward table deliberately 0.5 L lean, the dive still completes with 18 m hold error mean 0.09 m.