Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions firmware/boards/port.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "port_shared.h"
#include "wideband_config.h"
#include "heater_control.h"

struct AnalogChannelResult
{
Expand Down Expand Up @@ -57,7 +58,7 @@ class Configuration {
private:
// Increment this any time the configuration format changes
// It is stored along with the data to ensure that it has been written before
static constexpr uint32_t ExpectedTag = 0xDEADBE02;
static constexpr uint32_t ExpectedTag = 0xDEADBE03;
uint32_t Tag = ExpectedTag;

public:
Expand Down Expand Up @@ -107,6 +108,10 @@ class Configuration {
egt[i].AemNetIdOffset = i;
}

heaterConfig.HeaterSupplyOffVoltage = HEATER_SUPPLY_OFF_VOLTAGE;
heaterConfig.HeaterSupplyOnVoltage = HEATER_SUPPLY_ON_VOLTAGE;
heaterConfig.PreheatTimeSec = HEATER_PREHEAT_TIME;

/* Finaly */
Tag = ExpectedTag;
}
Expand Down Expand Up @@ -143,12 +148,15 @@ class Configuration {
uint8_t AemNetIdOffset;
uint8_t pad[5];
} egt[2];

struct HeaterConfig heaterConfig;
} __attribute__((packed));

// pad to 256 bytes including tag
uint8_t pad[256 - sizeof(Tag)];
};
};
static_assert(sizeof(Configuration) == 256, "Configuration size incorrect");

int InitConfiguration();
Configuration* GetConfiguration();
Expand All @@ -170,4 +178,4 @@ extern "C" void checkDfuAndJump();
SensorType GetSensorType();
void SetupESRDriver(SensorType sensor);
void ToggleESRDriver(SensorType sensor);
int GetESRSupplyR();
int GetESRSupplyR();
17 changes: 9 additions & 8 deletions firmware/heater_control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "fault.h"
#include "sampling.h"

#include "port.h"

using namespace wbo;

static const PidConfig heaterPidConfig =
Expand All @@ -13,18 +15,17 @@ static const PidConfig heaterPidConfig =
.clamp = 3.0f, // Integrator clamp (volts)
};

HeaterControllerBase::HeaterControllerBase(int ch, int preheatTimeSec, int warmupTimeSec)
HeaterControllerBase::HeaterControllerBase(int ch)
: m_pid(heaterPidConfig, HEATER_CONTROL_PERIOD)
, ch(ch)
, m_preheatTimeSec(preheatTimeSec)
, m_warmupTimeSec(warmupTimeSec)
{
}

void HeaterControllerBase::Configure(float targetTempC, float targetEsr)
void HeaterControllerBase::Configure(float targetTempC, float targetEsr, struct HeaterConfig* configuration)
{
m_targetTempC = targetTempC;
m_targetEsr = targetEsr;
m_configuration = configuration;

m_preheatTimer.reset();
m_warmupTimer.reset();
Expand Down Expand Up @@ -60,14 +61,14 @@ HeaterState HeaterControllerBase::GetNextState(HeaterState currentState, HeaterA
if (heaterAllowState == HeaterAllow::Unknown)
{
// measured voltage too low to auto-start heating
if (heaterSupplyVoltage < HEATER_SUPPLY_OFF_VOLTAGE)
if (heaterSupplyVoltage < m_configuration->HeaterSupplyOffVoltage)
{
m_heaterStableTimer.reset();
// set fault
SetFault(ch, Fault::SensorNoHeatSupply);
return HeaterState::NoHeaterSupply;
}
else if (heaterSupplyVoltage > HEATER_SUPPLY_ON_VOLTAGE)
else if (heaterSupplyVoltage > m_configuration->HeaterSupplyOnVoltage)
{
// measured voltage is high enougth to auto-start heating, wait some time to stabilize
heaterAllowed = m_heaterStableTimer.hasElapsedSec(HEATER_BATTERY_STAB_TIME);
Expand Down Expand Up @@ -101,7 +102,7 @@ HeaterState HeaterControllerBase::GetNextState(HeaterState currentState, HeaterA
#endif

// If preheat timeout, or sensor is already hot (engine running?)
if (m_preheatTimer.hasElapsedSec(m_preheatTimeSec) || sensorTemp > closedLoopTemp)
if (m_preheatTimer.hasElapsedSec(m_configuration->PreheatTimeSec) || sensorTemp > closedLoopTemp)
{
// If enough time has elapsed, start the ramp
// Start the ramp at 7 volts
Expand All @@ -121,7 +122,7 @@ HeaterState HeaterControllerBase::GetNextState(HeaterState currentState, HeaterA
m_closedLoopStableTimer.reset();
return HeaterState::ClosedLoop;
}
else if (m_warmupTimer.hasElapsedSec(m_warmupTimeSec))
else if (m_warmupTimer.hasElapsedSec(HEATER_WARMUP_TIMEOUT))
{
SetFault(ch, Fault::SensorDidntHeat);
// retry after timeout
Expand Down
18 changes: 12 additions & 6 deletions firmware/heater_control.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "can.h"
#include "pid.h"
#include "timer.h"
#include "fixed_point.h"

enum class HeaterState
{
Expand All @@ -17,6 +18,14 @@ enum class HeaterState
NoHeaterSupply,
};

struct HeaterConfig {
FixedPoint<uint8_t, 10> HeaterSupplyOffVoltage; // in 0.1V steps, 25.5V max
FixedPoint<uint8_t, 10> HeaterSupplyOnVoltage; // in 0.1V steps, 25.5V max
ScaledValue<uint8_t, 5> PreheatTimeSec; // In 5 second steps, 1275s max
uint8_t pad[5];
} __attribute__((packed));
static_assert(sizeof(HeaterConfig) == 8, "HeaterConfig size incorrect");

struct ISampler;

struct IHeaterController
Expand All @@ -31,8 +40,8 @@ struct IHeaterController
class HeaterControllerBase : public IHeaterController
{
public:
HeaterControllerBase(int ch, int preheatTimeSec, int warmupTimeSec);
void Configure(float targetTempC, float targetEsr);
HeaterControllerBase(int ch);
void Configure(float targetTempC, float targetEsr, struct HeaterConfig* configuration);
void Update(const ISampler& sampler, HeaterAllow heaterAllowState) override;

bool IsRunningClosedLoop() const override;
Expand Down Expand Up @@ -62,9 +71,6 @@ class HeaterControllerBase : public IHeaterController

const uint8_t ch;

const int m_preheatTimeSec;
const int m_warmupTimeSec;

int m_retryTime = 0;

Timer m_heaterStableTimer;
Expand All @@ -79,7 +85,7 @@ class HeaterControllerBase : public IHeaterController
Timer m_underheatTimer;
Timer m_overheatTimer;

static const int batteryStabTimeCounter = HEATER_BATTERY_STAB_TIME / HEATER_CONTROL_PERIOD;
struct HeaterConfig* m_configuration;
};

const IHeaterController& GetHeaterController(int ch);
Expand Down
11 changes: 7 additions & 4 deletions firmware/heater_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ static const PWMConfig heaterPwmConfig = {
class HeaterController : public HeaterControllerBase {
public:
HeaterController(int ch, int pwm_ch)
: HeaterControllerBase(ch, HEATER_PREHEAT_TIME, HEATER_WARMUP_TIMEOUT)
: HeaterControllerBase(ch)
, pwm_ch(pwm_ch)
{
}
Expand Down Expand Up @@ -74,21 +74,24 @@ static void HeaterThread(void*)
// immediately think we overshot the target temperature
chThdSleepMilliseconds(1000);

struct HeaterConfig* configuration = &GetConfiguration()->heaterConfig;

// Configure heater controllers for sensor type
for (int i = 0; i < AFR_CHANNELS; i++)
{
auto& h = heaterControllers[i];

switch (GetSensorType())
{
case SensorType::LSU42:
h.Configure(730, 80);
h.Configure(730, 80, configuration);
break;
case SensorType::LSUADV:
h.Configure(785, 300);
h.Configure(785, 300, configuration);
break;
case SensorType::LSU49:
default:
h.Configure(780, 300);
h.Configure(780, 300, configuration);
break;
}
}
Expand Down
12 changes: 11 additions & 1 deletion firmware/ini/wideband_dual.ini
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,11 @@ AemNetEgtTx0 = bits, U08, 152, [2:2], "Disable", "Enable"
AemNetEgtIdx0 = scalar, U08, 154, "", 1, 0, 0, 255, 0

AemNetEgtTx1 = bits, U08, 160, [2:2], "Disable", "Enable"
AemNetEgtIdx1 = scalar, U08, 162, "", 1, 0, 0, 255, 0
AemNetEgtIdx1 = scalar, U08, 162, "", 1, 0, 0, 255, 0

HeaterSupplyOffVoltage = scalar, U08, 168, "V", 0.1, 0, 0, 24.0, 2
HeaterSupplyOnVoltage = scalar, U08, 169, "V", 0.1, 0, 0, 24.0, 2
PreheatTimeSec = scalar, U08, 170, "s", 5, 0, 0, 1275, 0

page = 2 ; this is a RAM only page with no burnable flash
; name = class, type, offset, [shape], units, scale, translate, min, max, digits
Expand Down Expand Up @@ -333,6 +337,7 @@ entry = EGT1_commErrors, "EGT 1: comm errors", int, "%d"
menuDialog = main
menu = "&Settings"
subMenu = sensor_settings, "Sensor settings"
subMenu = heater_settings, "Heater settings"
subMenu = can_settings, "CAN AFR settings"
subMenu = can_egt_settings, "CAN EGT settings"

Expand Down Expand Up @@ -365,6 +370,11 @@ cmd_openblt = "Z\x00\xbc\x00\x00"
dialog = sensor_settings, "Sensor Settings"
field = "Sensor Type", LsuSensorType

dialog = heater_settings, "Heater Settings"
field = "Heater Supply Off Voltage", HeaterSupplyOffVoltage
field = "Heater Supply On Voltage", HeaterSupplyOnVoltage
field = "Preheat Time Sec", PreheatTimeSec

dialog = afr0_can_settings, "AFR 0 (left) channel CAN Settings"
field = "RusEFI protocol:"
field = "Output AFR", RusEfiTx0
Expand Down
83 changes: 83 additions & 0 deletions firmware/util/fixed_point.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#pragma once

#include <cstdint>
#include <type_traits>
#include <limits>

/**
* @brief A lightweight class for storing scaled values using a base type with ratio scale factor.
*
* This class is designed to be compatible with packed structures
*
* Examples:
* - ScaledValue<int16_t, 10> multiplies raw values by 10.0 (e.g., 13 raw -> 130.0f)
* - ScaledValue<int16_t, 1, 10> multiplies raw values by 0.1 (e.g., 128 raw -> 12.8f)
*
* @tparam TStorage Base type for storage (e.g., int8_t, uint8_t, int16_t, uint16_t, etc.).
* @tparam TScaleFactorNumerator Numerator of the scale factor (e.g., 10 for scale 10.0).
* @tparam TScaleFactorDenominator Denominator of the scale factor (e.g., 10 for scale 0.1).
*/
template<typename TStorage, uint16_t TScaleFactorNumerator, uint16_t TScaleFactorDenominator = 1>
struct ScaledValue {
static_assert(std::is_integral<TStorage>::value, "TStorage must be an integral type");
static_assert(TScaleFactorDenominator != 0, "TScaleFactorDenominator must not be zero");

static constexpr float scale() {
return static_cast<float>(TScaleFactorNumerator) / static_cast<float>(TScaleFactorDenominator);
}

TStorage value; // Storage using the base type

// Convert to float
constexpr float getValue() const {
return static_cast<float>(value) * scale();
}

// Convert from float
constexpr void setValue(float val) {
float scaled = val / scale();
if (scaled < minRawValue()) {
value = minRawValue();
return;
}
if (scaled > maxRawValue()) {
value = maxRawValue();
return;
}
value = static_cast<TStorage>(scaled + (scaled >= 0 ? 0.5f : -0.5f)); // Round to nearest
}

constexpr TStorage getRaw() const {
return value;
}

constexpr void setRaw(TStorage raw) {
value = raw;
}

// Implicit conversion to float
constexpr operator float() const {
return getValue();
}

// Implicit assignment from float
constexpr ScaledValue& operator=(float val) {
setValue(val);
return *this;
}

private:
// Maximum raw value that can be stored
constexpr TStorage maxRawValue() const {
return std::numeric_limits<TStorage>::max();
}

// Minimum raw value that can be stored
constexpr TStorage minRawValue() const {
return std::numeric_limits<TStorage>::min();
}
};

// Alias for fixed-point
template<typename TStorage, int TScaleFactor>
using FixedPoint = ScaledValue<TStorage, 1, TScaleFactor>;
2 changes: 2 additions & 0 deletions test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ CPPSRC += \
test_stubs.cpp \
tests/test_sampler.cpp \
tests/test_heater.cpp \
tests/test_fixed_point.cpp \
tests/test_config.cpp \

INCDIR += \
$(PROJECT_DIR)/googletest/googlemock/ \
Expand Down
Loading