2020-10-30 01:53:54 -07:00
|
|
|
#include "heater_control.h"
|
2020-10-31 14:58:34 -07:00
|
|
|
#include "wideband_config.h"
|
2020-10-30 01:53:54 -07:00
|
|
|
|
2020-10-30 02:40:18 -07:00
|
|
|
#include "ch.h"
|
2020-10-30 01:53:54 -07:00
|
|
|
#include "hal.h"
|
2020-12-10 18:32:41 -08:00
|
|
|
|
|
|
|
#include "fault.h"
|
2020-10-30 01:53:54 -07:00
|
|
|
#include "pwm.h"
|
|
|
|
#include "sampling.h"
|
2020-10-31 14:58:34 -07:00
|
|
|
#include "pid.h"
|
2022-04-06 01:22:09 -07:00
|
|
|
#include "can.h"
|
2020-10-30 01:53:54 -07:00
|
|
|
|
2022-01-01 21:10:55 -08:00
|
|
|
using namespace wbo;
|
|
|
|
|
2020-10-30 01:53:54 -07:00
|
|
|
// 400khz / 1024 = 390hz PWM
|
2022-08-29 17:19:30 -07:00
|
|
|
static Pwm heaterPwm(HEATER_PWM_DEVICE);
|
|
|
|
PWMConfig heaterPwmConfig = {
|
|
|
|
400'000,
|
|
|
|
1024,
|
|
|
|
nullptr,
|
|
|
|
{
|
|
|
|
{PWM_OUTPUT_ACTIVE_HIGH, nullptr},
|
|
|
|
{PWM_OUTPUT_ACTIVE_HIGH, nullptr},
|
|
|
|
{PWM_OUTPUT_ACTIVE_HIGH, nullptr},
|
|
|
|
{PWM_OUTPUT_ACTIVE_HIGH, nullptr}
|
|
|
|
},
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
#if STM32_PWM_USE_ADVANCED
|
|
|
|
0
|
|
|
|
#endif
|
|
|
|
};
|
2020-10-30 01:53:54 -07:00
|
|
|
|
2022-04-06 17:23:18 -07:00
|
|
|
static constexpr int preheatTimeCounter = HEATER_PREHEAT_TIME / HEATER_CONTROL_PERIOD;
|
|
|
|
static constexpr int batteryStabTimeCounter = HEATER_BATTERY_STAB_TIME / HEATER_CONTROL_PERIOD;
|
2020-10-30 02:40:18 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
struct heater_state {
|
|
|
|
Pid heaterPid;
|
|
|
|
int timeCounter;
|
|
|
|
int batteryStabTime;
|
|
|
|
float rampVoltage;
|
|
|
|
HeaterState heaterState;
|
|
|
|
uint8_t ch;
|
|
|
|
uint8_t pwm_ch;
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct heater_state state[AFR_CHANNELS] =
|
|
|
|
{
|
|
|
|
{
|
|
|
|
.heaterPid = Pid(
|
|
|
|
0.3f, // kP
|
|
|
|
0.3f, // kI
|
|
|
|
0.01f, // kD
|
|
|
|
3.0f, // Integrator clamp (volts)
|
|
|
|
HEATER_CONTROL_PERIOD
|
|
|
|
),
|
|
|
|
.timeCounter = preheatTimeCounter,
|
|
|
|
.batteryStabTime = batteryStabTimeCounter,
|
|
|
|
.rampVoltage = 0,
|
|
|
|
.heaterState = HeaterState::Preheat,
|
|
|
|
.ch = 0,
|
|
|
|
.pwm_ch = HEATER_PWM_CHANNEL_0,
|
|
|
|
},
|
|
|
|
#if (AFR_CHANNELS > 1)
|
|
|
|
{
|
|
|
|
.heaterPid = Pid(
|
|
|
|
0.3f, // kP
|
|
|
|
0.3f, // kI
|
|
|
|
0.01f, // kD
|
|
|
|
3.0f, // Integrator clamp (volts)
|
|
|
|
HEATER_CONTROL_PERIOD
|
|
|
|
),
|
|
|
|
.timeCounter = preheatTimeCounter,
|
|
|
|
.batteryStabTime = batteryStabTimeCounter,
|
|
|
|
.rampVoltage = 0,
|
|
|
|
.heaterState = HeaterState::Preheat,
|
|
|
|
.ch = 1,
|
|
|
|
.pwm_ch = HEATER_PWM_CHANNEL_1,
|
|
|
|
},
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
static HeaterState GetNextState(struct heater_state &s, HeaterAllow heaterAllowState, float batteryVoltage, float sensorEsr)
|
2020-10-30 02:40:18 -07:00
|
|
|
{
|
2022-04-06 01:22:09 -07:00
|
|
|
bool heaterAllowed = heaterAllowState == HeaterAllow::Allowed;
|
|
|
|
|
2022-04-06 14:07:39 -07:00
|
|
|
// Check battery voltage for thresholds only if there is still no command over CAN
|
|
|
|
if (heaterAllowState == HeaterAllow::Unknown)
|
|
|
|
{
|
|
|
|
// measured voltage too low to auto-start heating
|
|
|
|
if (batteryVoltage < HEATER_BATTETY_OFF_VOLTAGE)
|
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
s.batteryStabTime = batteryStabTimeCounter;
|
2022-04-06 14:07:39 -07:00
|
|
|
}
|
|
|
|
// measured voltage is high enougth to auto-start heating, wait some time to stabilize
|
2022-08-29 17:19:30 -07:00
|
|
|
if ((batteryVoltage > HEATER_BATTERY_ON_VOLTAGE) && (s.batteryStabTime > 0))
|
2022-04-06 14:07:39 -07:00
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
s.batteryStabTime--;
|
2022-04-06 14:07:39 -07:00
|
|
|
}
|
2022-08-29 17:19:30 -07:00
|
|
|
heaterAllowed = s.batteryStabTime == 0;
|
2022-04-06 14:07:39 -07:00
|
|
|
}
|
|
|
|
|
2021-07-15 21:50:22 -07:00
|
|
|
if (!heaterAllowed)
|
|
|
|
{
|
|
|
|
// ECU hasn't allowed preheat yet, reset timer, and force preheat state
|
2022-08-29 17:19:30 -07:00
|
|
|
s.timeCounter = preheatTimeCounter;
|
2021-07-15 21:50:22 -07:00
|
|
|
return HeaterState::Preheat;
|
|
|
|
}
|
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
switch (s.heaterState)
|
2020-10-30 02:40:18 -07:00
|
|
|
{
|
|
|
|
case HeaterState::Preheat:
|
2022-08-29 17:19:30 -07:00
|
|
|
s.timeCounter--;
|
2020-10-30 02:40:18 -07:00
|
|
|
|
2020-12-13 17:24:33 -08:00
|
|
|
// If preheat timeout, or sensor is already hot (engine running?)
|
2022-08-29 17:19:30 -07:00
|
|
|
if (s.timeCounter <= 0 || sensorEsr < HEATER_CLOSED_LOOP_THRESHOLD_ESR)
|
2020-10-30 02:40:18 -07:00
|
|
|
{
|
|
|
|
// If enough time has elapsed, start the ramp
|
2021-07-12 15:24:20 -07:00
|
|
|
// Start the ramp at 4 volts
|
2022-08-29 17:19:30 -07:00
|
|
|
s.rampVoltage = 4;
|
2020-12-10 18:32:41 -08:00
|
|
|
|
|
|
|
// Next phase times out at 15 seconds
|
2022-08-29 17:19:30 -07:00
|
|
|
s.timeCounter = HEATER_WARMUP_TIMEOUT / HEATER_CONTROL_PERIOD;
|
2020-12-10 18:32:41 -08:00
|
|
|
|
2020-10-30 02:40:18 -07:00
|
|
|
return HeaterState::WarmupRamp;
|
|
|
|
}
|
2020-10-31 14:54:50 -07:00
|
|
|
|
|
|
|
// Stay in preheat - wait for time to elapse
|
|
|
|
break;
|
2020-10-30 02:40:18 -07:00
|
|
|
case HeaterState::WarmupRamp:
|
2020-12-10 22:08:00 -08:00
|
|
|
if (sensorEsr < HEATER_CLOSED_LOOP_THRESHOLD_ESR)
|
2020-10-30 02:40:18 -07:00
|
|
|
{
|
|
|
|
return HeaterState::ClosedLoop;
|
|
|
|
}
|
2022-08-29 17:19:30 -07:00
|
|
|
else if (s.timeCounter == 0)
|
2020-12-10 18:32:41 -08:00
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
SetFault(s.ch, Fault::SensorDidntHeat);
|
2020-12-10 18:32:41 -08:00
|
|
|
return HeaterState::Stopped;
|
|
|
|
}
|
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
s.timeCounter--;
|
2020-10-31 14:54:50 -07:00
|
|
|
|
|
|
|
break;
|
2020-12-10 18:32:41 -08:00
|
|
|
case HeaterState::ClosedLoop:
|
2021-11-04 15:07:47 -07:00
|
|
|
// Check that the sensor's ESR is acceptable for normal operation
|
2020-12-11 15:46:03 -08:00
|
|
|
if (sensorEsr < HEATER_OVERHEAT_ESR)
|
2020-12-10 22:08:00 -08:00
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
SetFault(s.ch, Fault::SensorOverheat);
|
2020-12-11 15:10:04 -08:00
|
|
|
return HeaterState::Stopped;
|
2020-12-10 22:08:00 -08:00
|
|
|
}
|
2020-12-11 15:46:03 -08:00
|
|
|
else if (sensorEsr > HEATER_UNDERHEAT_ESR)
|
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
SetFault(s.ch, Fault::SensorUnderheat);
|
2020-12-11 15:46:03 -08:00
|
|
|
return HeaterState::Stopped;
|
|
|
|
}
|
2020-12-10 22:08:00 -08:00
|
|
|
|
2020-12-10 18:32:41 -08:00
|
|
|
break;
|
|
|
|
case HeaterState::Stopped: break;
|
2020-10-30 02:40:18 -07:00
|
|
|
}
|
2020-10-31 14:54:50 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
return s.heaterState;
|
2020-10-30 02:40:18 -07:00
|
|
|
}
|
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
static float GetVoltageForState(struct heater_state &s, float heaterEsr)
|
2020-10-30 02:40:18 -07:00
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
switch (s.heaterState)
|
2020-10-30 02:40:18 -07:00
|
|
|
{
|
2021-07-15 21:48:24 -07:00
|
|
|
case HeaterState::Preheat:
|
|
|
|
// Max allowed during condensation phase (preheat) is 2v
|
|
|
|
return 1.5f;
|
2020-10-30 02:40:18 -07:00
|
|
|
case HeaterState::WarmupRamp:
|
2022-08-29 17:19:30 -07:00
|
|
|
if (s.rampVoltage < 10)
|
2020-10-30 02:40:18 -07:00
|
|
|
{
|
2021-07-09 23:15:24 -07:00
|
|
|
// 0.3 volt per second, divided by battery voltage and update rate
|
|
|
|
constexpr float rampRateVoltPerSecond = 0.3f;
|
2021-04-26 17:40:06 -07:00
|
|
|
constexpr float heaterFrequency = 1000.0f / HEATER_CONTROL_PERIOD;
|
2022-08-29 17:19:30 -07:00
|
|
|
s.rampVoltage += (rampRateVoltPerSecond / heaterFrequency);
|
2020-10-30 02:40:18 -07:00
|
|
|
}
|
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
return s.rampVoltage;
|
2020-10-30 02:40:18 -07:00
|
|
|
case HeaterState::ClosedLoop:
|
2021-07-12 15:46:10 -07:00
|
|
|
// "nominal" heater voltage is 7.5v, so apply correction around that point (instead of relying on integrator so much)
|
2020-10-31 17:34:36 -07:00
|
|
|
// Negated because lower resistance -> hotter
|
2022-08-29 17:19:30 -07:00
|
|
|
return 7.5f - s.heaterPid.GetOutput(HEATER_TARGET_ESR, heaterEsr);
|
2020-12-10 18:32:41 -08:00
|
|
|
case HeaterState::Stopped:
|
2021-07-12 15:24:20 -07:00
|
|
|
// Something has gone wrong, turn off the heater.
|
2020-12-10 18:32:41 -08:00
|
|
|
return 0;
|
2020-10-30 02:40:18 -07:00
|
|
|
}
|
2020-12-10 21:05:02 -08:00
|
|
|
|
|
|
|
// should be unreachable
|
|
|
|
return 0;
|
2020-10-30 02:40:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
static THD_WORKING_AREA(waHeaterThread, 256);
|
|
|
|
static void HeaterThread(void*)
|
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
int ch;
|
|
|
|
|
2021-06-03 21:03:41 -07:00
|
|
|
// Wait for temperature sensing to stabilize so we don't
|
|
|
|
// immediately think we overshot the target temperature
|
|
|
|
chThdSleepMilliseconds(1000);
|
|
|
|
|
2020-10-30 02:40:18 -07:00
|
|
|
while (true)
|
|
|
|
{
|
2022-04-06 01:22:09 -07:00
|
|
|
auto heaterAllowState = GetHeaterAllowed();
|
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
for (ch = 0; ch < AFR_CHANNELS; ch++) {
|
|
|
|
heater_state &s = state[ch];
|
2022-04-06 01:22:09 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
// Read sensor state
|
|
|
|
float heaterEsr = GetSensorInternalResistance(ch);
|
2020-10-30 02:40:18 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
// If we haven't heard from rusEFI, use the internally sensed
|
|
|
|
// battery voltage instead of voltage over CAN.
|
|
|
|
float batteryVoltage = heaterAllowState == HeaterAllow::Unknown
|
|
|
|
? GetInternalBatteryVoltage(ch)
|
|
|
|
: GetRemoteBatteryVoltage();
|
2021-07-09 23:15:24 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
// Run the state machine
|
|
|
|
s.heaterState = GetNextState(s, heaterAllowState, batteryVoltage, heaterEsr);
|
|
|
|
float heaterVoltage = GetVoltageForState(s, heaterEsr);
|
2021-07-12 15:24:20 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
// Limit to 11 volts
|
|
|
|
if (heaterVoltage > 11) {
|
|
|
|
heaterVoltage = 11;
|
|
|
|
}
|
2022-07-15 00:07:13 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
// duty = (V_eff / V_batt) ^ 2
|
|
|
|
float voltageRatio = heaterVoltage / batteryVoltage;
|
|
|
|
float duty = voltageRatio * voltageRatio;
|
|
|
|
|
|
|
|
#ifdef HEATER_MAX_DUTY
|
|
|
|
if (duty > HEATER_MAX_DUTY) {
|
|
|
|
duty = HEATER_MAX_DUTY;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (batteryVoltage < 23)
|
|
|
|
{
|
|
|
|
// Pipe the output to the heater driver
|
|
|
|
heaterPwm.SetDuty(s.pwm_ch, duty);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Overvoltage protection - sensor not rated for PWM above 24v
|
|
|
|
heaterPwm.SetDuty(s.pwm_ch, 0);
|
|
|
|
}
|
2021-07-12 22:44:42 -07:00
|
|
|
}
|
2020-10-30 02:40:18 -07:00
|
|
|
|
|
|
|
// Loop at ~20hz
|
2020-10-31 14:58:34 -07:00
|
|
|
chThdSleepMilliseconds(HEATER_CONTROL_PERIOD);
|
2020-10-30 02:40:18 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-30 01:53:54 -07:00
|
|
|
void StartHeaterControl()
|
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
heaterPwm.Start(heaterPwmConfig);
|
|
|
|
heaterPwm.SetDuty(state[0].pwm_ch, 0);
|
|
|
|
#if (AFR_CHANNELS > 1)
|
|
|
|
heaterPwm.SetDuty(state[1].pwm_ch, 0);
|
|
|
|
#endif
|
2020-10-30 02:40:18 -07:00
|
|
|
|
|
|
|
chThdCreateStatic(waHeaterThread, sizeof(waHeaterThread), NORMALPRIO + 1, HeaterThread, nullptr);
|
2020-10-30 01:53:54 -07:00
|
|
|
}
|
2020-10-31 14:54:50 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
bool IsRunningClosedLoop(int ch)
|
2020-10-31 14:54:50 -07:00
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
return state[ch].heaterState == HeaterState::ClosedLoop;
|
2020-10-31 14:54:50 -07:00
|
|
|
}
|
2021-07-12 15:31:02 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
float GetHeaterDuty(int ch)
|
2022-01-04 11:16:46 -08:00
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
return heaterPwm.GetLastDuty(state[ch].pwm_ch);
|
2022-01-04 11:16:46 -08:00
|
|
|
}
|
2022-05-11 01:41:07 -07:00
|
|
|
|
2022-08-29 17:19:30 -07:00
|
|
|
HeaterState GetHeaterState(int ch)
|
2022-05-11 01:41:07 -07:00
|
|
|
{
|
2022-08-29 17:19:30 -07:00
|
|
|
return state[ch].heaterState;
|
2022-05-11 01:41:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const char* describeHeaterState(HeaterState state)
|
|
|
|
{
|
|
|
|
switch (state) {
|
|
|
|
case HeaterState::Preheat:
|
|
|
|
return "Preheat";
|
|
|
|
case HeaterState::WarmupRamp:
|
|
|
|
return "WarmupRamp";
|
|
|
|
case HeaterState::ClosedLoop:
|
|
|
|
return "ClosedLoop";
|
|
|
|
case HeaterState::Stopped:
|
|
|
|
return "Stopped";
|
|
|
|
}
|
|
|
|
|
|
|
|
return "Unknown";
|
|
|
|
}
|