wideband/firmware/heater_control.cpp

360 lines
9.7 KiB
C++
Raw Normal View History

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 "port.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"
#include "can.h"
2020-10-30 01:53:54 -07:00
struct sensorHeaterParams {
float targetTemp;
float targetESR;
};
static const struct sensorHeaterParams heaterParams49 = {
.targetTemp = 780,
.targetESR = 300,
};
static const struct sensorHeaterParams heaterParams42 = {
.targetTemp = 730,
.targetESR = 80,
};
static const struct sensorHeaterParams heaterParamsAdv = {
.targetTemp = 785,
.targetESR = 300,
};
static const sensorHeaterParams *getHeaterParams(SensorType type) {
switch (type) {
case SensorType::LSU42:
return &heaterParams42;
case SensorType::LSUADV:
return &heaterParamsAdv;
case SensorType::LSU49:
default:
return &heaterParams49;
}
}
2022-01-01 21:10:55 -08:00
using namespace wbo;
2020-10-30 01:53:54 -07:00
// 400khz / 1024 = 390hz PWM
static Pwm heaterPwm(HEATER_PWM_DEVICE);
2022-12-01 15:51:21 -08:00
static const 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;
static const struct sensorHeaterParams *heater;
2020-10-30 02:40:18 -07:00
2023-06-23 13:50:01 -07:00
class HeaterController
{
2023-06-23 13:50:01 -07:00
public:
HeaterController(int ch, int pwm_ch)
: ch(ch), pwm_ch(pwm_ch)
{
2023-06-23 13:50:01 -07:00
}
void Update(const ISampler& sampler, HeaterAllow heaterAllowState);
protected:
HeaterState GetNextState(HeaterAllow haeterAllowState, float batteryVoltage, float sensorTemp);
float GetVoltageForState(float heaterEsr);
// private:
public:
Pid heaterPid =
{
0.3f, // kP
0.3f, // kI
0.01f, // kD
3.0f, // Integrator clamp (volts)
HEATER_CONTROL_PERIOD
2023-06-23 13:50:01 -07:00
};
int timeCounter = preheatTimeCounter;
int batteryStabTime = batteryStabTimeCounter;
float rampVoltage = 0;
float heaterVoltage = 0;
HeaterState heaterState = HeaterState::Preheat;
#ifdef HEATER_MAX_DUTY
int cycle;
#endif
const uint8_t ch;
const uint8_t pwm_ch;
};
HeaterController heaterControllers[AFR_CHANNELS] =
{
{ 0, HEATER_PWM_CHANNEL_0 },
#if AFR_CHANNELS >= 2
{ 1, HEATER_PWM_CHANNEL_1 }
#endif
};
2023-06-23 13:50:01 -07:00
HeaterState HeaterController::GetNextState(HeaterAllow heaterAllowState, float batteryVoltage, float sensorTemp)
2020-10-30 02:40:18 -07:00
{
bool heaterAllowed = heaterAllowState == HeaterAllow::Allowed;
// 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)
{
2023-06-23 13:50:01 -07:00
batteryStabTime = batteryStabTimeCounter;
}
// measured voltage is high enougth to auto-start heating, wait some time to stabilize
2023-06-23 13:50:01 -07:00
if ((batteryVoltage > HEATER_BATTERY_ON_VOLTAGE) && (batteryStabTime > 0))
{
2023-06-23 13:50:01 -07:00
batteryStabTime--;
}
2023-06-23 13:50:01 -07:00
heaterAllowed = batteryStabTime == 0;
}
2021-07-15 21:50:22 -07:00
if (!heaterAllowed)
{
// ECU hasn't allowed preheat yet, reset timer, and force preheat state
2023-06-23 13:50:01 -07:00
timeCounter = preheatTimeCounter;
2021-07-15 21:50:22 -07:00
return HeaterState::Preheat;
}
float overheatTemp = heater->targetTemp + 100;
float closedLoopTemp = heater->targetTemp - 50;
float underheatTemp = heater->targetTemp - 100;
2023-06-23 13:50:01 -07:00
switch (heaterState)
2020-10-30 02:40:18 -07:00
{
case HeaterState::Preheat:
2023-06-23 13:50:01 -07:00
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?)
2023-06-23 13:50:01 -07:00
if (timeCounter <= 0 || sensorTemp > closedLoopTemp)
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
2023-06-23 13:50:01 -07:00
rampVoltage = 4;
2020-12-10 18:32:41 -08:00
// Next phase times out at 15 seconds
2023-06-23 13:50:01 -07:00
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:
if (sensorTemp > closedLoopTemp)
2020-10-30 02:40:18 -07:00
{
return HeaterState::ClosedLoop;
}
2023-06-23 13:50:01 -07:00
else if (timeCounter == 0)
2020-12-10 18:32:41 -08:00
{
2023-06-23 13:50:01 -07:00
SetFault(ch, Fault::SensorDidntHeat);
2020-12-10 18:32:41 -08:00
return HeaterState::Stopped;
}
2023-06-23 13:50:01 -07:00
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
if (sensorTemp > overheatTemp)
2020-12-10 22:08:00 -08:00
{
2023-06-23 13:50:01 -07:00
SetFault(ch, Fault::SensorOverheat);
2020-12-11 15:10:04 -08:00
return HeaterState::Stopped;
2020-12-10 22:08:00 -08:00
}
else if (sensorTemp < underheatTemp)
2020-12-11 15:46:03 -08:00
{
2023-06-23 13:50:01 -07:00
SetFault(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:
case HeaterState::NoHeaterSupply:
/* nop */
break;
2020-10-30 02:40:18 -07:00
}
2020-10-31 14:54:50 -07:00
2023-06-23 13:50:01 -07:00
return heaterState;
2020-10-30 02:40:18 -07:00
}
2023-06-23 13:50:01 -07:00
float HeaterController::GetVoltageForState(float heaterEsr)
2020-10-30 02:40:18 -07:00
{
2023-06-23 13:50:01 -07:00
switch (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:
2023-06-23 13:50:01 -07:00
if (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;
2023-06-23 13:50:01 -07:00
rampVoltage += (rampRateVoltPerSecond / heaterFrequency);
2020-10-30 02:40:18 -07:00
}
2023-06-23 13:50:01 -07:00
return 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
// TODO: heater PID should operate on temperature, not ESR
2023-06-23 13:50:01 -07:00
return 7.5f - heaterPid.GetOutput(heater->targetESR, 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
}
2023-06-23 13:50:01 -07:00
void HeaterController::Update(const ISampler& sampler, HeaterAllow heaterAllowState)
{
// Read sensor state
float heaterEsr = sampler.GetSensorInternalResistance();
float sensorTemperature = sampler.GetSensorTemperature();
// If we haven't heard from the ECU, use the internally sensed
// battery voltage instead of voltage over CAN.
float batteryVoltage = heaterAllowState == HeaterAllow::Unknown
? sampler.GetInternalBatteryVoltage()
: GetRemoteBatteryVoltage();
// Run the state machine
heaterState = GetNextState(heaterAllowState, batteryVoltage, sensorTemperature);
float heaterVoltage = GetVoltageForState(heaterEsr);
// Limit to 11 volts
if (heaterVoltage > 11) {
heaterVoltage = 11;
}
// duty = (V_eff / V_batt) ^ 2
float voltageRatio = heaterVoltage / batteryVoltage;
float duty = voltageRatio * voltageRatio;
#ifdef HEATER_MAX_DUTY
s.cycle++;
// limit PWM each 10th cycle (2 time per second) to measure heater supply voltage throuth "Heater-"
if ((s.cycle % 10) == 0) {
if (duty > HEATER_MAX_DUTY) {
duty = HEATER_MAX_DUTY;
}
}
#endif
if (batteryVoltage >= 23)
{
duty = 0;
heaterVoltage = 0;
}
// Pipe the output to the heater driver
heaterPwm.SetDuty(pwm_ch, duty);
heaterVoltage = heaterVoltage;
}
2020-10-30 02:40:18 -07:00
static THD_WORKING_AREA(waHeaterThread, 256);
static void HeaterThread(void*)
{
int i;
chRegSetThreadName("Heater");
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);
// Get sensor type and settings
heater = getHeaterParams(GetSensorType());
2020-10-30 02:40:18 -07:00
while (true)
{
auto heaterAllowState = GetHeaterAllowed();
for (i = 0; i < AFR_CHANNELS; i++) {
2023-06-20 17:26:54 -07:00
const auto& sampler = GetSampler(i);
2023-06-23 13:50:01 -07:00
auto& heater = heaterControllers[i];
2023-06-20 17:26:54 -07:00
2023-06-23 13:50:01 -07:00
heater.Update(sampler, heaterAllowState);
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()
{
heaterPwm.Start(heaterPwmConfig);
2023-06-23 13:50:01 -07:00
heaterPwm.SetDuty(heaterControllers[0].pwm_ch, 0);
#if (AFR_CHANNELS > 1)
2023-06-23 13:50:01 -07:00
heaterPwm.SetDuty(heaterControllers[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
bool IsRunningClosedLoop(int ch)
2020-10-31 14:54:50 -07:00
{
2023-06-23 13:50:01 -07:00
return heaterControllers[ch].heaterState == HeaterState::ClosedLoop;
2020-10-31 14:54:50 -07:00
}
2021-07-12 15:31:02 -07:00
float GetHeaterDuty(int ch)
2022-01-04 11:16:46 -08:00
{
2023-06-23 13:50:01 -07:00
return heaterPwm.GetLastDuty(heaterControllers[ch].pwm_ch);
2022-01-04 11:16:46 -08:00
}
float GetHeaterEffVoltage(int ch)
{
2023-06-23 13:50:01 -07:00
return heaterControllers[ch].heaterVoltage;
}
HeaterState GetHeaterState(int ch)
{
2023-06-23 13:50:01 -07:00
return heaterControllers[ch].heaterState;
}
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";
case HeaterState::NoHeaterSupply:
return "NoHeaterSupply";
}
return "Unknown";
}