77
88using namespace wbo ;
99
10+ #define HEATER_OVERVOLTAGE_THRESHOLD 23 .0f
11+ #define MAX_HEATER_VOLTAGE 12 .0f
12+
1013static const PidConfig heaterPidConfig =
1114{
1215 .kP = 0 .3f , // kP
@@ -26,11 +29,14 @@ void HeaterControllerBase::Configure(float targetTempC, float targetEsr, struct
2629 m_targetTempC = targetTempC;
2730 m_targetEsr = targetEsr;
2831 m_configuration = configuration;
32+ m_retryTime = 0 ;
33+
34+ m_stateTimer.reset ();
2935
30- m_preheatTimer.reset ();
31- m_warmupTimer.reset ();
3236 m_heaterStableTimer.reset ();
33- m_closedLoopStableTimer.reset ();
37+ m_undervoltTimer.reset ();
38+ m_underheatTimer.reset ();
39+ m_overheatTimer.reset ();
3440}
3541
3642bool HeaterControllerBase::IsRunningClosedLoop () const
@@ -53,87 +59,143 @@ HeaterState HeaterControllerBase::GetHeaterState() const
5359 return heaterState;
5460}
5561
62+ HeaterState HeaterControllerBase::changeState (HeaterState newState, Status status)
63+ {
64+ m_stateTimer.reset ();
65+ SetStatus (ch, status);
66+ return newState;
67+ }
68+
69+ HeaterState HeaterControllerBase::stopWithRetry (Status status, int retryTimeSec)
70+ {
71+ m_retryTime = retryTimeSec;
72+ return changeState (HeaterState::Stopped, status);
73+ }
74+
5675HeaterState HeaterControllerBase::GetNextState (HeaterState currentState, HeaterAllow heaterAllowState, float heaterSupplyVoltage, float sensorTemp)
5776{
58- bool heaterAllowed = heaterAllowState == HeaterAllow::Allowed;
77+ const float overheatTemp = m_targetTempC + 100 ;
78+ const float closedLoopTemp = m_targetTempC - 30 ;
79+ const float underheatTemp = m_targetTempC - 100 ;
80+
81+ // Common checks that apply to all running states
82+ // This is here to avoid repeating these checks in every state - if any of these conditions are met, we want to stop heating immediately
83+ // There should be no code outside of specific states otherwise to keep the state machine logic clear and maintainable
5984
60- // Check battery voltage for thresholds only if there is still no command over CAN
61- if (heaterAllowState == HeaterAllow::Unknown)
85+ if (heaterSupplyVoltage > HEATER_OVERVOLTAGE_THRESHOLD )
6286 {
63- // measured voltage too low to auto-start heating
64- if (heaterSupplyVoltage < m_configuration->HeaterSupplyOffVoltage )
65- {
66- m_heaterStableTimer.reset ();
67- }
68- else if (heaterSupplyVoltage > m_configuration->HeaterSupplyOnVoltage )
69- {
70- // measured voltage is high enougth to auto-start heating, wait some time to stabilize
71- heaterAllowed = m_heaterStableTimer.hasElapsedSec (HEATER_BATTERY_STAB_TIME );
72- }
87+ // If voltage is dangerously high, immediately stop heating to protect the sensor
88+ return stopWithRetry (Status::SensorOvervoltage, HEATER_OVERVOLT_RETRY_TIMEOUT );
7389 }
7490
75- if (!heaterAllowed)
91+ // If voltage is back to normal, reset undervoltage timer
92+ if (heaterSupplyVoltage > m_configuration->HeaterSupplyOffVoltage )
7693 {
77- // ECU hasn't allowed preheat yet, reset timer, and force preheat state
78- m_preheatTimer.reset ();
79- SetStatus (ch, Status::Preheat);
80- return HeaterState::Preheat;
94+ m_undervoltTimer.reset ();
8195 }
8296
83- float overheatTemp = m_targetTempC + 100 ;
84- float closedLoopTemp = m_targetTempC - 30 ;
85- float underheatTemp = m_targetTempC - 100 ;
97+ // Voltage has been too low for too long, stop heating and set fault
98+ if (m_undervoltTimer.hasElapsedSec (0 .5f ))
99+ {
100+ return stopWithRetry (Status::SensorUndervoltage, HEATER_UNDERVOLT_RETRY_TIMEOUT );
101+ }
86102
87103 switch (currentState)
88104 {
105+ case HeaterState::Stopped:
106+ {
107+ // If retry timer is running, wait until it elapses before allowing to start preheat again
108+ if ((m_retryTime) && !(m_stateTimer.hasElapsedSec (m_retryTime)))
109+ {
110+ break ;
111+ }
112+ // Disable retry timer
113+ m_retryTime = 0 ;
114+
115+ if (heaterAllowState == HeaterAllow::NotAllowed)
116+ {
117+ // ECU has explicitly disallowed heating, stay stopped
118+ break ;
119+ }
120+
121+ // If we haven't received any CAN message about whether heating is allowed or not
122+ // we should wait until heater supply voltage stabilizes above threshold before allowing to start heating
123+ if (heaterAllowState == HeaterAllow::Unknown)
124+ {
125+ // Reset voltage_stable timer if voltage is too low
126+ if (heaterSupplyVoltage < m_configuration->HeaterSupplyOnVoltage )
127+ {
128+ m_heaterStableTimer.reset ();
129+ }
130+
131+ if (!m_heaterStableTimer.hasElapsedSec (HEATER_VOLTAGE_STAB_TIME ))
132+ {
133+ // Continue to wait for voltage to stabilize before allowing heating
134+ break ;
135+ }
136+ }
137+
138+ // Otherwise, start preheat
139+ return changeState (HeaterState::Preheat, Status::Preheat);
140+ break ;
141+ }
89142 case HeaterState::Preheat:
90- #ifdef HEATER_FAST_HEATING_THRESHOLD_T
91- if (sensorTemp >= HEATER_FAST_HEATING_THRESHOLD_T ) {
92- // if sensor is already hot - we can start from higher heater voltage
93- rampVoltage = 9 ;
143+ {
144+ bool startRamp = false ;
94145
95- // Reset the timer for the warmup phase
96- m_warmupTimer.reset ();
146+ rampVoltage = 7 .0f ;
97147
98- SetStatus (ch, Status::Warmup);
99- return HeaterState::WarmupRamp;
148+ #ifdef HEATER_FAST_HEATING_THRESHOLD_T
149+ // If the sensor is already above a certain temperature, skip preheat and go straight to warmup ramp
150+ if (sensorTemp > HEATER_FAST_HEATING_THRESHOLD_T )
151+ {
152+ // Start ramp at higher voltage to speed up heating for cases like hot restarts where sensor is already warm
153+ rampVoltage = 9 .0f ;
154+ startRamp = true ;
100155 }
156+
101157 #endif
102158
103- // If preheat timeout, or sensor is already hot (engine running?)
104- if (m_preheatTimer. hasElapsedSec (m_configuration-> PreheatTimeSec ) || sensorTemp > closedLoopTemp)
159+ // If the sensor is already hot (engine running?), skip preheat and go straight to warmup ramp
160+ if (sensorTemp > closedLoopTemp)
105161 {
106- // If enough time has elapsed, start the ramp
107- // Start the ramp at 7 volts
108- rampVoltage = 7 ;
162+ startRamp = true ;
163+ }
109164
110- // Reset the timer for the warmup phase
111- m_warmupTimer.reset ();
165+ // If enough time has elapsed in preheat (condensation phase has passed), start warmup ramp
166+ if (m_stateTimer.hasElapsedSec (m_configuration->PreheatTimeSec ))
167+ {
168+ startRamp = true ;
169+ }
112170
113- SetStatus (ch, Status::Warmup);
114- return HeaterState::WarmupRamp;
171+ if (startRamp)
172+ {
173+ // Reset the timer for the warmup phase
174+ return changeState (HeaterState::WarmupRamp, Status::Warmup);
115175 }
116176
117- // Stay in preheat - wait for time to elapse
177+ // Stay in preheat
118178 break ;
179+ }
119180 case HeaterState::WarmupRamp:
181+ {
182+ // Already hot enough, start closed loop
120183 if (sensorTemp > closedLoopTemp)
121184 {
122- m_closedLoopStableTimer.reset ();
123- SetStatus (ch, Status::RunningClosedLoop);
124- return HeaterState::ClosedLoop;
185+ return changeState (HeaterState::ClosedLoop, Status::RunningClosedLoop);
125186 }
126- else if (m_warmupTimer.hasElapsedSec (HEATER_WARMUP_TIMEOUT ))
187+
188+ // If we've been trying to warm up for too long without reaching the closed loop threshold,
189+ // something is wrong (like heater not working), so stop and set fault
190+ if (m_stateTimer.hasElapsedSec (HEATER_WARMUP_TIMEOUT ))
127191 {
128- SetStatus (ch, Status::SensorDidntHeat);
129- // retry after timeout
130- m_retryTime = HEATER_DIDNOTHEAT_RETRY_TIMEOUT ;
131- m_retryTimer.reset ();
132- return HeaterState::Stopped;
192+ return stopWithRetry (Status::SensorDidntHeat, HEATER_DIDNOTHEAT_RETRY_TIMEOUT );
133193 }
134194
135195 break ;
196+ }
136197 case HeaterState::ClosedLoop:
198+ {
137199 // Over/under heat timers track how long it's been since
138200 // temperature was within normal range (then we abort if
139201 // it's been too long out of range)
@@ -147,47 +209,46 @@ HeaterState HeaterControllerBase::GetNextState(HeaterState currentState, HeaterA
147209 m_underheatTimer.reset ();
148210 }
149211
150- if (m_closedLoopStableTimer.hasElapsedSec (HEATER_CLOSED_LOOP_STAB_TIME )) {
151- if (m_overheatTimer.hasElapsedSec (0 .5f ))
152- {
153- SetStatus (ch, Status::SensorOverheat);
154- // retry after timeout
155- m_retryTime = HEATER_OVERHEAT_RETRY_TIMEOUT ;
156- m_retryTimer.reset ();
157- return HeaterState::Stopped;
158- }
159- else if (m_underheatTimer.hasElapsedSec (0 .5f ))
160- {
161- SetStatus (ch, Status::SensorUnderheat);
162- // retry after timeout
163- m_retryTime = HEATER_UNDERHEAT_RETRY_TIMEOUT ;
164- m_retryTimer.reset ();
165- return HeaterState::Stopped;
166- }
167- } else {
212+ // Check for overheat befor checking for closed loop stable time
213+ // if we're overheating, we want to stop as soon as possible, even if we haven't been in closed loop for very long
214+ if (m_overheatTimer.hasElapsedSec (0 .5f ))
215+ {
216+ return stopWithRetry (Status::SensorOverheat, HEATER_OVERHEAT_RETRY_TIMEOUT );
217+ }
218+
219+
220+ if (!m_stateTimer.hasElapsedSec (HEATER_CLOSED_LOOP_STAB_TIME ))
221+ {
168222 // give some time for stabilization...
169223 // looks like heavy ramped Ipump affects sensorTemp measure
170224 // and right after switch to closed loop sensorTemp drops below underhead threshold
225+ break ;
171226 }
172227
173- break ;
174- case HeaterState::Stopped:
175- if ((m_retryTime) && (m_retryTimer. hasElapsedSec (m_retryTime))) {
176- return HeaterState::Preheat ;
228+
229+ else if (m_underheatTimer. hasElapsedSec ( 0 . 5f ))
230+ {
231+ return stopWithRetry (Status::SensorUnderheat, HEATER_UNDERHEAT_RETRY_TIMEOUT ) ;
177232 }
233+
178234 break ;
235+ }
179236 }
180237
181238 return currentState;
182239}
183240
184241float HeaterControllerBase::GetVoltageForState (HeaterState state, float sensorEsr)
185242{
243+ float heaterVoltage = 0 ;
244+
186245 switch (state)
187246 {
188247 case HeaterState::Preheat:
189248 // Max allowed during condensation phase (preheat) is 2v
190- return 2 .0f ;
249+ heaterVoltage = 2 .0f ;
250+ break ;
251+
191252 case HeaterState::WarmupRamp:
192253 if (rampVoltage < 12 )
193254 {
@@ -197,20 +258,29 @@ float HeaterControllerBase::GetVoltageForState(HeaterState state, float sensorEs
197258 rampVoltage += (rampRateVoltPerSecond / heaterFrequency);
198259 }
199260
200- return rampVoltage;
261+ heaterVoltage = rampVoltage;
262+ break ;
263+
201264 case HeaterState::ClosedLoop:
202265 // "nominal" heater voltage is 7.5v, so apply correction around that point (instead of relying on integrator so much)
203266 // Negated because lower resistance -> hotter
204267
205268 // TODO: heater PID should operate on temperature, not ESR
206- return 7 .5f - m_pid.GetOutput (m_targetEsr, sensorEsr);
269+ heaterVoltage = 7 .5f - m_pid.GetOutput (m_targetEsr, sensorEsr);
270+ break ;
271+
207272 case HeaterState::Stopped:
208273 // Something has gone wrong, turn off the heater.
209- return 0 ;
274+ heaterVoltage = 0 ;
275+ break ;
210276 }
211277
212- // should be unreachable
213- return 0 ;
278+ // Limit to MAX_HEATER_VOLTAGE as per specification
279+ if (heaterVoltage > MAX_HEATER_VOLTAGE ) {
280+ heaterVoltage = MAX_HEATER_VOLTAGE ;
281+ }
282+
283+ return heaterVoltage;
214284}
215285
216286void HeaterControllerBase::Update (const ISampler& sampler, HeaterAllow heaterAllowState)
@@ -219,6 +289,9 @@ void HeaterControllerBase::Update(const ISampler& sampler, HeaterAllow heaterAll
219289 float sensorEsr = sampler.GetSensorInternalResistance ();
220290 float sensorTemperature = sampler.GetSensorTemperature ();
221291
292+ // TODO: Clean this up, looks like a mess
293+ // Move supply voltage reading logic into port so that it's cleaner and more testable
294+ // Also unify voltage reading logic across the codebase, so that everyting is consistent
222295 #if defined(HEATER_INPUT_DIVIDER)
223296 // if board has ability to measure heater supply localy - use it
224297 float heaterSupplyVoltage = sampler.GetInternalHeaterVoltage ();
@@ -231,37 +304,25 @@ void HeaterControllerBase::Update(const ISampler& sampler, HeaterAllow heaterAll
231304
232305 // Run the state machine
233306 heaterState = GetNextState (heaterState, heaterAllowState, heaterSupplyVoltage, sensorTemperature);
234- float heaterVoltage = GetVoltageForState (heaterState, sensorEsr);
307+ heaterVoltage = GetVoltageForState (heaterState, sensorEsr);
235308
236- // Limit to 12 volts
237- if (heaterVoltage > 12 ) {
238- heaterVoltage = 12 ;
239- }
240-
241- // Very low supply voltage -> avoid divide by zero or very high duty
242- if (heaterSupplyVoltage < 3 ) {
243- heaterSupplyVoltage = 12 ;
244- }
245-
246- // duty = (V_eff / V_batt) ^ 2
247- float voltageRatio = (heaterSupplyVoltage < 1 .0f ) ? 0 : heaterVoltage / heaterSupplyVoltage;
248- float duty = voltageRatio * voltageRatio;
249-
250- #ifdef HEATER_MAX_DUTY
251- cycle++;
252- // limit PWM each 10th cycle (2 time per second) to measure heater supply voltage throuth "Heater-"
253- if ((cycle % 10 ) == 0 ) {
254- if (duty > HEATER_MAX_DUTY ) {
255- duty = HEATER_MAX_DUTY ;
256- }
257- }
258- #endif
309+ float duty = 0 ;
259310
260- // Protect the sensor in case of very high voltage
261- if (heaterSupplyVoltage >= 23 )
311+ if (heaterVoltage > 0 )
262312 {
263- duty = 0 ;
264- heaterVoltage = 0 ;
313+ // duty = (V_eff / V_batt) ^ 2
314+ float voltageRatio = heaterVoltage / heaterSupplyVoltage;
315+ duty = voltageRatio * voltageRatio;
316+
317+ #ifdef HEATER_MAX_DUTY
318+ cycle++;
319+ // limit PWM each 10th cycle (2 time per second) to measure heater supply voltage through "Heater-"
320+ if ((cycle % 10 ) == 0 ) {
321+ if (duty > HEATER_MAX_DUTY ) {
322+ duty = HEATER_MAX_DUTY ;
323+ }
324+ }
325+ #endif
265326 }
266327
267328 // Pipe the output to the heater driver
0 commit comments