Multichannel (#134)

* Multichannel AFR

* heater: fixes for multi channel mode

* f1_dual: fix configuration names

* pump_dac: fixes for multi-channel mode

* pwm: fixes for multichannel mode

* pump_control: reference instead of pointer

* sampling: reference instead of pointer

* heater_control: reference instead of pointer

* sampling: comment about heater/battery voltage

* f0_module: fixes for multi-channel update

* f1_rev2: fix for multichannel AFR

* hello rev 3

Co-authored-by: rusefillc <sdfsdfqsf2334234234>
This commit is contained in:
Andrey G 2022-08-30 03:19:30 +03:00 committed by GitHub
parent 2cc460adba
commit cddcd0d88c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 371 additions and 249 deletions

View File

@ -53,7 +53,10 @@ void AuxOutThread(void*)
{ {
while(1) while(1)
{ {
SetAuxDac(0, GetLambda()); for (int ch = 0; ch < AFR_CHANNELS; ch++)
{
SetAuxDac(ch, GetLambda(ch));
}
chThdSleepMilliseconds(10); chThdSleepMilliseconds(10);
} }

View File

@ -11,11 +11,11 @@
// PA7 // PA7
#define HEATER_PWM_DEVICE PWMD1 #define HEATER_PWM_DEVICE PWMD1
#define HEATER_PWM_CHANNEL 0 #define HEATER_PWM_CHANNEL_0 0
// PA6 // PA6
#define PUMP_DAC_PWM_DEVICE PWMD3 #define PUMP_DAC_PWM_DEVICE PWMD3
#define PUMP_DAC_PWM_CHANNEL 0 #define PUMP_DAC_PWM_CHANNEL_0 0
#define ID_SEL1_PORT GPIOB #define ID_SEL1_PORT GPIOB
#define ID_SEL1_PIN 1 #define ID_SEL1_PIN 1

View File

@ -31,13 +31,12 @@
// L_heater_pwm - PB7 TIM4_CH2 // L_heater_pwm - PB7 TIM4_CH2
#define HEATER_PWM_DEVICE PWMD4 #define HEATER_PWM_DEVICE PWMD4
#define HEATER_PWM_CHANNEL 1 #define HEATER_PWM_CHANNEL_0 1
#define L_HEATER_PORT GPIOB #define L_HEATER_PORT GPIOB
#define L_HEATER_PIN 7 #define L_HEATER_PIN 7
// B_heater_pwm - PB6 TIM4_CH1 // R_heater_pwm - PB6 TIM4_CH1
#define R_HEATER_PWM_DEVICE PWMD4 #define HEATER_PWM_CHANNEL_1 0
#define R_HEATER_PWM_CHANNEL 0
#define R_HEATER_PORT GPIOB #define R_HEATER_PORT GPIOB
#define R_HEATER_PIN 6 #define R_HEATER_PIN 6
@ -51,7 +50,8 @@
// PA1 TIM2_CH2 // PA1 TIM2_CH2
#define PUMP_DAC_PWM_DEVICE PWMD2 #define PUMP_DAC_PWM_DEVICE PWMD2
#define PUMP_DAC_PWM_CHANNEL 1 #define PUMP_DAC_PWM_CHANNEL_0 2 /* left */
#define PUMP_DAC_PWM_CHANNEL_1 1 /* right */
// TIM1 - DAC for AUX outputs // TIM1 - DAC for AUX outputs
#define AUXOUT_DAC_PWM_DEVICE PWMD1 #define AUXOUT_DAC_PWM_DEVICE PWMD1

View File

@ -31,11 +31,11 @@
// PB6 TIM4_CH1 // PB6 TIM4_CH1
#define HEATER_PWM_DEVICE PWMD4 #define HEATER_PWM_DEVICE PWMD4
#define HEATER_PWM_CHANNEL 0 #define HEATER_PWM_CHANNEL_0 0
// PA1 TIM2_CH2 // PA1 TIM2_CH2
#define PUMP_DAC_PWM_DEVICE PWMD2 #define PUMP_DAC_PWM_DEVICE PWMD2
#define PUMP_DAC_PWM_CHANNEL 1 #define PUMP_DAC_PWM_CHANNEL_0 1
// TIM1 - DAC for AUX outputs // TIM1 - DAC for AUX outputs
#define AUXOUT_DAC_PWM_DEVICE PWMD1 #define AUXOUT_DAC_PWM_DEVICE PWMD1

View File

@ -31,11 +31,11 @@
// PB6 TIM4_CH1 // PB6 TIM4_CH1
#define HEATER_PWM_DEVICE PWMD4 #define HEATER_PWM_DEVICE PWMD4
#define HEATER_PWM_CHANNEL 0 #define HEATER_PWM_CHANNEL_0 0
// PA1 TIM2_CH2 // PA1 TIM2_CH2
#define PUMP_DAC_PWM_DEVICE PWMD2 #define PUMP_DAC_PWM_DEVICE PWMD2
#define PUMP_DAC_PWM_CHANNEL 1 #define PUMP_DAC_PWM_CHANNEL_0 1
// DAC for AUX outputs // DAC for AUX outputs
#define AUXOUT_DAC_DEVICE DACD1 #define AUXOUT_DAC_DEVICE DACD1

View File

@ -131,7 +131,8 @@ void InitCan()
void SendRusefiFormat(uint8_t idx) void SendRusefiFormat(uint8_t idx)
{ {
auto baseAddress = WB_DATA_BASE_ADDR + 2 * idx; auto baseAddress = WB_DATA_BASE_ADDR + 2 * idx;
auto esr = GetSensorInternalResistance(); /* TODO: */
int ch = 0;
{ {
CanTxTyped<wbo::StandardData> frame(baseAddress + 0); CanTxTyped<wbo::StandardData> frame(baseAddress + 0);
@ -139,19 +140,21 @@ void SendRusefiFormat(uint8_t idx)
// The same header is imported by the ECU and checked against this data in the frame // The same header is imported by the ECU and checked against this data in the frame
frame.get().Version = RUSEFI_WIDEBAND_VERSION; frame.get().Version = RUSEFI_WIDEBAND_VERSION;
uint16_t lambda = GetLambda() * 10000; uint16_t lambda = GetLambda(ch) * 10000;
frame.get().Lambda = lambda; frame.get().Lambda = lambda;
frame.get().TemperatureC = GetSensorTemperature(); frame.get().TemperatureC = GetSensorTemperature(ch);
frame.get().Valid = IsRunningClosedLoop() ? 0x01 : 0x00; frame.get().Valid = IsRunningClosedLoop(ch) ? 0x01 : 0x00;
} }
{ {
auto esr = GetSensorInternalResistance(ch);
CanTxTyped<wbo::DiagData> frame(baseAddress + 1); CanTxTyped<wbo::DiagData> frame(baseAddress + 1);
frame.get().Esr = esr; frame.get().Esr = esr;
frame.get().NernstDc = GetNernstDc() * 1000; frame.get().NernstDc = GetNernstDc(ch) * 1000;
frame.get().PumpDuty = GetPumpOutputDuty() * 255; frame.get().PumpDuty = GetPumpOutputDuty(ch) * 255;
frame.get().Status = GetCurrentFault(); frame.get().Status = GetCurrentFault(ch);
frame.get().HeaterDuty = GetHeaterDuty() * 255; frame.get().HeaterDuty = GetHeaterDuty(ch) * 255;
} }
} }

View File

@ -4,7 +4,7 @@ using namespace wbo;
static Fault currentFault = Fault::None; static Fault currentFault = Fault::None;
void SetFault(Fault fault) void SetFault(int ch, Fault fault)
{ {
currentFault = fault; currentFault = fault;
} }
@ -14,7 +14,7 @@ bool HasFault()
return currentFault != Fault::None; return currentFault != Fault::None;
} }
Fault GetCurrentFault() Fault GetCurrentFault(int ch)
{ {
return currentFault; return currentFault;
} }

View File

@ -4,6 +4,6 @@
#include "../for_rusefi/wideband_can.h" #include "../for_rusefi/wideband_can.h"
void SetFault(wbo::Fault fault); void SetFault(int ch, wbo::Fault fault);
bool HasFault(); bool HasFault();
wbo::Fault GetCurrentFault(); wbo::Fault GetCurrentFault(int ch);

View File

@ -13,15 +13,74 @@
using namespace wbo; using namespace wbo;
// 400khz / 1024 = 390hz PWM // 400khz / 1024 = 390hz PWM
static Pwm heaterPwm(HEATER_PWM_DEVICE, HEATER_PWM_CHANNEL, 400'000, 1024); 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
};
static constexpr int preheatTimeCounter = HEATER_PREHEAT_TIME / HEATER_CONTROL_PERIOD; static constexpr int preheatTimeCounter = HEATER_PREHEAT_TIME / HEATER_CONTROL_PERIOD;
static constexpr int batteryStabTimeCounter = HEATER_BATTERY_STAB_TIME / HEATER_CONTROL_PERIOD; static constexpr int batteryStabTimeCounter = HEATER_BATTERY_STAB_TIME / HEATER_CONTROL_PERIOD;
static int timeCounter = preheatTimeCounter;
static int batteryStabTime = batteryStabTimeCounter;
static float rampVoltage = 0;
static HeaterState GetNextState(HeaterState state, HeaterAllow heaterAllowState, float batteryVoltage, float sensorEsr) 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)
{ {
bool heaterAllowed = heaterAllowState == HeaterAllow::Allowed; bool heaterAllowed = heaterAllowState == HeaterAllow::Allowed;
@ -31,37 +90,37 @@ static HeaterState GetNextState(HeaterState state, HeaterAllow heaterAllowState,
// measured voltage too low to auto-start heating // measured voltage too low to auto-start heating
if (batteryVoltage < HEATER_BATTETY_OFF_VOLTAGE) if (batteryVoltage < HEATER_BATTETY_OFF_VOLTAGE)
{ {
batteryStabTime = batteryStabTimeCounter; s.batteryStabTime = batteryStabTimeCounter;
} }
// measured voltage is high enougth to auto-start heating, wait some time to stabilize // measured voltage is high enougth to auto-start heating, wait some time to stabilize
if ((batteryVoltage > HEATER_BATTERY_ON_VOLTAGE) && (batteryStabTime > 0)) if ((batteryVoltage > HEATER_BATTERY_ON_VOLTAGE) && (s.batteryStabTime > 0))
{ {
batteryStabTime--; s.batteryStabTime--;
} }
heaterAllowed = batteryStabTime == 0; heaterAllowed = s.batteryStabTime == 0;
} }
if (!heaterAllowed) if (!heaterAllowed)
{ {
// ECU hasn't allowed preheat yet, reset timer, and force preheat state // ECU hasn't allowed preheat yet, reset timer, and force preheat state
timeCounter = preheatTimeCounter; s.timeCounter = preheatTimeCounter;
return HeaterState::Preheat; return HeaterState::Preheat;
} }
switch (state) switch (s.heaterState)
{ {
case HeaterState::Preheat: case HeaterState::Preheat:
timeCounter--; s.timeCounter--;
// If preheat timeout, or sensor is already hot (engine running?) // If preheat timeout, or sensor is already hot (engine running?)
if (timeCounter <= 0 || sensorEsr < HEATER_CLOSED_LOOP_THRESHOLD_ESR) if (s.timeCounter <= 0 || sensorEsr < HEATER_CLOSED_LOOP_THRESHOLD_ESR)
{ {
// If enough time has elapsed, start the ramp // If enough time has elapsed, start the ramp
// Start the ramp at 4 volts // Start the ramp at 4 volts
rampVoltage = 4; s.rampVoltage = 4;
// Next phase times out at 15 seconds // Next phase times out at 15 seconds
timeCounter = HEATER_WARMUP_TIMEOUT / HEATER_CONTROL_PERIOD; s.timeCounter = HEATER_WARMUP_TIMEOUT / HEATER_CONTROL_PERIOD;
return HeaterState::WarmupRamp; return HeaterState::WarmupRamp;
} }
@ -73,25 +132,25 @@ static HeaterState GetNextState(HeaterState state, HeaterAllow heaterAllowState,
{ {
return HeaterState::ClosedLoop; return HeaterState::ClosedLoop;
} }
else if (timeCounter == 0) else if (s.timeCounter == 0)
{ {
SetFault(Fault::SensorDidntHeat); SetFault(s.ch, Fault::SensorDidntHeat);
return HeaterState::Stopped; return HeaterState::Stopped;
} }
timeCounter--; s.timeCounter--;
break; break;
case HeaterState::ClosedLoop: case HeaterState::ClosedLoop:
// Check that the sensor's ESR is acceptable for normal operation // Check that the sensor's ESR is acceptable for normal operation
if (sensorEsr < HEATER_OVERHEAT_ESR) if (sensorEsr < HEATER_OVERHEAT_ESR)
{ {
SetFault(Fault::SensorOverheat); SetFault(s.ch, Fault::SensorOverheat);
return HeaterState::Stopped; return HeaterState::Stopped;
} }
else if (sensorEsr > HEATER_UNDERHEAT_ESR) else if (sensorEsr > HEATER_UNDERHEAT_ESR)
{ {
SetFault(Fault::SensorUnderheat); SetFault(s.ch, Fault::SensorUnderheat);
return HeaterState::Stopped; return HeaterState::Stopped;
} }
@ -99,38 +158,30 @@ static HeaterState GetNextState(HeaterState state, HeaterAllow heaterAllowState,
case HeaterState::Stopped: break; case HeaterState::Stopped: break;
} }
return state; return s.heaterState;
} }
static Pid heaterPid( static float GetVoltageForState(struct heater_state &s, float heaterEsr)
0.3f, // kP
0.3f, // kI
0.01f, // kD
3.0f, // Integrator clamp (volts)
HEATER_CONTROL_PERIOD
);
static float GetVoltageForState(HeaterState state, float heaterEsr)
{ {
switch (state) switch (s.heaterState)
{ {
case HeaterState::Preheat: case HeaterState::Preheat:
// Max allowed during condensation phase (preheat) is 2v // Max allowed during condensation phase (preheat) is 2v
return 1.5f; return 1.5f;
case HeaterState::WarmupRamp: case HeaterState::WarmupRamp:
if (rampVoltage < 10) if (s.rampVoltage < 10)
{ {
// 0.3 volt per second, divided by battery voltage and update rate // 0.3 volt per second, divided by battery voltage and update rate
constexpr float rampRateVoltPerSecond = 0.3f; constexpr float rampRateVoltPerSecond = 0.3f;
constexpr float heaterFrequency = 1000.0f / HEATER_CONTROL_PERIOD; constexpr float heaterFrequency = 1000.0f / HEATER_CONTROL_PERIOD;
rampVoltage += (rampRateVoltPerSecond / heaterFrequency); s.rampVoltage += (rampRateVoltPerSecond / heaterFrequency);
} }
return rampVoltage; return s.rampVoltage;
case HeaterState::ClosedLoop: case HeaterState::ClosedLoop:
// "nominal" heater voltage is 7.5v, so apply correction around that point (instead of relying on integrator so much) // "nominal" heater voltage is 7.5v, so apply correction around that point (instead of relying on integrator so much)
// Negated because lower resistance -> hotter // Negated because lower resistance -> hotter
return 7.5f - heaterPid.GetOutput(HEATER_TARGET_ESR, heaterEsr); return 7.5f - s.heaterPid.GetOutput(HEATER_TARGET_ESR, heaterEsr);
case HeaterState::Stopped: case HeaterState::Stopped:
// Something has gone wrong, turn off the heater. // Something has gone wrong, turn off the heater.
return 0; return 0;
@ -140,57 +191,60 @@ static float GetVoltageForState(HeaterState state, float heaterEsr)
return 0; return 0;
} }
static HeaterState state = HeaterState::Preheat;
static THD_WORKING_AREA(waHeaterThread, 256); static THD_WORKING_AREA(waHeaterThread, 256);
static void HeaterThread(void*) static void HeaterThread(void*)
{ {
int ch;
// Wait for temperature sensing to stabilize so we don't // Wait for temperature sensing to stabilize so we don't
// immediately think we overshot the target temperature // immediately think we overshot the target temperature
chThdSleepMilliseconds(1000); chThdSleepMilliseconds(1000);
while (true) while (true)
{ {
// Read sensor state
float heaterEsr = GetSensorInternalResistance();
auto heaterAllowState = GetHeaterAllowed(); auto heaterAllowState = GetHeaterAllowed();
// If we haven't heard from rusEFI, use the internally sensed for (ch = 0; ch < AFR_CHANNELS; ch++) {
// battery voltage instead of voltage over CAN. heater_state &s = state[ch];
float batteryVoltage = heaterAllowState == HeaterAllow::Unknown
? GetInternalBatteryVoltage()
: GetRemoteBatteryVoltage();
// Run the state machine // Read sensor state
state = GetNextState(state, heaterAllowState, batteryVoltage, heaterEsr); float heaterEsr = GetSensorInternalResistance(ch);
float heaterVoltage = GetVoltageForState(state, heaterEsr);
// Limit to 11 volts // If we haven't heard from rusEFI, use the internally sensed
if (heaterVoltage > 11) { // battery voltage instead of voltage over CAN.
heaterVoltage = 11; float batteryVoltage = heaterAllowState == HeaterAllow::Unknown
} ? GetInternalBatteryVoltage(ch)
: GetRemoteBatteryVoltage();
// duty = (V_eff / V_batt) ^ 2 // Run the state machine
float voltageRatio = heaterVoltage / batteryVoltage; s.heaterState = GetNextState(s, heaterAllowState, batteryVoltage, heaterEsr);
float duty = voltageRatio * voltageRatio; float heaterVoltage = GetVoltageForState(s, heaterEsr);
#ifdef HEATER_MAX_DUTY // Limit to 11 volts
if (duty > HEATER_MAX_DUTY) { if (heaterVoltage > 11) {
duty = HEATER_MAX_DUTY; heaterVoltage = 11;
} }
#endif
if (batteryVoltage < 23) // duty = (V_eff / V_batt) ^ 2
{ float voltageRatio = heaterVoltage / batteryVoltage;
// Pipe the output to the heater driver float duty = voltageRatio * voltageRatio;
heaterPwm.SetDuty(duty);
} #ifdef HEATER_MAX_DUTY
else if (duty > HEATER_MAX_DUTY) {
{ duty = HEATER_MAX_DUTY;
// Overvoltage protection - sensor not rated for PWM above 24v }
heaterPwm.SetDuty(0); #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);
}
} }
// Loop at ~20hz // Loop at ~20hz
@ -200,25 +254,28 @@ static void HeaterThread(void*)
void StartHeaterControl() void StartHeaterControl()
{ {
heaterPwm.Start(); heaterPwm.Start(heaterPwmConfig);
heaterPwm.SetDuty(0); heaterPwm.SetDuty(state[0].pwm_ch, 0);
#if (AFR_CHANNELS > 1)
heaterPwm.SetDuty(state[1].pwm_ch, 0);
#endif
chThdCreateStatic(waHeaterThread, sizeof(waHeaterThread), NORMALPRIO + 1, HeaterThread, nullptr); chThdCreateStatic(waHeaterThread, sizeof(waHeaterThread), NORMALPRIO + 1, HeaterThread, nullptr);
} }
bool IsRunningClosedLoop() bool IsRunningClosedLoop(int ch)
{ {
return state == HeaterState::ClosedLoop; return state[ch].heaterState == HeaterState::ClosedLoop;
} }
float GetHeaterDuty() float GetHeaterDuty(int ch)
{ {
return heaterPwm.GetLastDuty(); return heaterPwm.GetLastDuty(state[ch].pwm_ch);
} }
HeaterState GetHeaterState() HeaterState GetHeaterState(int ch)
{ {
return state; return state[ch].heaterState;
} }
const char* describeHeaterState(HeaterState state) const char* describeHeaterState(HeaterState state)

View File

@ -11,7 +11,7 @@ enum class HeaterState
}; };
void StartHeaterControl(); void StartHeaterControl();
bool IsRunningClosedLoop(); bool IsRunningClosedLoop(int ch);
float GetHeaterDuty(); float GetHeaterDuty(int ch);
HeaterState GetHeaterState(); HeaterState GetHeaterState(int ch);
const char* describeHeaterState(HeaterState state); const char* describeHeaterState(HeaterState state);

View File

@ -22,9 +22,9 @@ static float GetPhi(float pumpCurrent)
return gain * pumpCurrent + 0.99559f; return gain * pumpCurrent + 0.99559f;
} }
float GetLambda() float GetLambda(int ch)
{ {
float pumpCurrent = GetPumpNominalCurrent(); float pumpCurrent = GetPumpNominalCurrent(ch);
// Lambda is reciprocal of phi // Lambda is reciprocal of phi
return 1 / GetPhi(pumpCurrent); return 1 / GetPhi(pumpCurrent);

View File

@ -1,3 +1,3 @@
#pragma once #pragma once
float GetLambda(); float GetLambda(int ch);

View File

@ -1,3 +1,4 @@
#include "wideband_config.h"
#include "livedata.h" #include "livedata.h"
#include "lambda_conversion.h" #include "lambda_conversion.h"
@ -9,18 +10,23 @@
#include <rusefi/fragments.h> #include <rusefi/fragments.h>
static livedata_common_s livedata_common; static livedata_common_s livedata_common;
static livedata_afr_s livedata_afr; static livedata_afr_s livedata_afr[AFR_CHANNELS];
void SamplingUpdateLiveData() void SamplingUpdateLiveData()
{ {
livedata_afr.afr = GetLambda(); for (int ch = 0; ch < AFR_CHANNELS; ch++)
livedata_afr.temperature = GetSensorTemperature(); {
livedata_afr.nernstVoltage = GetNernstDc(); volatile struct livedata_afr_s *data = &livedata_afr[ch];
livedata_afr.pumpCurrentTarget = GetPumpCurrent();
livedata_afr.pumpCurrentMeasured = GetPumpNominalCurrent();
livedata_afr.heaterDuty = GetHeaterDuty();
livedata_common.vbatt = GetInternalBatteryVoltage(); data->afr = GetLambda(ch);
data->temperature = GetSensorTemperature(ch);
data->nernstVoltage = GetNernstDc(ch);
data->pumpCurrentTarget = GetPumpCurrent(ch);
data->pumpCurrentMeasured = GetPumpNominalCurrent(ch);
data->heaterDuty = GetHeaterDuty(ch);
}
livedata_common.vbatt = GetInternalBatteryVoltage(0);
} }
const livedata_common_s * getCommonLiveDataStructAddr() const livedata_common_s * getCommonLiveDataStructAddr()
@ -28,14 +34,16 @@ const livedata_common_s * getCommonLiveDataStructAddr()
return &livedata_common; return &livedata_common;
} }
const livedata_afr_s * getAfrLiveDataStructAddr() const struct livedata_afr_s * getAfrLiveDataStructAddr(const int ch)
{ {
return &livedata_afr; if (ch < AFR_CHANNELS)
return &livedata_afr[ch];
return NULL;
} }
static const FragmentEntry fragments[] = { static const FragmentEntry fragments[] = {
getCommonLiveDataStructAddr(), getCommonLiveDataStructAddr(),
getAfrLiveDataStructAddr(), getAfrLiveDataStructAddr(0),
}; };
FragmentList getFragments() { FragmentList getFragments() {

View File

@ -2,6 +2,8 @@
#include <stdint.h> #include <stdint.h>
#include "wideband_config.h"
/* +0 offset */ /* +0 offset */
struct livedata_common_s { struct livedata_common_s {
union { union {
@ -33,4 +35,4 @@ void SamplingUpdateLiveData();
/* access functions */ /* access functions */
const struct livedata_common_s * getCommonLiveDataStructAddr(); const struct livedata_common_s * getCommonLiveDataStructAddr();
const struct livedata_afr_s * getAfrLiveDataStructAddr(); const struct livedata_afr_s * getAfrLiveDataStructAddr(const int ch);

View File

@ -42,7 +42,9 @@ int main() {
while(true) while(true)
{ {
auto fault = GetCurrentFault(); /* TODO: show error for all AFR channels */
/* TODO: show EGT errors */
auto fault = GetCurrentFault(0);
if (fault == Fault::None) if (fault == Fault::None)
{ {
@ -53,7 +55,7 @@ int main() {
palTogglePad(LED_GREEN_PORT, LED_GREEN_PIN); palTogglePad(LED_GREEN_PORT, LED_GREEN_PIN);
// Slow blink if closed loop, fast if not // Slow blink if closed loop, fast if not
chThdSleepMilliseconds(IsRunningClosedLoop() ? 700 : 50); chThdSleepMilliseconds(IsRunningClosedLoop(0) ? 700 : 50);
} }
else else
{ {

View File

@ -7,28 +7,46 @@
#include "ch.h" #include "ch.h"
// Bosch CJ125 is somewhere VERY ROUGHLY like 200-400A/(v*s) integrator gain struct pump_control_state {
static Pid pumpPid(50.0f, 10000.0f, 0, 10, 2); Pid pumpPid;
};
static struct pump_control_state state[AFR_CHANNELS] =
{
{
Pid(50.0f, 10000.0f, 0.0f, 10.0f, 2),
},
#if (AFR_CHANNELS > 1)
{
Pid(50.0f, 10000.0f, 0.0f, 10.0f, 2),
}
#endif
};
static THD_WORKING_AREA(waPumpThread, 256); static THD_WORKING_AREA(waPumpThread, 256);
static void PumpThread(void*) static void PumpThread(void*)
{ {
while(true) while(true)
{ {
// Only actuate pump when running closed loop! for (int ch = 0; ch < AFR_CHANNELS; ch++)
if (IsRunningClosedLoop())
{ {
float nernstVoltage = GetNernstDc(); pump_control_state &s = state[ch];
float result = pumpPid.GetOutput(NERNST_TARGET, nernstVoltage); // Only actuate pump when running closed loop!
if (IsRunningClosedLoop(ch))
{
float nernstVoltage = GetNernstDc(ch);
// result is in mA float result = s.pumpPid.GetOutput(NERNST_TARGET, nernstVoltage);
SetPumpCurrentTarget(result * 1000);
} // result is in mA
else SetPumpCurrentTarget(ch, result * 1000);
{ }
// Otherwise set zero pump current to avoid damaging the sensor else
SetPumpCurrentTarget(0); {
// Otherwise set zero pump current to avoid damaging the sensor
SetPumpCurrentTarget(ch, 0);
}
} }
// Run at 500hz // Run at 500hz

View File

@ -7,28 +7,59 @@
#include "hal.h" #include "hal.h"
// 48MHz / 1024 = 46.8khz PWM // 48MHz / 1024 = 46.8khz PWM
static Pwm pumpDac(PUMP_DAC_PWM_DEVICE, PUMP_DAC_PWM_CHANNEL, 48'000'000, 1024); PWMConfig pumpDacConfig = {
48'000'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
};
static int32_t curIpump; static Pwm pumpDac(PUMP_DAC_PWM_DEVICE);
struct pump_dac_state {
int32_t curIpump;
};
static const uint8_t pumpDacPwmCh[] = {
PUMP_DAC_PWM_CHANNEL_0,
#if (AFR_CHANNELS > 1)
PUMP_DAC_PWM_CHANNEL_1,
#endif
};
static struct pump_dac_state state[AFR_CHANNELS];
void InitPumpDac() void InitPumpDac()
{ {
pumpDac.Start(); pumpDac.Start(pumpDacConfig);
// Set zero current to start - sensor can be damaged if current flowing for (int ch = 0; ch < AFR_CHANNELS; ch++)
// while warming up {
SetPumpCurrentTarget(0); // Set zero current to start - sensor can be damaged if current flowing
// while warming up
SetPumpCurrentTarget(ch, 0);
}
} }
void SetPumpCurrentTarget(int32_t microampere) void SetPumpCurrentTarget(int ch, int32_t microampere)
{ {
// Don't allow pump current when the sensor isn't hot // Don't allow pump current when the sensor isn't hot
if (!IsRunningClosedLoop()) if (!IsRunningClosedLoop(ch))
{ {
microampere = 0; microampere = 0;
} }
curIpump = microampere; state[ch].curIpump = microampere;
// 47 ohm resistor // 47 ohm resistor
// 0.147 gain // 0.147 gain
@ -38,15 +69,15 @@ void SetPumpCurrentTarget(int32_t microampere)
// offset by half vcc // offset by half vcc
volts += HALF_VCC; volts += HALF_VCC;
pumpDac.SetDuty(volts / VCC_VOLTS); pumpDac.SetDuty(pumpDacPwmCh[ch], volts / VCC_VOLTS);
} }
float GetPumpOutputDuty() float GetPumpOutputDuty(int ch)
{ {
return pumpDac.GetLastDuty(); return pumpDac.GetLastDuty(ch);
} }
float GetPumpCurrent() float GetPumpCurrent(int ch)
{ {
return (float)curIpump / 1000.0; return (float)state[ch].curIpump / 1000.0;
} }

View File

@ -3,6 +3,6 @@
#include <cstdint> #include <cstdint>
void InitPumpDac(); void InitPumpDac();
void SetPumpCurrentTarget(int32_t microamperes); void SetPumpCurrentTarget(int ch, int32_t microamperes);
float GetPumpOutputDuty(); float GetPumpOutputDuty(int ch);
float GetPumpCurrent(); float GetPumpCurrent(int ch);

View File

@ -2,17 +2,8 @@
#include <rusefi/math.h> #include <rusefi/math.h>
Pwm::Pwm(PWMDriver& driver, uint8_t channel, uint32_t counterFrequency, uint32_t counterPeriod)
: m_driver(&driver)
, m_channel(channel)
, m_counterFrequency(counterFrequency)
, m_counterPeriod(counterPeriod)
{
}
Pwm::Pwm(PWMDriver& driver) Pwm::Pwm(PWMDriver& driver)
: m_driver(&driver) : m_driver(&driver)
, m_channel(0)
, m_counterFrequency(0) , m_counterFrequency(0)
, m_counterPeriod(0) , m_counterPeriod(0)
{ {
@ -50,17 +41,13 @@ void Pwm::Start(PWMConfig& config)
void Pwm::SetDuty(int channel, float duty) { void Pwm::SetDuty(int channel, float duty) {
auto dutyFloat = clampF(0, duty, 1); auto dutyFloat = clampF(0, duty, 1);
m_dutyFloat = dutyFloat; m_dutyFloat[channel] = dutyFloat;
pwmcnt_t highTime = m_counterPeriod * dutyFloat; pwmcnt_t highTime = m_counterPeriod * dutyFloat;
pwm_lld_enable_channel(m_driver, channel, highTime); pwmEnableChannel(m_driver, channel, highTime);
} }
void Pwm::SetDuty(float duty) { float Pwm::GetLastDuty(int channel)
SetDuty(m_channel, duty);
}
float Pwm::GetLastDuty() const
{ {
return m_dutyFloat; return m_dutyFloat[channel];
} }

View File

@ -10,19 +10,17 @@ struct PWMDriver;
class Pwm class Pwm
{ {
public: public:
Pwm(PWMDriver& driver, uint8_t channel, uint32_t counterFrequency, uint32_t counterPeriod);
Pwm(PWMDriver& driver); Pwm(PWMDriver& driver);
void Start(); void Start();
void Start(PWMConfig& config); void Start(PWMConfig& config);
void SetDuty(float duty);
void SetDuty(int channel, float duty); void SetDuty(int channel, float duty);
float GetLastDuty() const; float GetLastDuty(int channel);
private: private:
PWMDriver* const m_driver; PWMDriver* const m_driver;
const uint8_t m_channel; //const uint8_t m_channel;
/* const */ uint32_t m_counterFrequency; /* const */ uint32_t m_counterFrequency;
/* const */ uint16_t m_counterPeriod; /* const */ uint16_t m_counterPeriod;
float m_dutyFloat; float m_dutyFloat[4];
}; };

View File

@ -12,10 +12,14 @@
#include <rusefi/interpolation.h> #include <rusefi/interpolation.h>
// Stored results // Stored results
static float nernstAc = 0; struct measure_results {
static float nernstDc = 0; float nernstAc;
static float pumpCurrentSenseVoltage = 0; float nernstDc;
static float internalBatteryVoltage = 0; float pumpCurrentSenseVoltage;
float internalBatteryVoltage;
};
static struct measure_results results[AFR_CHANNELS];
// Last point is approximated by the greatest measurable sensor resistance // Last point is approximated by the greatest measurable sensor resistance
static const float lsu49TempBins[] = { 80, 150, 200, 250, 300, 350, 400, 450, 550, 650, 800, 1000, 1200, 2500, 5000 }; static const float lsu49TempBins[] = { 80, 150, 200, 250, 300, 350, 400, 450, 550, 650, 800, 1000, 1200, 2500, 5000 };
@ -30,51 +34,51 @@ static THD_WORKING_AREA(waSamplingThread, 256);
static void SamplingThread(void*) static void SamplingThread(void*)
{ {
float r_2 = 0; float r_2[AFR_CHANNELS] = {0};
float r_3 = 0; float r_3[AFR_CHANNELS] = {0};
/* GD32: Insert 20us delay after ADC enable */ /* GD32: Insert 20us delay after ADC enable */
chThdSleepMilliseconds(1); chThdSleepMilliseconds(1);
while(true) while(true)
{ {
/* TODO: run for all channels */
int ch = 0;
auto result = AnalogSample(); auto result = AnalogSample();
// Toggle the pin after sampling so that any switching noise occurs while we're doing our math instead of when sampling // Toggle the pin after sampling so that any switching noise occurs while we're doing our math instead of when sampling
palTogglePad(NERNST_ESR_DRIVER_PORT, NERNST_ESR_DRIVER_PIN); palTogglePad(NERNST_ESR_DRIVER_PORT, NERNST_ESR_DRIVER_PIN);
float r_1 = result.ch[ch].NernstVoltage; for (int ch = 0; ch < AFR_CHANNELS; ch++) {
measure_results &res = results[ch];
float r_1 = result.ch[ch].NernstVoltage;
// r2_opposite_phase estimates where the previous sample would be had we not been toggling // r2_opposite_phase estimates where the previous sample would be had we not been toggling
// AKA the absolute value of the difference between r2_opposite_phase and r2 is the amplitude // AKA the absolute value of the difference between r2_opposite_phase and r2 is the amplitude
// of the AC component on the nernst voltage. We have to pull this trick so as to use the past 3 // of the AC component on the nernst voltage. We have to pull this trick so as to use the past 3
// samples to cancel out any slope in the DC (aka actual nernst cell output) from the AC measurement // samples to cancel out any slope in the DC (aka actual nernst cell output) from the AC measurement
// See firmware/sampling.png for a drawing of what's going on here // See firmware/sampling.png for a drawing of what's going on here
float r2_opposite_phase = (r_1 + r_3) / 2; float r2_opposite_phase = (r_1 + r_3[ch]) / 2;
// Compute AC (difference) and DC (average) components // Compute AC (difference) and DC (average) components
float nernstAcLocal = f_abs(r2_opposite_phase - r_2); float nernstAcLocal = f_abs(r2_opposite_phase - r_2[ch]);
nernstDc = (r2_opposite_phase + r_2) / 2; res.nernstDc = (r2_opposite_phase + r_2[ch]) / 2;
nernstAc = res.nernstAc =
(1 - ESR_SENSE_ALPHA) * nernstAc + (1 - ESR_SENSE_ALPHA) * res.nernstAc +
ESR_SENSE_ALPHA * nernstAcLocal; ESR_SENSE_ALPHA * nernstAcLocal;
// Exponential moving average (aka first order lpf) // Exponential moving average (aka first order lpf)
pumpCurrentSenseVoltage = res.pumpCurrentSenseVoltage =
(1 - PUMP_FILTER_ALPHA) * pumpCurrentSenseVoltage + (1 - PUMP_FILTER_ALPHA) * res.pumpCurrentSenseVoltage +
PUMP_FILTER_ALPHA * (result.ch[ch].PumpCurrentVoltage - result.VirtualGroundVoltageInt); PUMP_FILTER_ALPHA * (result.ch[ch].PumpCurrentVoltage - result.VirtualGroundVoltageInt);
#ifdef BATTERY_INPUT_DIVIDER #ifdef BATTERY_INPUT_DIVIDER
internalBatteryVoltage = result.ch[ch].BatteryVoltage; res.internalBatteryVoltage = result.ch[ch].BatteryVoltage;
#endif #endif
// Shift history over by one // Shift history over by one
r_3 = r_2; r_3[ch] = r_2[ch];
r_2 = r_1; r_2[ch] = r_1;
}
#if defined(TS_ENABLED) #if defined(TS_ENABLED)
/* tunerstudio */ /* tunerstudio */
@ -89,24 +93,24 @@ void StartSampling()
chThdCreateStatic(waSamplingThread, sizeof(waSamplingThread), NORMALPRIO + 5, SamplingThread, nullptr); chThdCreateStatic(waSamplingThread, sizeof(waSamplingThread), NORMALPRIO + 5, SamplingThread, nullptr);
} }
float GetNernstAc() float GetNernstAc(int ch)
{ {
return nernstAc; return results[ch].nernstAc;
} }
float GetSensorInternalResistance() float GetSensorInternalResistance(int ch)
{ {
// Sensor is the lowside of a divider, top side is 22k, and 3.3v AC pk-pk is injected // Sensor is the lowside of a divider, top side is 22k, and 3.3v AC pk-pk is injected
float totalEsr = ESR_SUPPLY_R / (VCC_VOLTS / GetNernstAc() - 1); float totalEsr = ESR_SUPPLY_R / (VCC_VOLTS / GetNernstAc(ch) - 1);
// There is a resistor between the opamp and Vm sensor pin. Remove the effect of that // There is a resistor between the opamp and Vm sensor pin. Remove the effect of that
// resistor so that the remainder is only the ESR of the sensor itself // resistor so that the remainder is only the ESR of the sensor itself
return totalEsr - VM_RESISTOR_VALUE; return totalEsr - VM_RESISTOR_VALUE;
} }
float GetSensorTemperature() float GetSensorTemperature(int ch)
{ {
float esr = GetSensorInternalResistance(); float esr = GetSensorInternalResistance(ch);
if (esr > 5000) if (esr > 5000)
{ {
@ -116,21 +120,24 @@ float GetSensorTemperature()
return interpolate2d(esr, lsu49TempBins, lsu49TempValues); return interpolate2d(esr, lsu49TempBins, lsu49TempValues);
} }
float GetNernstDc() float GetNernstDc(int ch)
{ {
return nernstDc; return results[ch].nernstDc;
} }
float GetPumpNominalCurrent() float GetPumpNominalCurrent(int ch)
{ {
// Gain is 10x, then a 61.9 ohm resistor // Gain is 10x, then a 61.9 ohm resistor
// Effective resistance with the gain is 619 ohms // Effective resistance with the gain is 619 ohms
// 1000 is to convert to milliamperes // 1000 is to convert to milliamperes
constexpr float ratio = -1000 / (PUMP_CURRENT_SENSE_GAIN * LSU_SENSE_R); constexpr float ratio = -1000 / (PUMP_CURRENT_SENSE_GAIN * LSU_SENSE_R);
return pumpCurrentSenseVoltage * ratio; return results[ch].pumpCurrentSenseVoltage * ratio;
} }
float GetInternalBatteryVoltage() float GetInternalBatteryVoltage(int ch)
{ {
return internalBatteryVoltage; // Dual HW can measure heater voltage for each channel
// by measuring voltage on Heater- while FET is off
// TODO: rename function?
return results[ch].internalBatteryVoltage;
} }

View File

@ -2,11 +2,9 @@
void StartSampling(); void StartSampling();
float GetNernstAc(); float GetNernstAc(int ch);
float GetSensorInternalResistance(); float GetSensorInternalResistance(int ch);
float GetSensorTemperature(); float GetSensorTemperature(int ch);
float GetNernstDc(); float GetNernstDc(int ch);
float GetPumpNominalCurrent(int ch);
float GetPumpNominalCurrent(); float GetInternalBatteryVoltage(int ch);
float GetInternalBatteryVoltage();

View File

@ -33,33 +33,41 @@ static void UartThread(void*)
while(true) while(true)
{ {
float lambda = GetLambda(); int ch;
int lambdaIntPart = lambda;
int lambdaThousandths = (lambda - lambdaIntPart) * 1000;
int batteryVoltageMv = GetInternalBatteryVoltage() * 1000;
int duty = GetHeaterDuty() * 100;
size_t writeCount = chsnprintf(printBuffer, 200, for (ch = 0; ch < AFR_CHANNELS; ch++) {
"%d.%03d\tAC %d mV\tR: %d\tT: %d\tIpump: %d\tVbat: %d\theater: %s (%d)\tfault: %s\r\n", float lambda = GetLambda(ch);
lambdaIntPart, lambdaThousandths, int lambdaIntPart = lambda;
(int)(GetNernstAc() * 1000.0), int lambdaThousandths = (lambda - lambdaIntPart) * 1000;
(int)GetSensorInternalResistance(), int batteryVoltageMv = GetInternalBatteryVoltage(ch) * 1000;
(int)GetSensorTemperature(), int duty = GetHeaterDuty(ch) * 100;
(int)(GetPumpNominalCurrent() * 1000),
batteryVoltageMv, size_t writeCount = chsnprintf(printBuffer, 200,
describeHeaterState(GetHeaterState()), duty, "[AFR%d]: %d.%03d DC %4d mV AC %4d mV Rint: %5d T: %4d C Ipump: %6d uA Vheater: %5d heater: %s (%d)\tfault: %s\r\n",
describeFault(GetCurrentFault())); ch,
chnWrite(&SD1, (const uint8_t *)printBuffer, writeCount); lambdaIntPart, lambdaThousandths,
chThdSleepMilliseconds(50); (int)(GetNernstDc(ch) * 1000.0),
(int)(GetNernstAc(ch) * 1000.0),
(int)GetSensorInternalResistance(ch),
(int)GetSensorTemperature(ch),
(int)(GetPumpNominalCurrent(ch) * 1000),
batteryVoltageMv,
describeHeaterState(GetHeaterState(ch)), duty,
describeFault(GetCurrentFault(ch)));
chnWrite(&SD1, (const uint8_t *)printBuffer, writeCount);
}
#if HAL_USE_SPI #if HAL_USE_SPI
writeCount = chsnprintf(printBuffer, 200, for (ch = 0; ch < EGT_CHANNELS; ch++) {
"EGT: %d C (int %d C)\r\n", size_t writeCount = chsnprintf(printBuffer, 200,
(int)getEgtDrivers()[0].temperature, "EGT[%d]: %d C (int %d C)\r\n",
(int)getEgtDrivers()[0].cold_joint_temperature); (int)getEgtDrivers()[ch].temperature,
chnWrite(&SD1, (const uint8_t *)printBuffer, writeCount); (int)getEgtDrivers()[ch].cold_joint_temperature);
chThdSleepMilliseconds(50); chnWrite(&SD1, (const uint8_t *)printBuffer, writeCount);
}
#endif /* HAL_USE_SPI */ #endif /* HAL_USE_SPI */
chThdSleepMilliseconds(100);
} }
} }