From 87fc14ff203ec54851d7e623b42f713b980c25e4 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 21:17:05 -0700 Subject: [PATCH 01/47] implement stm32 adc provider --- firmware/hw_layer/ports/stm32/port_mpu_util.h | 6 +++++ .../hw_layer/ports/stm32/stm32_adc_v2.cpp | 20 ++++++++++++--- .../hw_layer/ports/stm32/stm32_adc_v4.cpp | 25 ++++++++++++++++--- .../hw_layer/ports/stm32/stm32_common.cpp | 14 +++++++++++ 4 files changed, 58 insertions(+), 7 deletions(-) diff --git a/firmware/hw_layer/ports/stm32/port_mpu_util.h b/firmware/hw_layer/ports/stm32/port_mpu_util.h index 8b678f7269..3b900a2c9b 100644 --- a/firmware/hw_layer/ports/stm32/port_mpu_util.h +++ b/firmware/hw_layer/ports/stm32/port_mpu_util.h @@ -8,6 +8,7 @@ #pragma once +#include "adc_provider.h" #include "device_mpu_util.h" #define MCU_SERIAL_NUMBER_BYTES 12 @@ -86,3 +87,8 @@ void boardPrepareForStop(); // Called just before the MCU is put in standby mode void boardPrepareForStandby(); #endif + +struct Stm32AdcProviderBase : public AdcProvider { + bool enable(const char* name, size_t idx) override; + void disable(size_t idx) override; +}; diff --git a/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp b/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp index 0cd40ccd9e..70c8912453 100644 --- a/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp @@ -206,9 +206,23 @@ bool readSlowAnalogInputs() { return result; } -adcsample_t getSlowAdcSample(adc_channel_e channel) { - return convertedAdcSamples[channel - EFI_ADC_0]; -} +struct Stm32AdcV2Provider final : public Stm32AdcProviderBase { + Stm32AdcV2Provider() { + registerAdcProvider(*this, /*firstIndex*/ 0, /*size*/ 16); + } + + const char* name() const override { + return "STM32 ADC v2"; + } + + float get(size_t idx) const override { + auto sample = convertedAdcSamples[idx]; + + return engineConfiguration->adcVcc / ADC_MAX_VALUE * sample; + } +}; + +static Stm32AdcV2Provider provider; #if EFI_USE_FAST_ADC diff --git a/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp b/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp index a096bdfd34..167cc6f917 100644 --- a/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp @@ -233,7 +233,7 @@ bool readSlowAnalogInputs() { return true; } -adcsample_t getSlowAdcSample(adc_channel_e channel) { +static adcsample_t getSample(size_t index) { // Maps ADC channel to index in the interleaved 16-bit sample buffer. // In dual mode, samples are interleaved: ADC1[0], ADC2[0], ADC1[1], ADC2[1], ... // ADC1 channels (seq 0-9) map to even indices, ADC2 channels (seq 0-9) map to odd indices. @@ -246,11 +246,28 @@ adcsample_t getSlowAdcSample(adc_channel_e channel) { 17, 19 // ADC2: PF13/14 (seq 8-9 -> indices 17,19) }; - auto channelIndex = channel - EFI_ADC_0; - auto bufferPosition = descramble[channelIndex]; + auto bufferPosition = descramble[index]; return sampleBuffer.samples16[bufferPosition]; } +struct Stm32AdcV4Provider final : public Stm32AdcProviderBase { + Stm32AdcV4Provider() { + registerAdcProvider(*this, /*firstIndex*/ 0, /*size*/ 20); + } + + const char* name() const override { + return "STM32 ADC v4"; + } + + float get(size_t idx) const override { + auto sample = getSample(idx); + + return engineConfiguration->adcVcc / ADC_MAX_VALUE * sample; + } +}; + +static Stm32AdcV4Provider provider; + static constexpr FastAdcToken invalidToken = (FastAdcToken)(-1); FastAdcToken enableFastAdcChannel(const char*, adc_channel_e channel) { @@ -267,7 +284,7 @@ adcsample_t getFastAdc(FastAdcToken token) { return 0; } - return getSlowAdcSample(static_cast(token)); + return getSample(token + EFI_ADC_0); } #ifdef EFI_SOFTWARE_KNOCK diff --git a/firmware/hw_layer/ports/stm32/stm32_common.cpp b/firmware/hw_layer/ports/stm32/stm32_common.cpp index 75ec835135..4e03d2fce6 100644 --- a/firmware/hw_layer/ports/stm32/stm32_common.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_common.cpp @@ -836,4 +836,18 @@ void assertInterruptPriority(const char* func, uint8_t expectedPrio) { } } +bool Stm32AdcProviderBase::enable(const char* name, size_t idx) { + brain_pin_e pin = getAdcChannelBrainPin(name, idx + EFI_ADC_0); + if (pin != Gpio::Invalid) { + efiSetPadMode(name, pin, PAL_MODE_INPUT_ANALOG); + return true; + } + + return false; +} + +void Stm32AdcProviderBase::disable(size_t idx) { + efiSetPadUnused(getAdcChannelBrainPin("adc unsubscribe", idx + EFI_ADC_0)); +} + #endif // EFI_PROD_CODE From 795ed4949173e23ad21ba999b5b37a7c06a7526d Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 21:17:41 -0700 Subject: [PATCH 02/47] kill legacy getSlowAdcSample --- firmware/hw_layer/ports/mpu_util.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/firmware/hw_layer/ports/mpu_util.h b/firmware/hw_layer/ports/mpu_util.h index 702d6bd6b3..3421f84b16 100644 --- a/firmware/hw_layer/ports/mpu_util.h +++ b/firmware/hw_layer/ports/mpu_util.h @@ -25,8 +25,6 @@ void portInitAdc(); float getMcuTemperature(); // Convert all slow ADC inputs. Returns true if the conversion succeeded, false if a failure occured. bool readSlowAnalogInputs(); -// Read the sampled value of a slow channel -adcsample_t getSlowAdcSample(adc_channel_e channel); #endif // CAN bus From 6e89b34c2b3110bea2f241a6fbc73debd533358d Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 21:50:17 -0700 Subject: [PATCH 03/47] dead code --- firmware/hw_layer/adc/adc_external.h | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 firmware/hw_layer/adc/adc_external.h diff --git a/firmware/hw_layer/adc/adc_external.h b/firmware/hw_layer/adc/adc_external.h deleted file mode 100644 index 75ad8e076c..0000000000 --- a/firmware/hw_layer/adc/adc_external.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * @file adc_external.h - * - * external ADC is probably broken, at least there is a major mess - * - * @date Aug 18, 2013 - * @author Andrey Belomutskiy, (c) 2012-2020 - */ - -#pragma once - -#include "mcp3208.h" - -#define getAdcValue(channel) getMcp3208adc(channel) -#define adcToVoltsDivided(adc) (5.0f / 4095 * (adc)) -#define getVoltageDivided(msg, channel) \ - (isAdcChannelValid(channel) ? adcToVoltsDivided(getAdcValue(msg, channel), channel) : 66.66) From cf20585a6046465a9c37bd3b3e9fb73d60dc44e4 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 21:54:01 -0700 Subject: [PATCH 04/47] dead stuff --- .../ports/stm32/stm32f4/efifeatures.h | 2 -- simulator/simulator/adc_inputs.h | 10 ------ simulator/simulator/boards.cpp | 15 --------- simulator/simulator/boards.h | 9 ------ unit_tests/adc_inputs.h | 31 ------------------- unit_tests/boards.cpp | 12 ------- unit_tests/boards.h | 3 -- 7 files changed, 82 deletions(-) delete mode 100644 simulator/simulator/adc_inputs.h delete mode 100644 unit_tests/adc_inputs.h diff --git a/firmware/hw_layer/ports/stm32/stm32f4/efifeatures.h b/firmware/hw_layer/ports/stm32/stm32f4/efifeatures.h index 8a1ac888e8..fdd75d4a82 100644 --- a/firmware/hw_layer/ports/stm32/stm32f4/efifeatures.h +++ b/firmware/hw_layer/ports/stm32/stm32f4/efifeatures.h @@ -235,8 +235,6 @@ */ #define EFI_CUSTOM_PANIC_METHOD TRUE -#define ADC_CHANNEL_VREF ADC_CHANNEL_IN14 - #ifndef LED_CRITICAL_ERROR_BRAIN_PIN #define LED_CRITICAL_ERROR_BRAIN_PIN Gpio::D14 #endif diff --git a/simulator/simulator/adc_inputs.h b/simulator/simulator/adc_inputs.h deleted file mode 100644 index fe39fefe5e..0000000000 --- a/simulator/simulator/adc_inputs.h +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @file adc_inputs.h - * - * @date Dec 7, 2013 - * @author Andrey Belomutskiy, (c) 2012-2020 - */ - -#pragma once - -#include "adc_math.h" diff --git a/simulator/simulator/boards.cpp b/simulator/simulator/boards.cpp index 42bb1c683b..256543831b 100644 --- a/simulator/simulator/boards.cpp +++ b/simulator/simulator/boards.cpp @@ -9,23 +9,8 @@ #include "boards.h" #include "engine_sniffer.h" -#include "adc_math.h" #include "backup_ram.h" -int getAdcValue(const char* msg, int hwChannel) { - return 0; -} - -// voltage in MCU universe, from zero to VDD -float getVoltage(const char* msg, adc_channel_e hwChannel) { - return adcToVolts(getAdcValue(msg, hwChannel)); -} - -// Board voltage, with divider coefficient accounted for -float getVoltageDivided(const char* msg, adc_channel_e hwChannel) { - return getVoltage(msg, hwChannel) * engineConfiguration->analogInputDividerCoefficient; -} - BackupSramData* getBackupSram() { static BackupSramData data; return &data; diff --git a/simulator/simulator/boards.h b/simulator/simulator/boards.h index c8d20236f4..d8b62b5c47 100644 --- a/simulator/simulator/boards.h +++ b/simulator/simulator/boards.h @@ -1,15 +1,6 @@ #pragma once -#define ADC_LOGIC_TPS 0 -#define ADC_LOGIC_AFR 0 -#define ADC_LOGIC_MAF 0 -#define ADC_LOGIC_MAP 0 -#define ADC_CHANNEL_VREF 0 -#define ADC_CHANNEL_VBATT 0 -#define ADC_LOGIC_INTAKE_AIR 0 -#define ADC_LOGIC_COOLANT 0 - int getAdcValue(const char* msg, int channel); #define waitForSlowAdc() \ {} diff --git a/unit_tests/adc_inputs.h b/unit_tests/adc_inputs.h deleted file mode 100644 index 7a3a83455e..0000000000 --- a/unit_tests/adc_inputs.h +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @file adc_inputs.h - * - * @date Dec 7, 2013 - * @author Andrey Belomutskiy, (c) 2012-2020 - */ - -#pragma once - -#include "rusefi_hw_enums.h" - -inline bool isAdcChannelValid(adc_channel_e hwChannel) { - if (hwChannel <= EFI_ADC_NONE) { - return false; - } else if (hwChannel >= EFI_ADC_LAST_CHANNEL) { - /* this should not happen! - * if we have enum out of range somewhere in settings - * that means something goes terribly wrong - * TODO: should we say something? - */ - return false; - } else { - return true; - } -} - -#define adcToVoltsDivided(adc) (adcToVolts(adc) * engineConfiguration->analogInputDividerCoefficient) -#define GPT_FREQ_FAST 100000 -#define GPT_PERIOD_FAST 10 - -#include "boards.h" diff --git a/unit_tests/boards.cpp b/unit_tests/boards.cpp index 250db657c6..df0ea28a70 100644 --- a/unit_tests/boards.cpp +++ b/unit_tests/boards.cpp @@ -10,18 +10,6 @@ #include "boards.h" #include "backup_ram.h" -float getVoltageDivided(const char* msg, adc_channel_e hwChannel) { - return 0; -} - -float getVoltage(const char* msg, adc_channel_e hwChannel) { - return 0; -} - -int getAdcValue(const char* msg, adc_channel_e hwChannel) { - return 0; -} - BackupSramData* getBackupSram() { static BackupSramData data; return &data; diff --git a/unit_tests/boards.h b/unit_tests/boards.h index c11cd2134e..a5585128c1 100644 --- a/unit_tests/boards.h +++ b/unit_tests/boards.h @@ -8,9 +8,6 @@ #pragma once #include "engine_configuration.h" -#include "adc_math.h" - -#define ADC_CHANNEL_VREF 0 int getAdcValue(const char* msg, adc_channel_e channel); From f7bbb232d3e82e565baac9abfbfc59f20685bfa9 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 21:54:11 -0700 Subject: [PATCH 05/47] operators for adc_channel_e --- firmware/util/efilib.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/firmware/util/efilib.h b/firmware/util/efilib.h index 0b11c08c0c..850db71165 100644 --- a/firmware/util/efilib.h +++ b/firmware/util/efilib.h @@ -139,6 +139,23 @@ inline constexpr Gpio operator+(size_t a, Gpio b) { return b + a; } +inline constexpr size_t operator-(adc_channel_e a, adc_channel_e b) { + return (size_t)a - (size_t)b; +} + +inline constexpr adc_channel_e operator-(adc_channel_e a, size_t b) { + return (adc_channel_e)((size_t)a - b); +} + +inline constexpr adc_channel_e operator+(adc_channel_e a, size_t b) { + return (adc_channel_e)((size_t)a + b); +} + +inline constexpr adc_channel_e operator+(size_t a, adc_channel_e b) { + // addition is commutative, just use the other operator + return b + a; +} + namespace efi { template struct remove_reference { From c6d1f2550d237118207a972bec860e075ffb8aa0 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 21:54:44 -0700 Subject: [PATCH 06/47] disable ProtectedGpio for now --- .../hw_layer/drivers/gpio/protected_gpio.cpp | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/firmware/hw_layer/drivers/gpio/protected_gpio.cpp b/firmware/hw_layer/drivers/gpio/protected_gpio.cpp index ceb7058e4d..61327ab75b 100644 --- a/firmware/hw_layer/drivers/gpio/protected_gpio.cpp +++ b/firmware/hw_layer/drivers/gpio/protected_gpio.cpp @@ -52,17 +52,18 @@ int ProtectedGpio::get() const { } void ProtectedGpio::check(efitick_t /*nowNt*/) { - if (!m_config) { - return; - } - - float senseVolts = getVoltage("protected", m_config->SenseChannel); - float amps = senseVolts * m_config->AmpsPerVolt; - - // TODO: smarter state machine - if (amps > m_config->MaximumAllowedCurrent) { - m_output.setValue(false); - } + // if (!m_config) { + // return; + // } + + // TODO: refactor to use a sensor/adc subscription + // float senseVolts = getVoltage("protected", m_config->SenseChannel); + // float amps = senseVolts * m_config->AmpsPerVolt; + + // // TODO: smarter state machine + // if (amps > m_config->MaximumAllowedCurrent) { + // m_output.setValue(false); + // } } brain_pin_diag_e ProtectedGpio::getDiag() const { From 5906c8938c85ca6b4e2c3ab0f503f968f049da47 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 21:57:14 -0700 Subject: [PATCH 07/47] remove adcToVoltsDivided --- firmware/hw_layer/hardware.cpp | 5 +++-- firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp | 4 ++-- firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/firmware/hw_layer/hardware.cpp b/firmware/hw_layer/hardware.cpp index dd2d377ee3..327404d5c1 100644 --- a/firmware/hw_layer/hardware.cpp +++ b/firmware/hw_layer/hardware.cpp @@ -92,8 +92,9 @@ void onFastAdcComplete(adcsample_t*) { #ifdef MODULE_MAP_AVERAGING engine->module()->submitSample( - adcToVoltsDivided(getFastAdc(fastMapSampleIndex), engineConfiguration->map.sensor.hwChannel), - adcToVoltsDivided(getFastAdc(fastMapSampleIndex2), engineConfiguration->map2HwChannel)); + getFastAdc(fastMapSampleIndex) * + getAnalogInputDividerCoefficient(engineConfiguration->map.sensor.hwChannel), + getFastAdc(fastMapSampleIndex2) * getAnalogInputDividerCoefficient(engineConfiguration->map2HwChannel)); #endif // MODULE_MAP_AVERAGING } #endif /* HAL_USE_ADC */ diff --git a/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp b/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp index 70c8912453..a24801bd3e 100644 --- a/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp @@ -297,7 +297,7 @@ FastAdcToken enableFastAdcChannel(const char* msg, adc_channel_e channel) { static NO_CACHE adcsample_t fastAdcSampleBuf[ADC_BUF_DEPTH_FAST * ADC_MAX_CHANNELS_COUNT]; -adcsample_t getFastAdc(FastAdcToken token) { +float getFastAdc(FastAdcToken token) { if (token == invalidToken) { return 0; } @@ -307,7 +307,7 @@ adcsample_t getFastAdc(FastAdcToken token) { return 0; } - return fastAdcSampleBuf[token]; + return engineConfiguration->adcVcc / ADC_MAX_VALUE * fastAdcSampleBuf[token]; } auto& ADC_FAST_DEVICE = ADCD2; diff --git a/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp b/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp index 167cc6f917..31dd2b1a4d 100644 --- a/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp @@ -279,12 +279,12 @@ FastAdcToken enableFastAdcChannel(const char*, adc_channel_e channel) { return static_cast(channel); } -adcsample_t getFastAdc(FastAdcToken token) { +float getFastAdc(FastAdcToken token) { if (token == invalidToken) { return 0; } - return getSample(token + EFI_ADC_0); + return engineConfiguration->adcVcc / ADC_MAX_VALUE * getSample(token + EFI_ADC_0); } #ifdef EFI_SOFTWARE_KNOCK From 9bd1bd285cdc8b67a1e48fd094b4cc627e7e03ae Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 22:03:25 -0700 Subject: [PATCH 08/47] dead dead dead --- firmware/hw_layer/adc/adc_inputs.h | 10 ++-------- firmware/hw_layer/algo/adc_math.h | 27 --------------------------- simulator/simulator/boards.h | 1 - unit_tests/boards.cpp | 1 - unit_tests/boards.h | 22 ---------------------- unit_tests/mocks.h | 10 ++++++++++ 6 files changed, 12 insertions(+), 59 deletions(-) delete mode 100644 firmware/hw_layer/algo/adc_math.h delete mode 100644 unit_tests/boards.h diff --git a/firmware/hw_layer/adc/adc_inputs.h b/firmware/hw_layer/adc/adc_inputs.h index b756d3c61e..8a33643b06 100644 --- a/firmware/hw_layer/adc/adc_inputs.h +++ b/firmware/hw_layer/adc/adc_inputs.h @@ -9,7 +9,6 @@ #pragma once #include "global.h" -#include "adc_math.h" float getAnalogInputDividerCoefficient(adc_channel_e); @@ -50,12 +49,7 @@ brain_pin_e getAdcChannelBrainPin(const char* msg, adc_channel_e hwChannel); // wait until slow ADC readings are valid void waitForSlowAdc(); -int getAdcHardwareIndexByInternalIndex(int index); - -int getSlowAdcValue(const char* msg, adc_channel_e index); -float getMCUInternalTemperature(void); - -#define adcToVoltsDivided(adc, hwChannel) (adcToVolts(adc) * getAnalogInputDividerCoefficient(hwChannel)) +float getMCUInternalTemperature(); #if !defined(GPT_FREQ_FAST) || !defined(GPT_PERIOD_FAST) /** @@ -79,7 +73,7 @@ void onFastAdcComplete(adcsample_t* samples); using FastAdcToken = size_t; FastAdcToken enableFastAdcChannel(const char* msg, adc_channel_e channel); -adcsample_t getFastAdc(FastAdcToken token); +float getFastAdc(FastAdcToken token); const ADCConversionGroup* getKnockConversionGroup(uint8_t channelIdx); void onKnockSamplingComplete(); #endif // HAL_USE_ADC diff --git a/firmware/hw_layer/algo/adc_math.h b/firmware/hw_layer/algo/adc_math.h deleted file mode 100644 index 1dedd55957..0000000000 --- a/firmware/hw_layer/algo/adc_math.h +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @file adc_math.h - * - * todo: all this looks to be about internal ADC, merge this file with adc_inputs.h? - * these macro are also used in simulator so maybe not really merging with adc_inputs.h until simulator get's it's own - * implementation? - * - * @date Mar 18, 2014 - * @author Andrey Belomutskiy, (c) 2012-2020 - */ - -#pragma once - -#if EFI_PROD_CODE -#include "port_mpu_util.h" -#include "rusefi_hw_enums.h" -#else // not EFI_PROD_CODE -#define ADC_MAX_VALUE 4095 -#endif - -#define adcToVolts(adc) ((engineConfiguration->adcVcc) / ADC_MAX_VALUE * (adc)) - -#define voltsToAdc(volts) ((volts) * (ADC_MAX_VALUE / (engineConfiguration->adcVcc))) - -float getVoltage(const char* msg, adc_channel_e channel); - -float getVoltageDivided(const char* msg, adc_channel_e channel); diff --git a/simulator/simulator/boards.h b/simulator/simulator/boards.h index d8b62b5c47..ac7a21f311 100644 --- a/simulator/simulator/boards.h +++ b/simulator/simulator/boards.h @@ -1,7 +1,6 @@ #pragma once -int getAdcValue(const char* msg, int channel); #define waitForSlowAdc() \ {} diff --git a/unit_tests/boards.cpp b/unit_tests/boards.cpp index df0ea28a70..a8d19ff5c3 100644 --- a/unit_tests/boards.cpp +++ b/unit_tests/boards.cpp @@ -7,7 +7,6 @@ #include "pch.h" -#include "boards.h" #include "backup_ram.h" BackupSramData* getBackupSram() { diff --git a/unit_tests/boards.h b/unit_tests/boards.h deleted file mode 100644 index a5585128c1..0000000000 --- a/unit_tests/boards.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * @file boards.h - * - * @date Nov 15, 2013 - * @author Andrey Belomutskiy, (c) 2012-2020 - */ - -#pragma once - -#include "engine_configuration.h" - -int getAdcValue(const char* msg, adc_channel_e channel); - -// via: ChibiOS/os/common/ports/templates/chcore.h -/** - * @brief Interrupt saved context. - * @details This structure represents the stack frame saved during a - * preemption-capable interrupt handler. - * @note R2 and R13 are not saved because those are assumed to be immutable - * during the system life cycle. - */ -struct port_extctx {}; diff --git a/unit_tests/mocks.h b/unit_tests/mocks.h index d7415e73dc..7af102fbe0 100644 --- a/unit_tests/mocks.h +++ b/unit_tests/mocks.h @@ -1,5 +1,15 @@ #pragma once +// via: ChibiOS/os/common/ports/templates/chcore.h +/** + * @brief Interrupt saved context. + * @details This structure represents the stack frame saved during a + * preemption-capable interrupt handler. + * @note R2 and R13 are not saved because those are assumed to be immutable + * during the system life cycle. + */ +struct port_extctx {}; + #include "electronic_throttle.h" #include "dc_motor.h" #include "table_helper.h" From 2b11ef162eb6be7f1a45201abea20aec2dfa2d1d Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 22:07:40 -0700 Subject: [PATCH 09/47] more dead!? --- firmware/hw_layer/adc/AdcConfiguration.h | 24 ------------------- firmware/hw_layer/hardware.cpp | 1 - .../hw_layer/ports/stm32/stm32_adc_v2.cpp | 19 +++++++++------ .../hw_layer/ports/stm32/stm32_adc_v4.cpp | 2 -- 4 files changed, 12 insertions(+), 34 deletions(-) delete mode 100644 firmware/hw_layer/adc/AdcConfiguration.h diff --git a/firmware/hw_layer/adc/AdcConfiguration.h b/firmware/hw_layer/adc/AdcConfiguration.h deleted file mode 100644 index f3a0703d61..0000000000 --- a/firmware/hw_layer/adc/AdcConfiguration.h +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @file AdcConfiguration.h - * - * @date May 3, 2014 - * @author Andrey Belomutskiy, (c) 2012-2020 - */ - -#pragma once - -#if HAL_USE_ADC - -#ifndef ADC_MAX_CHANNELS_COUNT -#define ADC_MAX_CHANNELS_COUNT 16 -#endif /* ADC_MAX_CHANNELS_COUNT */ - -#ifndef SLOW_ADC_CHANNEL_COUNT -#ifdef ADC_MUX_PIN -#define SLOW_ADC_CHANNEL_COUNT 32 -#else // not ADC_MUX_PIN -#define SLOW_ADC_CHANNEL_COUNT 16 -#endif // def ADC_MUX_PIN -#endif // SLOW_ADC_CHANNEL_COUNT - -#endif /* HAL_USE_ADC */ diff --git a/firmware/hw_layer/hardware.cpp b/firmware/hw_layer/hardware.cpp index 327404d5c1..cb5205ea4c 100644 --- a/firmware/hw_layer/hardware.cpp +++ b/firmware/hw_layer/hardware.cpp @@ -25,7 +25,6 @@ #include "mpu_util.h" #endif /* EFI_PROD_CODE */ -#include "AdcConfiguration.h" #include "idle_hardware.h" #include "trigger_central.h" #include "gitversion.h" diff --git a/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp b/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp index a24801bd3e..8f5acede02 100644 --- a/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp @@ -8,8 +8,6 @@ #include "pch.h" -#include "AdcConfiguration.h" - #if HAL_USE_ADC /* Depth of the conversion buffer, channels are sampled X times each.*/ @@ -189,6 +187,14 @@ static bool readBatch(adcsample_t* convertedSamples) { return true; } +#ifndef SLOW_ADC_CHANNEL_COUNT +#ifdef ADC_MUX_PIN +#define SLOW_ADC_CHANNEL_COUNT 32 +#else // not ADC_MUX_PIN +#define SLOW_ADC_CHANNEL_COUNT 16 +#endif // def ADC_MUX_PIN +#endif // SLOW_ADC_CHANNEL_COUNT + static adcsample_t convertedAdcSamples[SLOW_ADC_CHANNEL_COUNT]; bool readSlowAnalogInputs() { @@ -226,8 +232,6 @@ static Stm32AdcV2Provider provider; #if EFI_USE_FAST_ADC -#include "AdcConfiguration.h" - static void adc_callback_fast(ADCDriver* adcp) { // State may not be complete if we get a callback for "half done" if (adcp->state == ADC_COMPLETE) { @@ -263,6 +267,7 @@ ADCConversionGroup adcgrpcfgFast = { .sqr3 = 0, // Conversion group sequence 1...6 }; +static const size_t maxFastChannels = 8; static size_t fastAdcChannelCount = 0; static constexpr FastAdcToken invalidToken = (FastAdcToken)(-1); @@ -272,7 +277,7 @@ FastAdcToken enableFastAdcChannel(const char* msg, adc_channel_e channel) { return invalidToken; } - if (fastAdcChannelCount >= ADC_MAX_CHANNELS_COUNT) { + if (fastAdcChannelCount >= maxFastChannels) { firmwareError("too many fast ADC channels, attempted channel was %s", msg); } @@ -295,7 +300,7 @@ FastAdcToken enableFastAdcChannel(const char* msg, adc_channel_e channel) { return adcIndex; } -static NO_CACHE adcsample_t fastAdcSampleBuf[ADC_BUF_DEPTH_FAST * ADC_MAX_CHANNELS_COUNT]; +static NO_CACHE adcsample_t fastAdcSampleBuf[ADC_BUF_DEPTH_FAST * maxFastChannels]; float getFastAdc(FastAdcToken token) { if (token == invalidToken) { @@ -320,7 +325,7 @@ static void fast_adc_timer_callback(GPTDriver*) { return; } - if (adcgrpcfgFast.num_channels == 0 || adcgrpcfgFast.num_channels >= ADC_MAX_CHANNELS_COUNT) { + if (adcgrpcfgFast.num_channels == 0 || adcgrpcfgFast.num_channels > maxFastChannels) { // No channels configured (yet), don't attempt to sample // with an invalid configuration return; diff --git a/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp b/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp index 31dd2b1a4d..0d4254f6f9 100644 --- a/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_adc_v4.cpp @@ -8,8 +8,6 @@ #include "pch.h" -#include "AdcConfiguration.h" - #if HAL_USE_ADC #include "mpu_util.h" From 6e7219da9cdde0fbc6ce74bd7d15f758109aa019 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 22:08:42 -0700 Subject: [PATCH 10/47] support ADC_MUX_PIN I guess --- firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp b/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp index 8f5acede02..5ca2bad42d 100644 --- a/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_adc_v2.cpp @@ -214,7 +214,7 @@ bool readSlowAnalogInputs() { struct Stm32AdcV2Provider final : public Stm32AdcProviderBase { Stm32AdcV2Provider() { - registerAdcProvider(*this, /*firstIndex*/ 0, /*size*/ 16); + registerAdcProvider(*this, /*firstIndex*/ 0, /*size*/ SLOW_ADC_CHANNEL_COUNT); } const char* name() const override { From d7b303a3ef8f6656a36ff944f44bcf0337905dfc Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 22:09:35 -0700 Subject: [PATCH 11/47] mostly hook up ADC provider --- firmware/hw_layer/adc/adc_inputs.cpp | 37 +---------- firmware/hw_layer/adc/adc_provider.cpp | 71 ++++++++++++++++++++++ firmware/hw_layer/adc/adc_provider.h | 21 +++++++ firmware/hw_layer/adc/adc_subscription.cpp | 37 +++-------- firmware/hw_layer/hw_layer.mk | 1 + 5 files changed, 102 insertions(+), 65 deletions(-) create mode 100644 firmware/hw_layer/adc/adc_provider.cpp create mode 100644 firmware/hw_layer/adc/adc_provider.h diff --git a/firmware/hw_layer/adc/adc_inputs.cpp b/firmware/hw_layer/adc/adc_inputs.cpp index 5540d2793a..bb3ab8fd7d 100644 --- a/firmware/hw_layer/adc/adc_inputs.cpp +++ b/firmware/hw_layer/adc/adc_inputs.cpp @@ -29,37 +29,15 @@ float __attribute__((weak)) getAnalogInputDividerCoefficient(adc_channel_e) { #if HAL_USE_ADC #include "adc_subscription.h" -#include "AdcConfiguration.h" -#include "mpu_util.h" -#include "periodic_thread_controller.h" #include "protected_gpio.h" -// Board voltage, with divider coefficient accounted for -float getVoltageDivided(const char* msg, adc_channel_e hwChannel) { - return getVoltage(msg, hwChannel) * getAnalogInputDividerCoefficient(hwChannel); -} - -// voltage in MCU universe, from zero to VDD -float getVoltage(const char* msg, adc_channel_e hwChannel) { - return adcToVolts(getSlowAdcValue(msg, hwChannel)); -} - -static uint32_t slowAdcCounter = 0; - static float mcuTemperature; float getMCUInternalTemperature() { return mcuTemperature; } -int getSlowAdcValue(const char* msg, adc_channel_e hwChannel) { - if (!isAdcChannelValid(hwChannel)) { - warning(ObdCode::CUSTOM_OBD_ANALOG_INPUT_NOT_CONFIGURED, "ADC: %s input is not configured", msg); - return -1; - } - - return getSlowAdcSample(hwChannel); -} +static uint32_t slowAdcCounter = 0; void waitForSlowAdc() { // Wait for a few slow adc updates to happen @@ -91,15 +69,4 @@ void updateSlowAdc(efitick_t nowNt) { slowAdcCounter++; } -#else /* not HAL_USE_ADC */ - -__attribute__((weak)) float getVoltageDivided(const char*, adc_channel_e) { - return 0; -} - -// voltage in MCU universe, from zero to VDD -__attribute__((weak)) float getVoltage(const char*, adc_channel_e) { - return 0; -} - -#endif +#endif // HAL_USE_ADC diff --git a/firmware/hw_layer/adc/adc_provider.cpp b/firmware/hw_layer/adc/adc_provider.cpp new file mode 100644 index 0000000000..8ebaa21088 --- /dev/null +++ b/firmware/hw_layer/adc/adc_provider.cpp @@ -0,0 +1,71 @@ +#include "pch.h" + +#include "adc_provider.h" + +namespace { +struct ProviderSlot { + AdcProvider* provider = nullptr; + size_t localIdx = 0; +}; + +constexpr size_t SLOT_COUNT = EFI_ADC_LAST_CHANNEL - EFI_ADC_0; + +static ProviderSlot s_slots[SLOT_COUNT]; + +ProviderSlot* findSlot(adc_channel_e channel) { + if (!isAdcChannelValid(channel)) { + return nullptr; + } + + size_t idx = channel - EFI_ADC_0; + if (idx >= SLOT_COUNT) { + return nullptr; + } + + return &s_slots[idx]; +} +} // namespace + +void registerAdcProvider(AdcProvider& provider, size_t firstChannelIndex, size_t numChannels) { + for (size_t i = 0; i < numChannels; i++) { + size_t globalIdx = firstChannelIndex + i; + + if (globalIdx >= SLOT_COUNT) { + firmwareError( + ObdCode::CUSTOM_ERR_ASSERT, + "%s: ADC channel index %u out of range", + provider.name(), + (unsigned)globalIdx); + return; + } + + s_slots[globalIdx] = {&provider, i}; + } +} + +float AdcProvider::getVoltage(adc_channel_e channel) { + auto slot = findSlot(channel); + if (!slot || !slot->provider) { + return 0; + } + + return slot->provider->get(slot->localIdx); +} + +bool AdcProvider::acquire(adc_channel_e channel) { + auto slot = findSlot(channel); + if (!slot || !slot->provider) { + return false; + } + + return slot->provider->enable("adc", slot->localIdx); +} + +void AdcProvider::release(adc_channel_e channel) { + auto slot = findSlot(channel); + if (!slot || !slot->provider) { + return; + } + + slot->provider->disable(slot->localIdx); +} diff --git a/firmware/hw_layer/adc/adc_provider.h b/firmware/hw_layer/adc/adc_provider.h new file mode 100644 index 0000000000..e0c5cf5af4 --- /dev/null +++ b/firmware/hw_layer/adc/adc_provider.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +struct AdcProvider { + static float getVoltage(adc_channel_e channel); + static bool acquire(adc_channel_e channel); + static void release(adc_channel_e channel); + + // Get the name of this ADC provider + virtual const char* name() const = 0; + + // Enable a channel. Returns true if successful, false if error. + virtual bool enable(const char* name, size_t idx) = 0; + virtual void disable(size_t idx) = 0; + + // Get the voltage on the idx-th channel of this provider + virtual float get(size_t idx) const = 0; +}; + +void registerAdcProvider(AdcProvider&, size_t firstChannelIndex, size_t numChannels); diff --git a/firmware/hw_layer/adc/adc_subscription.cpp b/firmware/hw_layer/adc/adc_subscription.cpp index b976834d32..e407c02ff8 100644 --- a/firmware/hw_layer/adc/adc_subscription.cpp +++ b/firmware/hw_layer/adc/adc_subscription.cpp @@ -1,19 +1,11 @@ #include "pch.h" #include "adc_subscription.h" +#include "adc_provider.h" +#include "adc_inputs.h" #include "biquad.h" -#if EFI_UNIT_TEST - -/*static*/ void AdcSubscription::SubscribeSensor(FunctionalSensor&, adc_channel_e, float, float) {} - -/*static*/ void AdcSubscription::UnsubscribeSensor(FunctionalSensor&) {} - -/*static*/ void AdcSubscription::UnsubscribeSensor(FunctionalSensor&, adc_channel_e) {} - -#else - struct AdcSubscriptionEntry { FunctionalSensor* Sensor; float VoltsPerAdcVolt; @@ -74,22 +66,14 @@ static AdcSubscriptionEntry* findEntry() { return; } -#if EFI_PROD_CODE // Enable the input pin - /** - TODO: this code is similar to initIfValid, what is the plan? shall we extract helper method or else? - */ - brain_pin_e pin = getAdcChannelBrainPin(name, channel); - if (pin != Gpio::Invalid) { - // todo: external muxes for internal ADC #3350 - efiSetPadMode(name, pin, PAL_MODE_INPUT_ANALOG); - } + AdcProvider::acquire(channel); // if 0, default to the board's divider coefficient for given channel if (voltsPerAdcVolt == 0) { voltsPerAdcVolt = getAnalogInputDividerCoefficient(channel); } -#endif /* EFI_PROD_CODE */ + // Populate the entry entry->VoltsPerAdcVolt = voltsPerAdcVolt; entry->Channel = channel; @@ -108,10 +92,7 @@ static AdcSubscriptionEntry* findEntry() { return; } -#if EFI_PROD_CODE - // Release the pin - efiSetPadUnused(getAdcChannelBrainPin("adc unsubscribe", entry->Channel)); -#endif // EFI_PROD_CODE + AdcProvider::release(entry->Channel); sensor.unregister(); @@ -143,7 +124,7 @@ void AdcSubscription::UpdateSubscribers(efitick_t nowNt) { continue; } - float mcuVolts = getVoltage("sensor", entry.Channel); + float mcuVolts = AdcProvider::getVoltage(entry.Channel); float sensorVolts = mcuVolts * entry.VoltsPerAdcVolt; // On the very first update, preload the filter as if we've been @@ -160,7 +141,6 @@ void AdcSubscription::UpdateSubscribers(efitick_t nowNt) { } } -#if EFI_PROD_CODE void AdcSubscription::PrintInfo() { for (size_t i = 0; i < efi::size(s_entries); i++) { auto& entry = s_entries[i]; @@ -171,7 +151,7 @@ void AdcSubscription::PrintInfo() { } const auto name = entry.Sensor->getSensorName(); - float mcuVolts = getVoltage("sensor", entry.Channel); + float mcuVolts = AdcProvider::getVoltage(entry.Channel); float sensorVolts = mcuVolts * entry.VoltsPerAdcVolt; auto channel = entry.Channel; @@ -187,6 +167,3 @@ void AdcSubscription::PrintInfo() { entry.VoltsPerAdcVolt); } } -#endif // EFI_PROD_CODE - -#endif // !EFI_UNIT_TEST diff --git a/firmware/hw_layer/hw_layer.mk b/firmware/hw_layer/hw_layer.mk index f4b63fd2a1..a54cf76e3a 100644 --- a/firmware/hw_layer/hw_layer.mk +++ b/firmware/hw_layer/hw_layer.mk @@ -21,6 +21,7 @@ HW_LAYER_EMS_CPP = \ $(PROJECT_DIR)/hw_layer/mmc_card_attach.cpp \ $(PROJECT_DIR)/hw_layer/mmc_card_mount.cpp \ $(PROJECT_DIR)/hw_layer/adc/adc_inputs.cpp \ + $(PROJECT_DIR)/hw_layer/adc/adc_provider.cpp \ $(PROJECT_DIR)/hw_layer/adc/adc_subscription.cpp \ $(PROJECT_DIR)/hw_layer/adc/ads1015.cpp \ $(PROJECT_DIR)/hw_layer/mc33816.cpp \ From 4f4f9a0c5814790679d3f22219a8e96e1985538e Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 22:41:14 -0700 Subject: [PATCH 12/47] fix raw analog outputs --- firmware/console/status_loop.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/firmware/console/status_loop.cpp b/firmware/console/status_loop.cpp index 83feb06134..570dcf4676 100644 --- a/firmware/console/status_loop.cpp +++ b/firmware/console/status_loop.cpp @@ -342,11 +342,9 @@ static void updateRawSensors() { engine->outputChannels.luaGauges[0] = Sensor::getOrZero(SensorType::LuaGauge1); engine->outputChannels.luaGauges[1] = Sensor::getOrZero(SensorType::LuaGauge2); - for (int i = 0; i < LUA_ANALOG_INPUT_COUNT; i++) { - adc_channel_e channel = engineConfiguration->auxAnalogInputs[i]; - if (isAdcChannelValid(channel)) { - engine->outputChannels.rawAnalogInput[i] = getVoltageDivided("raw aux", channel); - } + for (size_t i = 0; i < efi::size(engine->outputChannels.rawAnalogInput); i++) { + engine->outputChannels.rawAnalogInput[i] = + Sensor::getRaw(static_cast(static_cast(SensorType::AuxAnalog1) + i)); } engine->outputChannels.rawAfr = Sensor::getRaw(SensorType::Lambda1); From 63c6868e04d8d8a86e7a642aa97a203f082989b6 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Wed, 6 May 2026 22:47:14 -0700 Subject: [PATCH 13/47] fix build probably --- .../hw_layer/ports/stm32/stm32_common.cpp | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/firmware/hw_layer/ports/stm32/stm32_common.cpp b/firmware/hw_layer/ports/stm32/stm32_common.cpp index 4e03d2fce6..dce93141e7 100644 --- a/firmware/hw_layer/ports/stm32/stm32_common.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_common.cpp @@ -163,6 +163,20 @@ int getAdcChannelPin(adc_channel_e hwChannel) { return getHwPin("get_pin", brainPin); } +bool Stm32AdcProviderBase::enable(const char* name, size_t idx) { + brain_pin_e pin = getAdcChannelBrainPin(name, idx + EFI_ADC_0); + if (pin != Gpio::Invalid) { + efiSetPadMode(name, pin, PAL_MODE_INPUT_ANALOG); + return true; + } + + return false; +} + +void Stm32AdcProviderBase::disable(size_t idx) { + efiSetPadUnused(getAdcChannelBrainPin("adc unsubscribe", idx + EFI_ADC_0)); +} + #endif /* HAL_USE_ADC */ #if EFI_PROD_CODE @@ -836,18 +850,4 @@ void assertInterruptPriority(const char* func, uint8_t expectedPrio) { } } -bool Stm32AdcProviderBase::enable(const char* name, size_t idx) { - brain_pin_e pin = getAdcChannelBrainPin(name, idx + EFI_ADC_0); - if (pin != Gpio::Invalid) { - efiSetPadMode(name, pin, PAL_MODE_INPUT_ANALOG); - return true; - } - - return false; -} - -void Stm32AdcProviderBase::disable(size_t idx) { - efiSetPadUnused(getAdcChannelBrainPin("adc unsubscribe", idx + EFI_ADC_0)); -} - #endif // EFI_PROD_CODE From a486990957679d4d9edad2cd1cc0e73d92685e46 Mon Sep 17 00:00:00 2001 From: Matthew Kennedy Date: Thu, 7 May 2026 13:43:36 -0700 Subject: [PATCH 14/47] list of providers, rather than list of channels --- firmware/hw_layer/adc/adc_provider.cpp | 70 ++++++++++++++++---------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/firmware/hw_layer/adc/adc_provider.cpp b/firmware/hw_layer/adc/adc_provider.cpp index 8ebaa21088..ed7b1e7f00 100644 --- a/firmware/hw_layer/adc/adc_provider.cpp +++ b/firmware/hw_layer/adc/adc_provider.cpp @@ -3,69 +3,87 @@ #include "adc_provider.h" namespace { -struct ProviderSlot { - AdcProvider* provider = nullptr; - size_t localIdx = 0; +struct ProviderEntry { + AdcProvider* provider; + size_t firstChannelIndex; + size_t numChannels; }; -constexpr size_t SLOT_COUNT = EFI_ADC_LAST_CHANNEL - EFI_ADC_0; +constexpr size_t MAX_PROVIDERS = 4; -static ProviderSlot s_slots[SLOT_COUNT]; +static ProviderEntry s_providers[MAX_PROVIDERS]; +static size_t s_providerCount = 0; -ProviderSlot* findSlot(adc_channel_e channel) { +// Returns the provider that owns this channel and writes the provider-local +// index to outLocalIdx. Returns nullptr if the channel isn't registered. +AdcProvider* findProvider(adc_channel_e channel, size_t& outLocalIdx) { if (!isAdcChannelValid(channel)) { return nullptr; } - size_t idx = channel - EFI_ADC_0; - if (idx >= SLOT_COUNT) { - return nullptr; + size_t globalIdx = channel - EFI_ADC_0; + + for (size_t i = 0; i < s_providerCount; i++) { + auto& entry = s_providers[i]; + if (globalIdx >= entry.firstChannelIndex && globalIdx < entry.firstChannelIndex + entry.numChannels) { + outLocalIdx = globalIdx - entry.firstChannelIndex; + return entry.provider; + } } - return &s_slots[idx]; + return nullptr; } } // namespace void registerAdcProvider(AdcProvider& provider, size_t firstChannelIndex, size_t numChannels) { - for (size_t i = 0; i < numChannels; i++) { - size_t globalIdx = firstChannelIndex + i; + if (s_providerCount >= MAX_PROVIDERS) { + firmwareError(ObdCode::CUSTOM_ERR_ASSERT, "too many ADC providers registering %s", provider.name()); + return; + } - if (globalIdx >= SLOT_COUNT) { + size_t newEnd = firstChannelIndex + numChannels; + for (size_t i = 0; i < s_providerCount; i++) { + auto& existing = s_providers[i]; + size_t existingEnd = existing.firstChannelIndex + existing.numChannels; + if (firstChannelIndex < existingEnd && existing.firstChannelIndex < newEnd) { firmwareError( ObdCode::CUSTOM_ERR_ASSERT, - "%s: ADC channel index %u out of range", + "ADC provider %s overlaps %s", provider.name(), - (unsigned)globalIdx); + existing.provider->name()); return; } - - s_slots[globalIdx] = {&provider, i}; } + + s_providers[s_providerCount++] = {&provider, firstChannelIndex, numChannels}; } float AdcProvider::getVoltage(adc_channel_e channel) { - auto slot = findSlot(channel); - if (!slot || !slot->provider) { + size_t localIdx; + auto provider = findProvider(channel, localIdx); + if (!provider) { return 0; } - return slot->provider->get(slot->localIdx); + return provider->get(localIdx); } bool AdcProvider::acquire(adc_channel_e channel) { - auto slot = findSlot(channel); - if (!slot || !slot->provider) { + size_t localIdx; + auto provider = findProvider(channel, localIdx); + if (!provider) { return false; } - return slot->provider->enable("adc", slot->localIdx); + return provider->enable("adc", localIdx); } void AdcProvider::release(adc_channel_e channel) { - auto slot = findSlot(channel); - if (!slot || !slot->provider) { + size_t localIdx; + auto provider = findProvider(channel, localIdx); + if (!provider) { return; } - slot->provider->disable(slot->localIdx); + provider->disable(localIdx); } From 015a12974d67673d7b8f73809b6030230da039fb Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 3 May 2026 13:02:50 +0200 Subject: [PATCH 15/47] further work --- firmware/controllers/controllers.mk | 2 +- .../controllers/system/g0_firmware_loader.cpp | 306 ++++++++++++++++++ .../controllers/system/g0_firmware_loader.h | 3 + firmware/rusefi.cpp | 5 + 4 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 firmware/controllers/system/g0_firmware_loader.cpp create mode 100644 firmware/controllers/system/g0_firmware_loader.h diff --git a/firmware/controllers/controllers.mk b/firmware/controllers/controllers.mk index 4a7430cbaa..bb042474d0 100644 --- a/firmware/controllers/controllers.mk +++ b/firmware/controllers/controllers.mk @@ -29,6 +29,7 @@ CONTROLLERS_SRC_CPP = \ $(CONTROLLERS_DIR)/system/timer/event_queue.cpp \ $(CONTROLLERS_DIR)/settings.cpp \ $(CONTROLLERS_DIR)/core/error_handling.cpp \ + $(CONTROLLERS_DIR)/system/g0_firmware_loader.cpp \ $(CONTROLLERS_DIR)/engine_cycle/high_pressure_fuel_pump.cpp \ $(CONTROLLERS_DIR)/engine_cycle/rpm_calculator.cpp \ $(CONTROLLERS_DIR)/engine_cycle/spark_logic.cpp \ @@ -73,4 +74,3 @@ CONTROLLERS_INC=\ $(CONTROLLERS_DIR)/actuators \ $(CONTROLLERS_DIR)/actuators/gppwm \ $(CONTROLLERS_DIR)/serial \ - diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp new file mode 100644 index 0000000000..ee8acf38c9 --- /dev/null +++ b/firmware/controllers/system/g0_firmware_loader.cpp @@ -0,0 +1,306 @@ +#include "pch.h" + +#if HW_ATLAS && HAL_USE_SPI + +#include "g0_firmware_loader.h" +#include "hardware.h" +#include "mpu_util.h" + +#if __has_include("../../ext/g0_firmware/for_fome/g0_firmware_image.h") +#include "../../ext/g0_firmware/for_fome/g0_firmware_image.h" +#define G0_FIRMWARE_IMAGE_AVAILABLE TRUE +#else +#define G0_FIRMWARE_IMAGE_AVAILABLE FALSE +#endif + +#if G0_FIRMWARE_IMAGE_AVAILABLE + +static constexpr spi_device_e G0_SPI_DEVICE = SPI_DEVICE_5; +static constexpr brain_pin_e G0_RESET_PIN = Gpio::B14; +static constexpr brain_pin_e G0_BOOT_PIN = Gpio::B15; +static constexpr brain_pin_e G0_SPI_CS_PIN = Gpio::F6; +static constexpr uint32_t G0_FLASH_BASE = 0x08000000; + +static constexpr uint8_t STM_BL_ACK = 0x79; +static constexpr uint8_t STM_BL_SYNC = 0x5A; +static constexpr uint8_t STM_BL_EXTENDED_ERASE = 0x44; +static constexpr uint8_t STM_BL_WRITE_MEMORY = 0x31; +static constexpr uint8_t STM_BL_GO = 0x21; + +static constexpr uint8_t G0_APP_CMD_NOP = 0x00; +static constexpr uint8_t G0_APP_CMD_READ_VERSION = 0x01; +static constexpr uint8_t G0_APP_CMD_ENTER_UPDATE = 0xA5; + +static constexpr uint8_t G0_APP_STATUS_READY = 0x00; +static constexpr uint8_t G0_APP_STATUS_UPDATE_MODE = 0x01; +static constexpr size_t G0_APP_FRAME_SIZE = 8; + +static SPIConfig g0SpiConfig = { + .circular = false, + .end_cb = NULL, + .ssport = NULL, + .sspad = 0, + .cfg1 = 7 | SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, + .cfg2 = 0}; + +static void setPin(brain_pin_e pin, bool value) { + palWritePad(getHwPort("g0", pin), getHwPin("g0", pin), value); +} + +static void initControlPins() { + efiSetPadMode("G0 BOOT", G0_BOOT_PIN, PAL_MODE_OUTPUT_PUSHPULL); + efiSetPadMode("G0 RESET", G0_RESET_PIN, PAL_MODE_OUTPUT_PUSHPULL); + + setPin(G0_BOOT_PIN, false); + setPin(G0_RESET_PIN, true); +} + +static void resetG0(bool bootloaderMode) { + setPin(G0_BOOT_PIN, bootloaderMode); + chThdSleepMilliseconds(5); + + setPin(G0_RESET_PIN, false); + chThdSleepMilliseconds(20); + + setPin(G0_RESET_PIN, true); + chThdSleepMilliseconds(100); +} + +static uint8_t spiByte(SPIDriver* spi, uint8_t tx) { + uint8_t rx = 0; + spiExchange(spi, 1, &tx, &rx); + return rx; +} + +static bool waitAck(SPIDriver* spi, int attempts = 200) { + while (attempts-- > 0) { + if (spiByte(spi, 0x00) == STM_BL_ACK) { + return true; + } + chThdSleepMilliseconds(1); + } + + return false; +} + +static bool sendBytesAndWaitAck(SPIDriver* spi, const uint8_t* data, size_t size) { + spiSend(spi, size, data); + return waitAck(spi); +} + +static bool sendCommand(SPIDriver* spi, uint8_t command) { + const uint8_t bytes[] = {command, static_cast(command ^ 0xFF)}; + return sendBytesAndWaitAck(spi, bytes, sizeof(bytes)); +} + +static bool sendAddress(SPIDriver* spi, uint32_t address) { + uint8_t bytes[5] = { + static_cast(address >> 24), + static_cast(address >> 16), + static_cast(address >> 8), + static_cast(address), + 0}; + + bytes[4] = bytes[0] ^ bytes[1] ^ bytes[2] ^ bytes[3]; + + return sendBytesAndWaitAck(spi, bytes, sizeof(bytes)); +} + +static bool massErase(SPIDriver* spi) { + if (!sendCommand(spi, STM_BL_EXTENDED_ERASE)) { + return false; + } + + const uint8_t globalMassErase[] = {0xFF, 0xFF, 0x00}; + return sendBytesAndWaitAck(spi, globalMassErase, sizeof(globalMassErase)); +} + +static bool writeBlock(SPIDriver* spi, uint32_t address, const uint8_t* data, size_t size) { + if (size == 0 || size > 256) { + return false; + } + + if (!sendCommand(spi, STM_BL_WRITE_MEMORY) || !sendAddress(spi, address)) { + return false; + } + + uint8_t buffer[258]; + buffer[0] = static_cast(size - 1); + uint8_t checksum = buffer[0]; + + for (size_t i = 0; i < size; i++) { + buffer[1 + i] = data[i]; + checksum ^= data[i]; + } + + buffer[1 + size] = checksum; + + return sendBytesAndWaitAck(spi, buffer, size + 2); +} + +static bool go(SPIDriver* spi, uint32_t address) { + return sendCommand(spi, STM_BL_GO) && sendAddress(spi, address); +} + +static uint32_t readLe32(const uint8_t* data) { + return static_cast(data[0]) + | (static_cast(data[1]) << 8) + | (static_cast(data[2]) << 16) + | (static_cast(data[3]) << 24); +} + +static void exchangeG0AppFrame(SPIDriver* spi, uint8_t command, uint8_t* rx) { + uint8_t tx[G0_APP_FRAME_SIZE] = {}; + tx[0] = command; + memset(rx, 0, G0_APP_FRAME_SIZE); + + spiSelect(spi); + spiExchange(spi, G0_APP_FRAME_SIZE, tx, rx); + spiUnselect(spi); +} + +static bool isG0AppResponse(const uint8_t* rx, uint8_t expectedLastCommand) { + const bool knownStatus = rx[0] == G0_APP_STATUS_READY || rx[0] == G0_APP_STATUS_UPDATE_MODE; + + return knownStatus + && rx[5] == expectedLastCommand + && rx[6] == 0 + && rx[7] == 0; +} + +static bool readG0AppVersion(SPIDriver* spi, uint32_t& version) { + uint8_t rx[G0_APP_FRAME_SIZE]; + + exchangeG0AppFrame(spi, G0_APP_CMD_READ_VERSION, rx); + chThdSleepMilliseconds(1); + + exchangeG0AppFrame(spi, G0_APP_CMD_NOP, rx); + if (!isG0AppResponse(rx, G0_APP_CMD_READ_VERSION)) { + return false; + } + + version = readLe32(&rx[1]); + return true; +} + +static void requestG0AppUpdate(SPIDriver* spi) { + uint8_t rx[G0_APP_FRAME_SIZE]; + + exchangeG0AppFrame(spi, G0_APP_CMD_ENTER_UPDATE, rx); + chThdSleepMilliseconds(1); + exchangeG0AppFrame(spi, G0_APP_CMD_NOP, rx); +} + +static bool injectG0Firmware(SPIDriver* spi) { + const size_t totalSize = sizeof(build_g0_extension_bin); + + if (totalSize == 0) { + efiPrintf("G0 firmware load ERROR: firmware image header is missing or empty"); + return false; + } + + efiPrintf("G0 firmware load: entering SPI bootloader"); + resetG0(true); + + spiSelect(spi); + + spiByte(spi, STM_BL_SYNC); + if (!waitAck(spi)) { + spiUnselect(spi); + efiPrintf("G0 firmware load ERROR: no ACK from SPI bootloader"); + return false; + } + + efiPrintf("G0 firmware load: erasing flash"); + if (!massErase(spi)) { + spiUnselect(spi); + efiPrintf("G0 firmware load ERROR: flash erase failed"); + return false; + } + + efiPrintf("G0 firmware load: writing %d bytes", static_cast(totalSize)); + for (size_t offset = 0; offset < totalSize; offset += 256) { + uint8_t block[256]; + const size_t blockSize = minI(256, totalSize - offset); + memset(block, 0xFF, sizeof(block)); + memcpy(block, build_g0_extension_bin + offset, blockSize); + + if (!writeBlock(spi, G0_FLASH_BASE + offset, block, sizeof(block))) { + spiUnselect(spi); + efiPrintf("G0 firmware load ERROR: write failed at offset %d", static_cast(offset)); + return false; + } + } + + efiPrintf("G0 firmware load: starting firmware"); + const bool ok = go(spi, G0_FLASH_BASE); + spiUnselect(spi); + + resetG0(false); + return ok; +} + +#endif // G0_FIRMWARE_IMAGE_AVAILABLE + +bool loadG0Firmware() { +#if G0_FIRMWARE_IMAGE_AVAILABLE + initControlPins(); + + engineConfiguration->is_enabled_spi_5 = true; + engineConfiguration->spi5sckPin = Gpio::F7; + engineConfiguration->spi5misoPin = Gpio::F8; + engineConfiguration->spi5mosiPin = Gpio::F9; + + turnOnSpi(G0_SPI_DEVICE); + + SPIDriver* spi = getSpiDevice(G0_SPI_DEVICE); + initSpiCs(&g0SpiConfig, G0_SPI_CS_PIN); + palSetPad(g0SpiConfig.ssport, g0SpiConfig.sspad); + spiStart(spi, &g0SpiConfig); + + lockSpi(G0_SPI_DEVICE); + resetG0(false); + + uint32_t currentVersion = 0; + bool ok = true; + + if (readG0AppVersion(spi, currentVersion)) { + efiPrintf("G0 firmware load: app version %d, bundled version %d", + static_cast(currentVersion), + static_cast(build_g0_extension_version)); + + if (currentVersion == build_g0_extension_version) { + efiPrintf("G0 firmware load: firmware is already current"); + } else { + efiPrintf("G0 firmware load: requesting app update mode"); + requestG0AppUpdate(spi); + ok = injectG0Firmware(spi); + } + } else { + efiPrintf("G0 firmware load: no app version response, forcing update"); + ok = injectG0Firmware(spi); + } + + unlockSpi(G0_SPI_DEVICE); + + if (ok) { + efiPrintf("G0 firmware load: complete"); + } else { + efiPrintf("G0 firmware load: failed"); + } + + return ok; +#else + efiPrintf("G0 firmware load ERROR: run `make -C firmware/ext/g0_firmware for_fome_image` first"); + return false; +#endif +} + +#else + +bool loadG0Firmware() { + efiPrintf("G0 firmware load is only available on Atlas with SPI enabled"); + return false; +} + +#endif diff --git a/firmware/controllers/system/g0_firmware_loader.h b/firmware/controllers/system/g0_firmware_loader.h new file mode 100644 index 0000000000..3e213d945a --- /dev/null +++ b/firmware/controllers/system/g0_firmware_loader.h @@ -0,0 +1,3 @@ +#pragma once + +bool loadG0Firmware(); diff --git a/firmware/rusefi.cpp b/firmware/rusefi.cpp index 2d1ab59262..d8cd1745b4 100644 --- a/firmware/rusefi.cpp +++ b/firmware/rusefi.cpp @@ -116,6 +116,7 @@ #include "trigger_emulator_algo.h" #include "rusefi_lua.h" #include "bootloader_updater.h" +#include "g0_firmware_loader.h" #include @@ -206,6 +207,10 @@ void runRusEfi() { // Read configuration from flash memory loadConfiguration(); +#if HW_ATLAS + loadG0Firmware(); +#endif + #if EFI_TUNER_STUDIO startTunerStudioConnectivity(); #endif /* EFI_TUNER_STUDIO */ From 0851dec58cf075522c2eaa814ea0ffe82fb6cf33 Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 3 May 2026 13:52:40 +0200 Subject: [PATCH 16/47] fix --- .gitmodules | 4 ++ firmware/config/boards/atlas/compile_atlas.sh | 41 +++++++++++++++++++ firmware/ext/g0_firmware | 1 + 3 files changed, 46 insertions(+) create mode 160000 firmware/ext/g0_firmware diff --git a/.gitmodules b/.gitmodules index f1a012736c..5c02dc7786 100644 --- a/.gitmodules +++ b/.gitmodules @@ -44,3 +44,7 @@ path = firmware/ext/build-tools url = https://github.com/FOME-Tech/build-tools.git branch = main +[submodule "firmware/ext/g0_firmware"] + path = firmware/ext/g0_firmware + url = https://github.com/dynfer/g0-extension.git + branch = main diff --git a/firmware/config/boards/atlas/compile_atlas.sh b/firmware/config/boards/atlas/compile_atlas.sh index 19138ef761..876d0acdfc 100755 --- a/firmware/config/boards/atlas/compile_atlas.sh +++ b/firmware/config/boards/atlas/compile_atlas.sh @@ -1,5 +1,46 @@ #!/bin/bash +set -e + +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd) +FW_DIR=$(readlink -f "$SCRIPT_DIR/../../..") +REPO_DIR=$(readlink -f "$FW_DIR/..") +G0_DIR="$FW_DIR/ext/g0_firmware" + export USE_OPENBLT=yes +if [ -e "$G0_DIR/.git" ]; then + git -C "$G0_DIR" submodule update --init --recursive +else + git -C "$REPO_DIR" submodule update --init --recursive firmware/ext/g0_firmware +fi +git -C "$REPO_DIR" submodule update --init --depth=1 firmware/ext/build-tools + +UNAME_SM=$(uname -sm) +case "$UNAME_SM" in + "Darwin "*) + COMPILER_PLATFORM=arm-gnu-toolchain-11.3.rel1-darwin-x86_64-arm-none-eabi + ;; + "Linux x86_64") + COMPILER_PLATFORM=arm-gnu-toolchain-11.3.rel1-x86_64-arm-none-eabi + ;; + *) + echo "Unsupported compiler platform: $UNAME_SM" + exit 1 + ;; +esac + +G0_TRGT="$FW_DIR/ext/build-tools/$COMPILER_PLATFORM/bin/arm-none-eabi-" +[ -x "${G0_TRGT}g++" ] || { echo "Compiler not found at ${G0_TRGT}g++"; exit 1; } + +make -C "$G0_DIR" -j20 TRGT="$G0_TRGT" USE_OPT="-O0 -ggdb -fomit-frame-pointer -falign-functions=16 --specs=nosys.specs" for_fome_image + +G0_IMAGE_HEADER="$G0_DIR/for_fome/g0_firmware_image.h" +G0_APP_VERSION=$(sed -nE 's/.*APP_VERSION = ([0-9]+)U;.*/\1/p' "$G0_DIR/source/spi_app_protocol.cpp" | head -n1) +[ -n "$G0_APP_VERSION" ] || { echo "Unable to determine G0 app version"; exit 1; } +if ! grep -q 'build_g0_extension_version' "$G0_IMAGE_HEADER"; then + printf '\nstatic const unsigned int build_g0_extension_version = %sU;\n' "$G0_APP_VERSION" >> "$G0_IMAGE_HEADER" +fi + +cd "$SCRIPT_DIR" bash ../common_make.sh atlas ARCH_STM32H7 diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware new file mode 160000 index 0000000000..e19db16b60 --- /dev/null +++ b/firmware/ext/g0_firmware @@ -0,0 +1 @@ +Subproject commit e19db16b60496942b78492533d3b0b48f0a25075 From 426ceeb195d93ab907fbb336c0bae6a99e0484a3 Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 3 May 2026 18:14:31 +0200 Subject: [PATCH 17/47] add delay --- .../controllers/system/g0_firmware_loader.cpp | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp index ee8acf38c9..73b2881fa3 100644 --- a/firmware/controllers/system/g0_firmware_loader.cpp +++ b/firmware/controllers/system/g0_firmware_loader.cpp @@ -22,10 +22,12 @@ static constexpr brain_pin_e G0_SPI_CS_PIN = Gpio::F6; static constexpr uint32_t G0_FLASH_BASE = 0x08000000; static constexpr uint8_t STM_BL_ACK = 0x79; +static constexpr uint8_t STM_BL_NACK = 0x1F; static constexpr uint8_t STM_BL_SYNC = 0x5A; static constexpr uint8_t STM_BL_EXTENDED_ERASE = 0x44; static constexpr uint8_t STM_BL_WRITE_MEMORY = 0x31; static constexpr uint8_t STM_BL_GO = 0x21; +static constexpr int STM_BL_INTER_BYTE_DELAY_US = 20; static constexpr uint8_t G0_APP_CMD_NOP = 0x00; static constexpr uint8_t G0_APP_CMD_READ_VERSION = 0x01; @@ -72,11 +74,28 @@ static uint8_t spiByte(SPIDriver* spi, uint8_t tx) { return rx; } +static uint8_t bootloaderSpiByte(SPIDriver* spi, uint8_t tx) { + const uint8_t rx = spiByte(spi, tx); + + // AN4286 requires at least 15us between bytes for the STM32 ROM SPI bootloader. + chThdSleepMicroseconds(STM_BL_INTER_BYTE_DELAY_US); + + return rx; +} + static bool waitAck(SPIDriver* spi, int attempts = 200) { while (attempts-- > 0) { - if (spiByte(spi, 0x00) == STM_BL_ACK) { + const uint8_t response = bootloaderSpiByte(spi, 0x00); + + if (response == STM_BL_ACK) { + bootloaderSpiByte(spi, STM_BL_ACK); return true; } + + if (response == STM_BL_NACK) { + return false; + } + chThdSleepMilliseconds(1); } @@ -84,12 +103,15 @@ static bool waitAck(SPIDriver* spi, int attempts = 200) { } static bool sendBytesAndWaitAck(SPIDriver* spi, const uint8_t* data, size_t size) { - spiSend(spi, size, data); + for (size_t i = 0; i < size; i++) { + bootloaderSpiByte(spi, data[i]); + } + return waitAck(spi); } static bool sendCommand(SPIDriver* spi, uint8_t command) { - const uint8_t bytes[] = {command, static_cast(command ^ 0xFF)}; + const uint8_t bytes[] = {STM_BL_SYNC, command, static_cast(command ^ 0xFF)}; return sendBytesAndWaitAck(spi, bytes, sizeof(bytes)); } @@ -204,7 +226,7 @@ static bool injectG0Firmware(SPIDriver* spi) { spiSelect(spi); - spiByte(spi, STM_BL_SYNC); + bootloaderSpiByte(spi, STM_BL_SYNC); if (!waitAck(spi)) { spiUnselect(spi); efiPrintf("G0 firmware load ERROR: no ACK from SPI bootloader"); From 02f78bae31a7014776b0e8764791d4bb6e7a6bf5 Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 3 May 2026 20:22:19 +0200 Subject: [PATCH 18/47] further testing --- .../controllers/system/g0_firmware_loader.cpp | 88 +++++++++++++------ .../controllers/system/g0_firmware_loader.h | 2 +- firmware/rusefi.cpp | 1 + .../main/java/com/rusefi/BenchTestPane.java | 26 ++++++ 4 files changed, 90 insertions(+), 27 deletions(-) diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp index 73b2881fa3..b1c217608a 100644 --- a/firmware/controllers/system/g0_firmware_loader.cpp +++ b/firmware/controllers/system/g0_firmware_loader.cpp @@ -28,6 +28,7 @@ static constexpr uint8_t STM_BL_EXTENDED_ERASE = 0x44; static constexpr uint8_t STM_BL_WRITE_MEMORY = 0x31; static constexpr uint8_t STM_BL_GO = 0x21; static constexpr int STM_BL_INTER_BYTE_DELAY_US = 20; +static constexpr int G0_FLASH_MAX_ATTEMPTS = 2; static constexpr uint8_t G0_APP_CMD_NOP = 0x00; static constexpr uint8_t G0_APP_CMD_READ_VERSION = 0x01; @@ -36,6 +37,10 @@ static constexpr uint8_t G0_APP_CMD_ENTER_UPDATE = 0xA5; static constexpr uint8_t G0_APP_STATUS_READY = 0x00; static constexpr uint8_t G0_APP_STATUS_UPDATE_MODE = 0x01; static constexpr size_t G0_APP_FRAME_SIZE = 8; +static constexpr size_t G0_SPI_DMA_BUFFER_SIZE = 258; + +static NO_CACHE uint8_t g0SpiTxBuffer[G0_SPI_DMA_BUFFER_SIZE]; +static NO_CACHE uint8_t g0SpiRxBuffer[G0_SPI_DMA_BUFFER_SIZE]; static SPIConfig g0SpiConfig = { .circular = false, @@ -69,9 +74,10 @@ static void resetG0(bool bootloaderMode) { } static uint8_t spiByte(SPIDriver* spi, uint8_t tx) { - uint8_t rx = 0; - spiExchange(spi, 1, &tx, &rx); - return rx; + g0SpiTxBuffer[0] = tx; + g0SpiRxBuffer[0] = 0; + spiExchange(spi, 1, g0SpiTxBuffer, g0SpiRxBuffer); + return g0SpiRxBuffer[0]; } static uint8_t bootloaderSpiByte(SPIDriver* spi, uint8_t tx) { @@ -83,12 +89,20 @@ static uint8_t bootloaderSpiByte(SPIDriver* spi, uint8_t tx) { return rx; } +static bool handleBootloaderAck(SPIDriver* spi, uint8_t response) { + if (response == STM_BL_ACK) { + bootloaderSpiByte(spi, STM_BL_ACK); + return true; + } + + return false; +} + static bool waitAck(SPIDriver* spi, int attempts = 200) { while (attempts-- > 0) { const uint8_t response = bootloaderSpiByte(spi, 0x00); - if (response == STM_BL_ACK) { - bootloaderSpiByte(spi, STM_BL_ACK); + if (handleBootloaderAck(spi, response)) { return true; } @@ -104,7 +118,15 @@ static bool waitAck(SPIDriver* spi, int attempts = 200) { static bool sendBytesAndWaitAck(SPIDriver* spi, const uint8_t* data, size_t size) { for (size_t i = 0; i < size; i++) { - bootloaderSpiByte(spi, data[i]); + const uint8_t response = bootloaderSpiByte(spi, data[i]); + + if (handleBootloaderAck(spi, response)) { + return true; + } + + if (response == STM_BL_NACK) { + return false; + } } return waitAck(spi); @@ -165,29 +187,27 @@ static bool go(SPIDriver* spi, uint32_t address) { } static uint32_t readLe32(const uint8_t* data) { - return static_cast(data[0]) - | (static_cast(data[1]) << 8) - | (static_cast(data[2]) << 16) - | (static_cast(data[3]) << 24); + return static_cast(data[0]) | (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | (static_cast(data[3]) << 24); } static void exchangeG0AppFrame(SPIDriver* spi, uint8_t command, uint8_t* rx) { - uint8_t tx[G0_APP_FRAME_SIZE] = {}; - tx[0] = command; + memset(g0SpiTxBuffer, 0, G0_APP_FRAME_SIZE); + g0SpiTxBuffer[0] = command; + memset(g0SpiRxBuffer, 0, G0_APP_FRAME_SIZE); memset(rx, 0, G0_APP_FRAME_SIZE); spiSelect(spi); - spiExchange(spi, G0_APP_FRAME_SIZE, tx, rx); + spiExchange(spi, G0_APP_FRAME_SIZE, g0SpiTxBuffer, g0SpiRxBuffer); spiUnselect(spi); + + memcpy(rx, g0SpiRxBuffer, G0_APP_FRAME_SIZE); } static bool isG0AppResponse(const uint8_t* rx, uint8_t expectedLastCommand) { const bool knownStatus = rx[0] == G0_APP_STATUS_READY || rx[0] == G0_APP_STATUS_UPDATE_MODE; - return knownStatus - && rx[5] == expectedLastCommand - && rx[6] == 0 - && rx[7] == 0; + return knownStatus && rx[5] == expectedLastCommand && rx[6] == 0 && rx[7] == 0; } static bool readG0AppVersion(SPIDriver* spi, uint32_t& version) { @@ -262,9 +282,24 @@ static bool injectG0Firmware(SPIDriver* spi) { return ok; } +static bool injectG0FirmwareWithRetry(SPIDriver* spi) { + for (int attempt = 1; attempt <= G0_FLASH_MAX_ATTEMPTS; attempt++) { + efiPrintf("G0 firmware load: flash attempt %d/%d", attempt, G0_FLASH_MAX_ATTEMPTS); + + if (injectG0Firmware(spi)) { + return true; + } + + resetG0(false); + } + + efiPrintf("G0 firmware load ERROR: aborting G0 flash after %d failed attempts", G0_FLASH_MAX_ATTEMPTS); + return false; +} + #endif // G0_FIRMWARE_IMAGE_AVAILABLE -bool loadG0Firmware() { +bool loadG0Firmware(bool forceUpdate) { #if G0_FIRMWARE_IMAGE_AVAILABLE initControlPins(); @@ -287,20 +322,21 @@ bool loadG0Firmware() { bool ok = true; if (readG0AppVersion(spi, currentVersion)) { - efiPrintf("G0 firmware load: app version %d, bundled version %d", - static_cast(currentVersion), - static_cast(build_g0_extension_version)); + efiPrintf( + "G0 firmware load: app version %d, bundled version %d", + static_cast(currentVersion), + static_cast(build_g0_extension_version)); - if (currentVersion == build_g0_extension_version) { + if (!forceUpdate && currentVersion == build_g0_extension_version) { efiPrintf("G0 firmware load: firmware is already current"); } else { - efiPrintf("G0 firmware load: requesting app update mode"); + efiPrintf("G0 firmware load: requesting app update mode%s", forceUpdate ? " (forced)" : ""); requestG0AppUpdate(spi); - ok = injectG0Firmware(spi); + ok = injectG0FirmwareWithRetry(spi); } } else { efiPrintf("G0 firmware load: no app version response, forcing update"); - ok = injectG0Firmware(spi); + ok = injectG0FirmwareWithRetry(spi); } unlockSpi(G0_SPI_DEVICE); @@ -320,7 +356,7 @@ bool loadG0Firmware() { #else -bool loadG0Firmware() { +bool loadG0Firmware(bool) { efiPrintf("G0 firmware load is only available on Atlas with SPI enabled"); return false; } diff --git a/firmware/controllers/system/g0_firmware_loader.h b/firmware/controllers/system/g0_firmware_loader.h index 3e213d945a..d9827c8aaf 100644 --- a/firmware/controllers/system/g0_firmware_loader.h +++ b/firmware/controllers/system/g0_firmware_loader.h @@ -1,3 +1,3 @@ #pragma once -bool loadG0Firmware(); +bool loadG0Firmware(bool forceUpdate = false); diff --git a/firmware/rusefi.cpp b/firmware/rusefi.cpp index d8cd1745b4..adf54aadf8 100644 --- a/firmware/rusefi.cpp +++ b/firmware/rusefi.cpp @@ -174,6 +174,7 @@ void runRusEfi() { addConsoleAction(CMD_REBOOT, scheduleReboot); addConsoleAction(CMD_REBOOT_DFU, jump_to_bootloader); + addConsoleAction("force_g0_update", []() { loadG0Firmware(true); }); #if EFI_USE_OPENBLT addConsoleAction(CMD_REBOOT_OPENBLT, jump_to_openblt); diff --git a/java_console/ui/src/main/java/com/rusefi/BenchTestPane.java b/java_console/ui/src/main/java/com/rusefi/BenchTestPane.java index e97722c173..de6bee0ba1 100644 --- a/java_console/ui/src/main/java/com/rusefi/BenchTestPane.java +++ b/java_console/ui/src/main/java/com/rusefi/BenchTestPane.java @@ -16,6 +16,9 @@ import static com.rusefi.config.generated.Fields.*; public class BenchTestPane { + private static final String CMD_FORCE_G0_UPDATE = "force_g0_update"; + private static final int G0_UPDATE_TIMEOUT_MS = 120_000; + private final JPanel content = new JPanel(new GridLayout(2, 5)); private final UIContext uiContext; @@ -44,9 +47,32 @@ protected String getCommand() { return Fields.CMD_REBOOT_DFU; } }.getContent()); + content.add(createForceG0Update()); content.add(new MessagesView(config.getRoot()).messagesScroll); } + private Component createForceG0Update() { + CommandControl panel = new FixedCommandControl(uiContext, "G0 Firmware", "", "Force Update", CMD_FORCE_G0_UPDATE) { + @NotNull + @Override + protected ActionListener createButtonListener() { + return e -> { + int choice = JOptionPane.showConfirmDialog( + content, + "Force G0 firmware update?", + "G0 Firmware Update", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.WARNING_MESSAGE); + + if (choice == JOptionPane.OK_OPTION) { + uiContext.getCommandQueue().write(CMD_FORCE_G0_UPDATE, G0_UPDATE_TIMEOUT_MS); + } + }; + } + }; + return panel.getContent(); + } + private Component grabPerformanceTrace() { JButton button = new JButton("Grab PTrace"); ActionListener actionListener = e -> uiContext.getLinkManager().COMMUNICATION_EXECUTOR.execute(() -> { From e33e5235e1b5148d2bc39b0d87b8b2fba614262c Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 3 May 2026 21:42:51 +0200 Subject: [PATCH 19/47] working g0 upload --- firmware/config/boards/atlas/board_configuration.cpp | 6 ++++++ firmware/controllers/system/g0_firmware_loader.cpp | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/firmware/config/boards/atlas/board_configuration.cpp b/firmware/config/boards/atlas/board_configuration.cpp index babe02c337..2e561a5853 100644 --- a/firmware/config/boards/atlas/board_configuration.cpp +++ b/firmware/config/boards/atlas/board_configuration.cpp @@ -137,6 +137,12 @@ void setBoardConfigOverrides() { engineConfiguration->spi4misoPin = Gpio::E5; engineConfiguration->spi4mosiPin = Gpio::E6; + // G0 extension firmware SPI + engineConfiguration->is_enabled_spi_5 = true; + engineConfiguration->spi5sckPin = Gpio::F7; + engineConfiguration->spi5misoPin = Gpio::F8; + engineConfiguration->spi5mosiPin = Gpio::F9; + // Clear SPI SD card pins in case it's enabled from porting a Proteus tune if (engineConfiguration->sdCardSpiDevice || isBrainPinValid(engineConfiguration->sdCardCsPin)) { engineConfiguration->is_enabled_spi_3 = false; diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp index b1c217608a..7b22cfbc66 100644 --- a/firmware/controllers/system/g0_firmware_loader.cpp +++ b/firmware/controllers/system/g0_firmware_loader.cpp @@ -303,11 +303,6 @@ bool loadG0Firmware(bool forceUpdate) { #if G0_FIRMWARE_IMAGE_AVAILABLE initControlPins(); - engineConfiguration->is_enabled_spi_5 = true; - engineConfiguration->spi5sckPin = Gpio::F7; - engineConfiguration->spi5misoPin = Gpio::F8; - engineConfiguration->spi5mosiPin = Gpio::F9; - turnOnSpi(G0_SPI_DEVICE); SPIDriver* spi = getSpiDevice(G0_SPI_DEVICE); From 742c9332a360dc8533b1feccb3ebbb9a3b1125ea Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 3 May 2026 21:45:21 +0200 Subject: [PATCH 20/47] lets not have O0 here --- firmware/config/boards/atlas/compile_atlas.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/config/boards/atlas/compile_atlas.sh b/firmware/config/boards/atlas/compile_atlas.sh index 876d0acdfc..1c56a40803 100755 --- a/firmware/config/boards/atlas/compile_atlas.sh +++ b/firmware/config/boards/atlas/compile_atlas.sh @@ -33,7 +33,7 @@ esac G0_TRGT="$FW_DIR/ext/build-tools/$COMPILER_PLATFORM/bin/arm-none-eabi-" [ -x "${G0_TRGT}g++" ] || { echo "Compiler not found at ${G0_TRGT}g++"; exit 1; } -make -C "$G0_DIR" -j20 TRGT="$G0_TRGT" USE_OPT="-O0 -ggdb -fomit-frame-pointer -falign-functions=16 --specs=nosys.specs" for_fome_image +make -C "$G0_DIR" -j$(nproc) TRGT="$G0_TRGT" USE_OPT="-Os -ggdb -fomit-frame-pointer -falign-functions=16 --specs=nosys.specs" for_fome_image G0_IMAGE_HEADER="$G0_DIR/for_fome/g0_firmware_image.h" G0_APP_VERSION=$(sed -nE 's/.*APP_VERSION = ([0-9]+)U;.*/\1/p' "$G0_DIR/source/spi_app_protocol.cpp" | head -n1) From 7e7778f01f05f72853b9769c897571c663159396 Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 3 May 2026 22:24:10 +0200 Subject: [PATCH 21/47] release pins --- firmware/controllers/system/g0_firmware_loader.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp index 7b22cfbc66..7b197bf69b 100644 --- a/firmware/controllers/system/g0_firmware_loader.cpp +++ b/firmware/controllers/system/g0_firmware_loader.cpp @@ -62,6 +62,14 @@ static void initControlPins() { setPin(G0_RESET_PIN, true); } +static void releaseControlPins() { + setPin(G0_BOOT_PIN, false); + setPin(G0_RESET_PIN, true); + + efiSetPadMode("G0 BOOT", G0_BOOT_PIN, PAL_MODE_INPUT); + efiSetPadMode("G0 RESET", G0_RESET_PIN, PAL_MODE_INPUT); +} + static void resetG0(bool bootloaderMode) { setPin(G0_BOOT_PIN, bootloaderMode); chThdSleepMilliseconds(5); @@ -335,6 +343,7 @@ bool loadG0Firmware(bool forceUpdate) { } unlockSpi(G0_SPI_DEVICE); + releaseControlPins(); if (ok) { efiPrintf("G0 firmware load: complete"); From 6c57a4369a6f933ce499ad113169c5b328819967 Mon Sep 17 00:00:00 2001 From: dynfer Date: Mon, 4 May 2026 15:26:24 +0200 Subject: [PATCH 22/47] fix this --- firmware/config/boards/atlas/compile_atlas.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/firmware/config/boards/atlas/compile_atlas.sh b/firmware/config/boards/atlas/compile_atlas.sh index 1c56a40803..0f56f449be 100755 --- a/firmware/config/boards/atlas/compile_atlas.sh +++ b/firmware/config/boards/atlas/compile_atlas.sh @@ -36,7 +36,11 @@ G0_TRGT="$FW_DIR/ext/build-tools/$COMPILER_PLATFORM/bin/arm-none-eabi-" make -C "$G0_DIR" -j$(nproc) TRGT="$G0_TRGT" USE_OPT="-Os -ggdb -fomit-frame-pointer -falign-functions=16 --specs=nosys.specs" for_fome_image G0_IMAGE_HEADER="$G0_DIR/for_fome/g0_firmware_image.h" -G0_APP_VERSION=$(sed -nE 's/.*APP_VERSION = ([0-9]+)U;.*/\1/p' "$G0_DIR/source/spi_app_protocol.cpp" | head -n1) +G0_SPI_APP_PROTOCOL="$G0_DIR/spi_app_protocol.cpp" +if [ ! -f "$G0_SPI_APP_PROTOCOL" ]; then + G0_SPI_APP_PROTOCOL="$G0_DIR/source/spi_app_protocol.cpp" +fi +G0_APP_VERSION=$(sed -nE 's/.*(APP_VERSION|appVersion) = ([0-9]+)U;.*/\2/p' "$G0_SPI_APP_PROTOCOL" | head -n1) [ -n "$G0_APP_VERSION" ] || { echo "Unable to determine G0 app version"; exit 1; } if ! grep -q 'build_g0_extension_version' "$G0_IMAGE_HEADER"; then printf '\nstatic const unsigned int build_g0_extension_version = %sU;\n' "$G0_APP_VERSION" >> "$G0_IMAGE_HEADER" From 64ebfec30f3efab15f9d392a74c57f5f06d4f5ba Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 5 May 2026 09:38:49 +0200 Subject: [PATCH 23/47] bump the commit --- firmware/ext/g0_firmware | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index e19db16b60..c6e36fb04e 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit e19db16b60496942b78492533d3b0b48f0a25075 +Subproject commit c6e36fb04e8ecf474e67476ebf34d1e495e492bc From 2f50679349496eb7e78705f1e37d84df473575f9 Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 5 May 2026 10:04:11 +0200 Subject: [PATCH 24/47] further work --- firmware/config/boards/atlas/compile_atlas.sh | 5 +++-- firmware/controllers/system/g0_firmware_loader.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/firmware/config/boards/atlas/compile_atlas.sh b/firmware/config/boards/atlas/compile_atlas.sh index 0f56f449be..e7dc895601 100755 --- a/firmware/config/boards/atlas/compile_atlas.sh +++ b/firmware/config/boards/atlas/compile_atlas.sh @@ -9,8 +9,9 @@ G0_DIR="$FW_DIR/ext/g0_firmware" export USE_OPENBLT=yes -if [ -e "$G0_DIR/.git" ]; then - git -C "$G0_DIR" submodule update --init --recursive +git -C "$REPO_DIR" submodule sync firmware/ext/g0_firmware +if [ "${UPDATE_G0_FIRMWARE-yes}" = "yes" ]; then + git -C "$REPO_DIR" submodule update --init --recursive --remote firmware/ext/g0_firmware else git -C "$REPO_DIR" submodule update --init --recursive firmware/ext/g0_firmware fi diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp index 7b197bf69b..812d020487 100644 --- a/firmware/controllers/system/g0_firmware_loader.cpp +++ b/firmware/controllers/system/g0_firmware_loader.cpp @@ -318,7 +318,7 @@ bool loadG0Firmware(bool forceUpdate) { palSetPad(g0SpiConfig.ssport, g0SpiConfig.sspad); spiStart(spi, &g0SpiConfig); - lockSpi(G0_SPI_DEVICE); + spiAcquireBus(spi); resetG0(false); uint32_t currentVersion = 0; @@ -342,7 +342,7 @@ bool loadG0Firmware(bool forceUpdate) { ok = injectG0FirmwareWithRetry(spi); } - unlockSpi(G0_SPI_DEVICE); + spiReleaseBus(spi); releaseControlPins(); if (ok) { From f18a404eec0891b251ec007ab9943f9812d69ef0 Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 5 May 2026 20:00:42 +0200 Subject: [PATCH 25/47] bump g0 --- firmware/ext/g0_firmware | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index c6e36fb04e..fe06e8d6c3 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit c6e36fb04e8ecf474e67476ebf34d1e495e492bc +Subproject commit fe06e8d6c3b5124cc3aaec8ae9a961b5353ca98e From a6749c3705e2f0c407be95fd5639d79d65e1f565 Mon Sep 17 00:00:00 2001 From: dynfer Date: Fri, 8 May 2026 10:21:37 +0200 Subject: [PATCH 26/47] new implementation --- .../boards/atlas/connectors/analog.yaml | 48 ++++ firmware/config/boards/atlas_meta.h | 13 + firmware/controllers/algo/rusefi_hw_enums.h | 13 + firmware/controllers/controllers.mk | 1 - .../controllers/system/g0_firmware_loader.cpp | 19 +- firmware/controllers/system/system.mk | 1 + firmware/ext/g0_firmware | 2 +- firmware/hw_layer/g0_gpio/g0_analog.cpp | 223 ++++++++++++++++++ firmware/hw_layer/g0_gpio/g0_analog.h | 6 + firmware/hw_layer/hw_layer.mk | 1 + firmware/rusefi.cpp | 2 + 11 files changed, 320 insertions(+), 9 deletions(-) create mode 100644 firmware/hw_layer/g0_gpio/g0_analog.cpp create mode 100644 firmware/hw_layer/g0_gpio/g0_analog.h diff --git a/firmware/config/boards/atlas/connectors/analog.yaml b/firmware/config/boards/atlas/connectors/analog.yaml index ab5225eaa6..096aaeb554 100644 --- a/firmware/config/boards/atlas/connectors/analog.yaml +++ b/firmware/config/boards/atlas/connectors/analog.yaml @@ -74,3 +74,51 @@ pins: - id: EFI_ADC_18 class: analog_inputs ts_name: VBoost Sense + + - id: EFI_ADC_19 + class: analog_inputs + ts_name: G0 Analog 1 + + - id: EFI_ADC_20 + class: analog_inputs + ts_name: G0 Analog 2 + + - id: EFI_ADC_21 + class: analog_inputs + ts_name: G0 Analog 3 + + - id: EFI_ADC_22 + class: analog_inputs + ts_name: G0 Analog 4 + + - id: EFI_ADC_23 + class: analog_inputs + ts_name: G0 Analog 5 + + - id: EFI_ADC_24 + class: analog_inputs + ts_name: G0 Analog 6 + + - id: EFI_ADC_25 + class: analog_inputs + ts_name: G0 Analog 7 + + - id: EFI_ADC_26 + class: analog_inputs + ts_name: G0 Analog 8 + + - id: EFI_ADC_27 + class: analog_inputs + ts_name: G0 Analog Temp 1 + + - id: EFI_ADC_28 + class: analog_inputs + ts_name: G0 Analog Temp 2 + + - id: EFI_ADC_29 + class: analog_inputs + ts_name: G0 Analog Temp 3 + + - id: EFI_ADC_30 + class: analog_inputs + ts_name: G0 Analog Temp 4 diff --git a/firmware/config/boards/atlas_meta.h b/firmware/config/boards/atlas_meta.h index 52839fe571..97705c32f1 100644 --- a/firmware/config/boards/atlas_meta.h +++ b/firmware/config/boards/atlas_meta.h @@ -65,3 +65,16 @@ #define PROTEUS_DIGITAL_4 Gpio::E11 #define PROTEUS_DIGITAL_5 Gpio::E12 #define PROTEUS_DIGITAL_6 Gpio::E13 + +#define ATLAS_G0_ANALOG_1 EFI_ADC_20 +#define ATLAS_G0_ANALOG_2 EFI_ADC_21 +#define ATLAS_G0_ANALOG_3 EFI_ADC_22 +#define ATLAS_G0_ANALOG_4 EFI_ADC_23 +#define ATLAS_G0_ANALOG_5 EFI_ADC_24 +#define ATLAS_G0_ANALOG_6 EFI_ADC_25 +#define ATLAS_G0_ANALOG_7 EFI_ADC_26 +#define ATLAS_G0_ANALOG_8 EFI_ADC_27 +#define ATLAS_G0_ANALOG_TEMP_1 EFI_ADC_28 +#define ATLAS_G0_ANALOG_TEMP_2 EFI_ADC_29 +#define ATLAS_G0_ANALOG_TEMP_3 EFI_ADC_30 +#define ATLAS_G0_ANALOG_TEMP_4 EFI_ADC_31 \ No newline at end of file diff --git a/firmware/controllers/algo/rusefi_hw_enums.h b/firmware/controllers/algo/rusefi_hw_enums.h index 69a82e1dc4..51c1a917d4 100644 --- a/firmware/controllers/algo/rusefi_hw_enums.h +++ b/firmware/controllers/algo/rusefi_hw_enums.h @@ -234,6 +234,19 @@ enum class Gpio : uint16_t { TLE6240_PIN_15 = 213, TLE6240_PIN_16 = 214, + G0_ANALOG_1 = 215, + G0_ANALOG_2 = 216, + G0_ANALOG_3 = 217, + G0_ANALOG_4 = 218, + G0_ANALOG_5 = 219, + G0_ANALOG_6 = 220, + G0_ANALOG_7 = 221, + G0_ANALOG_8 = 222, + G0_ANALOG_TEMP_1 = 223, + G0_ANALOG_TEMP_2 = 224, + G0_ANALOG_TEMP_3 = 225, + G0_ANALOG_TEMP_4 = 226, + CAN_INPUT_0 = 249, CAN_INPUT_1 = 250, CAN_INPUT_2 = 251, diff --git a/firmware/controllers/controllers.mk b/firmware/controllers/controllers.mk index bb042474d0..c769b3bac9 100644 --- a/firmware/controllers/controllers.mk +++ b/firmware/controllers/controllers.mk @@ -29,7 +29,6 @@ CONTROLLERS_SRC_CPP = \ $(CONTROLLERS_DIR)/system/timer/event_queue.cpp \ $(CONTROLLERS_DIR)/settings.cpp \ $(CONTROLLERS_DIR)/core/error_handling.cpp \ - $(CONTROLLERS_DIR)/system/g0_firmware_loader.cpp \ $(CONTROLLERS_DIR)/engine_cycle/high_pressure_fuel_pump.cpp \ $(CONTROLLERS_DIR)/engine_cycle/rpm_calculator.cpp \ $(CONTROLLERS_DIR)/engine_cycle/spark_logic.cpp \ diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp index 812d020487..085af51d32 100644 --- a/firmware/controllers/system/g0_firmware_loader.cpp +++ b/firmware/controllers/system/g0_firmware_loader.cpp @@ -36,8 +36,10 @@ static constexpr uint8_t G0_APP_CMD_ENTER_UPDATE = 0xA5; static constexpr uint8_t G0_APP_STATUS_READY = 0x00; static constexpr uint8_t G0_APP_STATUS_UPDATE_MODE = 0x01; -static constexpr size_t G0_APP_FRAME_SIZE = 8; +static constexpr size_t G0_APP_FRAME_SIZE = 36; static constexpr size_t G0_SPI_DMA_BUFFER_SIZE = 258; +static constexpr uint8_t G0_APP_RESULT_OK = 0x01; +static constexpr uint8_t G0_APP_HEADER_SIZE = 4; static NO_CACHE uint8_t g0SpiTxBuffer[G0_SPI_DMA_BUFFER_SIZE]; static NO_CACHE uint8_t g0SpiRxBuffer[G0_SPI_DMA_BUFFER_SIZE]; @@ -66,8 +68,8 @@ static void releaseControlPins() { setPin(G0_BOOT_PIN, false); setPin(G0_RESET_PIN, true); - efiSetPadMode("G0 BOOT", G0_BOOT_PIN, PAL_MODE_INPUT); - efiSetPadMode("G0 RESET", G0_RESET_PIN, PAL_MODE_INPUT); + efiSetPadModeWithoutOwnershipAcquisition("G0 BOOT", G0_BOOT_PIN, PAL_MODE_INPUT); + efiSetPadModeWithoutOwnershipAcquisition("G0 RESET", G0_RESET_PIN, PAL_MODE_INPUT); } static void resetG0(bool bootloaderMode) { @@ -212,10 +214,10 @@ static void exchangeG0AppFrame(SPIDriver* spi, uint8_t command, uint8_t* rx) { memcpy(rx, g0SpiRxBuffer, G0_APP_FRAME_SIZE); } -static bool isG0AppResponse(const uint8_t* rx, uint8_t expectedLastCommand) { +static bool isG0AppResponse(const uint8_t* rx, uint8_t expectedCommand, uint8_t expectedPayloadLength) { const bool knownStatus = rx[0] == G0_APP_STATUS_READY || rx[0] == G0_APP_STATUS_UPDATE_MODE; - return knownStatus && rx[5] == expectedLastCommand && rx[6] == 0 && rx[7] == 0; + return knownStatus && rx[1] == G0_APP_RESULT_OK && rx[2] == expectedCommand && rx[3] == expectedPayloadLength; } static bool readG0AppVersion(SPIDriver* spi, uint32_t& version) { @@ -225,11 +227,14 @@ static bool readG0AppVersion(SPIDriver* spi, uint32_t& version) { chThdSleepMilliseconds(1); exchangeG0AppFrame(spi, G0_APP_CMD_NOP, rx); - if (!isG0AppResponse(rx, G0_APP_CMD_READ_VERSION)) { + + if (!isG0AppResponse(rx, G0_APP_CMD_READ_VERSION, 4)) { + efiPrintf("G0 firmware load: invalid version response %02X %02X %02X %02X", rx[0], rx[1], rx[2], rx[3]); + return false; } - version = readLe32(&rx[1]); + version = readLe32(&rx[G0_APP_HEADER_SIZE]); return true; } diff --git a/firmware/controllers/system/system.mk b/firmware/controllers/system/system.mk index 889b651218..3416e59cff 100644 --- a/firmware/controllers/system/system.mk +++ b/firmware/controllers/system/system.mk @@ -6,3 +6,4 @@ SYSTEMSRC_CPP = \ $(PROJECT_DIR)/controllers/system/dc_motor.cpp \ $(PROJECT_DIR)/controllers/system/timer/scheduler.cpp \ $(PROJECT_DIR)/controllers/system/timer/trigger_scheduler.cpp \ + $(PROJECT_DIR)/controllers/system/g0_firmware_loader.cpp diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index fe06e8d6c3..a7861d5a10 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit fe06e8d6c3b5124cc3aaec8ae9a961b5353ca98e +Subproject commit a7861d5a102f9aa6dd7eb7918d1e660f3099bf4d diff --git a/firmware/hw_layer/g0_gpio/g0_analog.cpp b/firmware/hw_layer/g0_gpio/g0_analog.cpp new file mode 100644 index 0000000000..8cf391d4b8 --- /dev/null +++ b/firmware/hw_layer/g0_gpio/g0_analog.cpp @@ -0,0 +1,223 @@ +#include "g0_analog.h" + +namespace { + +static constexpr size_t G070_ANALOG_CHANNEL_COUNT = 12; +static constexpr uint8_t spiAppFrameSize = 36U; +static constexpr uint8_t spiCmdReadAnalog = 0x10U; +static constexpr uint8_t spiCmdNop = 0x00U; +static constexpr uint8_t spiAppHeaderSize = 4U; +static constexpr uint8_t spiResultOk = 0x01U; +static constexpr spi_device_e G0_SPI_DEVICE = SPI_DEVICE_5; +static constexpr brain_pin_e G0_SPI_CS_PIN = Gpio::F6; + +static SPIConfig g0AnalogSpiConfig = { + .circular = false, + .end_cb = NULL, + .ssport = NULL, + .sspad = 0, + .cfg1 = 7 | SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, + .cfg2 = 0}; + +static NO_CACHE uint8_t txBuf[spiAppFrameSize]; +static NO_CACHE uint8_t rxBuf[spiAppFrameSize]; + +class G070SpiAdcProvider final : public AdcProvider { +public: + const char* name() const override { + return "G070SpiAdc"; + } + + SPIDriver* spiDevice = nullptr; + + bool enable(const char* name, size_t idx) override { + (void)name; + + if (idx >= G070_ANALOG_CHANNEL_COUNT) { + return false; + } + + m_enabled[idx] = true; + return true; + } + + void disable(size_t idx) override { + if (idx >= G070_ANALOG_CHANNEL_COUNT) { + return; + } + + m_enabled[idx] = false; + } + + float get(size_t idx) const override { + if (idx >= G070_ANALOG_CHANNEL_COUNT) { + return 0.0f; + } + + if (!m_ready) { + return 0.0f; + } + + return m_millivolts[idx] * 0.001f; + } + + void start() { + turnOnSpi(G0_SPI_DEVICE); + spiDevice = getSpiDevice(G0_SPI_DEVICE); + initSpiCs(&g0AnalogSpiConfig, G0_SPI_CS_PIN); + palSetPad(g0AnalogSpiConfig.ssport, g0AnalogSpiConfig.sspad); + if (spiDevice->state != SPI_READY) { + spiStart(spiDevice, &g0AnalogSpiConfig); + } else { + spiStop(spiDevice); + spiStart(spiDevice, &g0AnalogSpiConfig); + efiPrintf("G070 SPI ADC provider: SPI device was already ready, restarted it\n"); + } + + chThdCreateStatic(m_threadWa, sizeof(m_threadWa), NORMALPRIO, threadEntry, this); + } + +private: + static THD_FUNCTION(threadEntry, arg) { + static_cast(arg)->thread(); + } + + void thread() { + chRegSetThreadName("G070 ADC"); + + while (true) { + pollAnalog(); + + // 50 Hz update rate + chThdSleepMilliseconds(20); + } + } + + void pollAnalog() { + clearFrame(txBuf); + clearFrame(rxBuf); + /* + * First frame: + * Send READ_ANALOG command. + * The response received here is from the previous command, so ignore it. + */ + txBuf[0] = spiCmdReadAnalog; + + spiAcquireBus(spiDevice); + spiSelect(spiDevice); + spiExchange(spiDevice, spiAppFrameSize, txBuf, rxBuf); + spiUnselect(spiDevice); + + /* + * Second frame: + * Send NOP to clock out the READ_ANALOG response. + */ + clearFrame(txBuf); + clearFrame(rxBuf); + + txBuf[0] = spiCmdNop; + + spiSelect(spiDevice); + spiExchange(spiDevice, spiAppFrameSize, txBuf, rxBuf); + spiUnselect(spiDevice); + + parseAnalogResponse(rxBuf); + } + + void parseAnalogResponse(const uint8_t* rx) { + const uint8_t status = rx[0]; + const uint8_t result = rx[1]; + const uint8_t command = rx[2]; + const uint8_t payloadLength = rx[3]; + + static constexpr size_t printThrottle = 10; + static size_t printCounter = 0; + + const bool shouldPrint = (++printCounter >= printThrottle); + + if (shouldPrint) { + printCounter = 0; + efiPrintf( + "G070 ADC response: status=0x%02X result=0x%02X command=0x%02X payloadLength=%d\n", + status, + result, + command, + payloadLength); + } + + (void)status; + + if (result != spiResultOk) { + m_ready = false; + return; + } + + if (command != spiCmdReadAnalog) { + return; + } + + if (payloadLength < 2) { + m_ready = false; + return; + } + + const bool analogReady = rx[spiAppHeaderSize] != 0; + const uint8_t channelCount = rx[spiAppHeaderSize + 1]; + + if (!analogReady) { + m_ready = false; + return; + } + + const size_t count = channelCount < G070_ANALOG_CHANNEL_COUNT ? channelCount : G070_ANALOG_CHANNEL_COUNT; + + for (size_t i = 0; i < count; i++) { + const uint8_t offset = static_cast(spiAppHeaderSize + 2 + i * 2); + + m_millivolts[i] = getU16(rx, offset); + } + + if (shouldPrint) { + + efiPrintf("G070 ADC values (mV):"); + for (size_t i = 0; i < count; i++) { + efiPrintf(" %d", m_millivolts[i]); + } + efiPrintf("\n"); + } + + m_ready = true; + } + + static void clearFrame(uint8_t* frame) { + for (size_t i = 0; i < spiAppFrameSize; i++) { + frame[i] = 0; + } + } + + static uint16_t getU16(const uint8_t* buffer, uint8_t offset) { + return static_cast(buffer[offset]) | + static_cast(static_cast(buffer[offset + 1]) << 8); + } + +private: + bool m_enabled[G070_ANALOG_CHANNEL_COUNT] = {}; + volatile bool m_ready = false; + volatile uint16_t m_millivolts[G070_ANALOG_CHANNEL_COUNT] = {}; + + THD_WORKING_AREA(m_threadWa, 512); +}; + +} // namespace + +namespace { + +static G070SpiAdcProvider g070SpiAdcProvider; + +} + +void startG070SpiAdcProvider() { + g070SpiAdcProvider.start(); + + registerAdcProvider(g070SpiAdcProvider, EFI_ADC_19, G070_ANALOG_CHANNEL_COUNT); +} diff --git a/firmware/hw_layer/g0_gpio/g0_analog.h b/firmware/hw_layer/g0_gpio/g0_analog.h new file mode 100644 index 0000000000..309ea7c3a9 --- /dev/null +++ b/firmware/hw_layer/g0_gpio/g0_analog.h @@ -0,0 +1,6 @@ +#pragma once + +#include "pch.h" +#include "adc_provider.h" + +void startG070SpiAdcProvider(); diff --git a/firmware/hw_layer/hw_layer.mk b/firmware/hw_layer/hw_layer.mk index a54cf76e3a..0690e6cd2e 100644 --- a/firmware/hw_layer/hw_layer.mk +++ b/firmware/hw_layer/hw_layer.mk @@ -30,6 +30,7 @@ HW_LAYER_EMS_CPP = \ $(PROJECT_DIR)/hw_layer/io_pins.cpp \ $(PROJECT_DIR)/hw_layer/rtc_helper.cpp \ $(PROJECT_DIR)/hw_layer/debounce.cpp \ + $(PROJECT_DIR)/hw_layer/g0_gpio/g0_analog.cpp ifeq ($(USE_OPENBLT),yes) HW_LAYER_EMS += \ diff --git a/firmware/rusefi.cpp b/firmware/rusefi.cpp index adf54aadf8..817a2b9c87 100644 --- a/firmware/rusefi.cpp +++ b/firmware/rusefi.cpp @@ -117,6 +117,7 @@ #include "rusefi_lua.h" #include "bootloader_updater.h" #include "g0_firmware_loader.h" +#include "hw_layer/g0_gpio/g0_analog.h" #include @@ -210,6 +211,7 @@ void runRusEfi() { #if HW_ATLAS loadG0Firmware(); + startG070SpiAdcProvider(); #endif #if EFI_TUNER_STUDIO From a77868573c95b756ce22f2687cab797b72d88bd4 Mon Sep 17 00:00:00 2001 From: dynfer Date: Fri, 8 May 2026 11:11:22 +0200 Subject: [PATCH 27/47] working analog inputs via provider --- .../controllers/system/g0_firmware_loader.cpp | 2 +- firmware/hw_layer/g0_gpio/g0_analog.cpp | 91 ++++++++++++------- .../hw_layer/ports/stm32/stm32_common.cpp | 2 +- 3 files changed, 61 insertions(+), 34 deletions(-) diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp index 085af51d32..8fb6b67e34 100644 --- a/firmware/controllers/system/g0_firmware_loader.cpp +++ b/firmware/controllers/system/g0_firmware_loader.cpp @@ -38,7 +38,7 @@ static constexpr uint8_t G0_APP_STATUS_READY = 0x00; static constexpr uint8_t G0_APP_STATUS_UPDATE_MODE = 0x01; static constexpr size_t G0_APP_FRAME_SIZE = 36; static constexpr size_t G0_SPI_DMA_BUFFER_SIZE = 258; -static constexpr uint8_t G0_APP_RESULT_OK = 0x01; +static constexpr uint8_t G0_APP_RESULT_OK = 0x0; static constexpr uint8_t G0_APP_HEADER_SIZE = 4; static NO_CACHE uint8_t g0SpiTxBuffer[G0_SPI_DMA_BUFFER_SIZE]; diff --git a/firmware/hw_layer/g0_gpio/g0_analog.cpp b/firmware/hw_layer/g0_gpio/g0_analog.cpp index 8cf391d4b8..bda18d59e4 100644 --- a/firmware/hw_layer/g0_gpio/g0_analog.cpp +++ b/firmware/hw_layer/g0_gpio/g0_analog.cpp @@ -7,7 +7,10 @@ static constexpr uint8_t spiAppFrameSize = 36U; static constexpr uint8_t spiCmdReadAnalog = 0x10U; static constexpr uint8_t spiCmdNop = 0x00U; static constexpr uint8_t spiAppHeaderSize = 4U; -static constexpr uint8_t spiResultOk = 0x01U; +static constexpr uint8_t spiResultOk = 0x00U; +static constexpr uint8_t spiResultInvalid = 0x01U; +static constexpr uint8_t spiResultRange = 0x02U; +static constexpr uint8_t spiResultBusy = 0x03U; static constexpr spi_device_e G0_SPI_DEVICE = SPI_DEVICE_5; static constexpr brain_pin_e G0_SPI_CS_PIN = Gpio::F6; @@ -62,22 +65,28 @@ class G070SpiAdcProvider final : public AdcProvider { } void start() { + if (m_started) { + efiPrintf("G070 SPI ADC provider: already started"); + return; + } + + m_started = true; + turnOnSpi(G0_SPI_DEVICE); + spiDevice = getSpiDevice(G0_SPI_DEVICE); + initSpiCs(&g0AnalogSpiConfig, G0_SPI_CS_PIN); palSetPad(g0AnalogSpiConfig.ssport, g0AnalogSpiConfig.sspad); - if (spiDevice->state != SPI_READY) { - spiStart(spiDevice, &g0AnalogSpiConfig); - } else { - spiStop(spiDevice); - spiStart(spiDevice, &g0AnalogSpiConfig); - efiPrintf("G070 SPI ADC provider: SPI device was already ready, restarted it\n"); - } + + spiStart(spiDevice, &g0AnalogSpiConfig); chThdCreateStatic(m_threadWa, sizeof(m_threadWa), NORMALPRIO, threadEntry, this); } private: + bool m_started = false; + static THD_FUNCTION(threadEntry, arg) { static_cast(arg)->thread(); } @@ -93,34 +102,36 @@ class G070SpiAdcProvider final : public AdcProvider { } } - void pollAnalog() { - clearFrame(txBuf); - clearFrame(rxBuf); - /* - * First frame: - * Send READ_ANALOG command. - * The response received here is from the previous command, so ignore it. - */ - txBuf[0] = spiCmdReadAnalog; + static void printFrame(const char* name, const uint8_t* rx) { + efiPrintf( + "%s: %02X %02X %02X %02X %02X %02X %02X %02X", + name, + rx[0], + rx[1], + rx[2], + rx[3], + rx[4], + rx[5], + rx[6], + rx[7]); + } + void pollAnalog() { spiAcquireBus(spiDevice); - spiSelect(spiDevice); - spiExchange(spiDevice, spiAppFrameSize, txBuf, rxBuf); - spiUnselect(spiDevice); - /* - * Second frame: - * Send NOP to clock out the READ_ANALOG response. - */ clearFrame(txBuf); clearFrame(rxBuf); - txBuf[0] = spiCmdNop; + // Always request the next analog frame. + // The received frame is the response to the previous command. + txBuf[0] = spiCmdReadAnalog; spiSelect(spiDevice); spiExchange(spiDevice, spiAppFrameSize, txBuf, rxBuf); spiUnselect(spiDevice); + spiReleaseBus(spiDevice); + parseAnalogResponse(rxBuf); } @@ -130,22 +141,26 @@ class G070SpiAdcProvider final : public AdcProvider { const uint8_t command = rx[2]; const uint8_t payloadLength = rx[3]; - static constexpr size_t printThrottle = 10; + static constexpr size_t printThrottle = 50; static size_t printCounter = 0; const bool shouldPrint = (++printCounter >= printThrottle); if (shouldPrint) { printCounter = 0; + efiPrintf( - "G070 ADC response: status=0x%02X result=0x%02X command=0x%02X payloadLength=%d\n", + "G070 ADC response: status=0x%02X result=0x%02X command=0x%02X payloadLength=%d", status, result, command, payloadLength); } - (void)status; + if (status != 0x00 && status != 0x01) { + m_ready = false; + return; + } if (result != spiResultOk) { m_ready = false; @@ -153,10 +168,12 @@ class G070SpiAdcProvider final : public AdcProvider { } if (command != spiCmdReadAnalog) { + // This is often a valid NOP response, not necessarily garbage. + m_ready = false; return; } - if (payloadLength < 2) { + if (payloadLength != 26) { m_ready = false; return; } @@ -178,12 +195,13 @@ class G070SpiAdcProvider final : public AdcProvider { } if (shouldPrint) { - efiPrintf("G070 ADC values (mV):"); + for (size_t i = 0; i < count; i++) { efiPrintf(" %d", m_millivolts[i]); } - efiPrintf("\n"); + + efiPrintf(""); } m_ready = true; @@ -217,7 +235,16 @@ static G070SpiAdcProvider g070SpiAdcProvider; } void startG070SpiAdcProvider() { + static bool started = false; + + if (started) { + efiPrintf("startG070SpiAdcProvider: already started"); + return; + } + + started = true; + g070SpiAdcProvider.start(); - registerAdcProvider(g070SpiAdcProvider, EFI_ADC_19, G070_ANALOG_CHANNEL_COUNT); + registerAdcProvider(g070SpiAdcProvider, EFI_ADC_19 - EFI_ADC_0, G070_ANALOG_CHANNEL_COUNT); } diff --git a/firmware/hw_layer/ports/stm32/stm32_common.cpp b/firmware/hw_layer/ports/stm32/stm32_common.cpp index dce93141e7..74eaee722c 100644 --- a/firmware/hw_layer/ports/stm32/stm32_common.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_common.cpp @@ -657,7 +657,7 @@ void initSpiCs(SPIConfig* spiConfig, brain_pin_e csPin) { ioportmask_t pin = getHwPin("spi", csPin); spiConfig->ssport = port; spiConfig->sspad = pin; - efiSetPadMode("chip select", csPin, PAL_STM32_MODE_OUTPUT); + efiSetPadModeWithoutOwnershipAcquisition("chip select", csPin, PAL_STM32_MODE_OUTPUT); } #ifdef STM32H7XX From 55ae05b3d793daaa653294bce32984b5e9339a83 Mon Sep 17 00:00:00 2001 From: dynfer Date: Fri, 8 May 2026 11:20:45 +0200 Subject: [PATCH 28/47] disable debug printing --- firmware/hw_layer/g0_gpio/g0_analog.cpp | 26 ++++++++----------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/firmware/hw_layer/g0_gpio/g0_analog.cpp b/firmware/hw_layer/g0_gpio/g0_analog.cpp index bda18d59e4..681c6a9a06 100644 --- a/firmware/hw_layer/g0_gpio/g0_analog.cpp +++ b/firmware/hw_layer/g0_gpio/g0_analog.cpp @@ -102,20 +102,6 @@ class G070SpiAdcProvider final : public AdcProvider { } } - static void printFrame(const char* name, const uint8_t* rx) { - efiPrintf( - "%s: %02X %02X %02X %02X %02X %02X %02X %02X", - name, - rx[0], - rx[1], - rx[2], - rx[3], - rx[4], - rx[5], - rx[6], - rx[7]); - } - void pollAnalog() { spiAcquireBus(spiDevice); @@ -141,11 +127,12 @@ class G070SpiAdcProvider final : public AdcProvider { const uint8_t command = rx[2]; const uint8_t payloadLength = rx[3]; - static constexpr size_t printThrottle = 50; - static size_t printCounter = 0; + // static constexpr size_t printThrottle = 50; + // static size_t printCounter = 0; - const bool shouldPrint = (++printCounter >= printThrottle); + // const bool shouldPrint = (++printCounter >= printThrottle); + /* if (shouldPrint) { printCounter = 0; @@ -156,6 +143,7 @@ class G070SpiAdcProvider final : public AdcProvider { command, payloadLength); } + */ if (status != 0x00 && status != 0x01) { m_ready = false; @@ -194,6 +182,7 @@ class G070SpiAdcProvider final : public AdcProvider { m_millivolts[i] = getU16(rx, offset); } + /* if (shouldPrint) { efiPrintf("G070 ADC values (mV):"); @@ -203,6 +192,7 @@ class G070SpiAdcProvider final : public AdcProvider { efiPrintf(""); } + */ m_ready = true; } @@ -246,5 +236,5 @@ void startG070SpiAdcProvider() { g070SpiAdcProvider.start(); - registerAdcProvider(g070SpiAdcProvider, EFI_ADC_19 - EFI_ADC_0, G070_ANALOG_CHANNEL_COUNT); + registerAdcProvider(g070SpiAdcProvider, EFI_ADC_19, G070_ANALOG_CHANNEL_COUNT); } From ed974cccf1b5bf939ac31eecc925e5469098d995 Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 11:17:44 +0200 Subject: [PATCH 29/47] bump --- firmware/ext/g0_firmware | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index a7861d5a10..d7045bbe8c 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit a7861d5a102f9aa6dd7eb7918d1e660f3099bf4d +Subproject commit d7045bbe8cea111256e036c317539ad3fc107e5d From 53efa6a575892478d2685ddbb946d59dba8b6cbb Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 11:20:20 +0200 Subject: [PATCH 30/47] bump again --- firmware/ext/g0_firmware | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index d7045bbe8c..75391405f5 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit d7045bbe8cea111256e036c317539ad3fc107e5d +Subproject commit 75391405f50f7351fcb8e5d766e65036dd3327bd From b074d6b08a1db349960266fc64802c8ceb1bd011 Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 11:42:14 +0200 Subject: [PATCH 31/47] enable hw oversampling on the g0 --- firmware/ext/g0_firmware | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index 75391405f5..35156158bb 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit 75391405f50f7351fcb8e5d766e65036dd3327bd +Subproject commit 35156158bb6949cf59a128cf9ba182184c0495e3 From 9ed964ac0a2dbefbb6d9cc5770cd02d7740e139c Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 12:03:39 +0200 Subject: [PATCH 32/47] change the polling rate and fix the adc channels --- .../boards/atlas/connectors/analog.yaml | 24 +++++++++---------- firmware/config/boards/atlas_meta.h | 2 +- .../controllers/system/g0_firmware_loader.cpp | 4 ++-- firmware/hw_layer/g0_gpio/g0_analog.cpp | 8 ++++--- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/firmware/config/boards/atlas/connectors/analog.yaml b/firmware/config/boards/atlas/connectors/analog.yaml index 096aaeb554..1a47e4a1a8 100644 --- a/firmware/config/boards/atlas/connectors/analog.yaml +++ b/firmware/config/boards/atlas/connectors/analog.yaml @@ -75,50 +75,50 @@ pins: class: analog_inputs ts_name: VBoost Sense - - id: EFI_ADC_19 + - id: EFI_ADC_20 class: analog_inputs ts_name: G0 Analog 1 - - id: EFI_ADC_20 + - id: EFI_ADC_21 class: analog_inputs ts_name: G0 Analog 2 - - id: EFI_ADC_21 + - id: EFI_ADC_22 class: analog_inputs ts_name: G0 Analog 3 - - id: EFI_ADC_22 + - id: EFI_ADC_23 class: analog_inputs ts_name: G0 Analog 4 - - id: EFI_ADC_23 + - id: EFI_ADC_24 class: analog_inputs ts_name: G0 Analog 5 - - id: EFI_ADC_24 + - id: EFI_ADC_25 class: analog_inputs ts_name: G0 Analog 6 - - id: EFI_ADC_25 + - id: EFI_ADC_26 class: analog_inputs ts_name: G0 Analog 7 - - id: EFI_ADC_26 + - id: EFI_ADC_27 class: analog_inputs ts_name: G0 Analog 8 - - id: EFI_ADC_27 + - id: EFI_ADC_28 class: analog_inputs ts_name: G0 Analog Temp 1 - - id: EFI_ADC_28 + - id: EFI_ADC_29 class: analog_inputs ts_name: G0 Analog Temp 2 - - id: EFI_ADC_29 + - id: EFI_ADC_30 class: analog_inputs ts_name: G0 Analog Temp 3 - - id: EFI_ADC_30 + - id: EFI_ADC_31 class: analog_inputs ts_name: G0 Analog Temp 4 diff --git a/firmware/config/boards/atlas_meta.h b/firmware/config/boards/atlas_meta.h index 97705c32f1..59c36fc777 100644 --- a/firmware/config/boards/atlas_meta.h +++ b/firmware/config/boards/atlas_meta.h @@ -77,4 +77,4 @@ #define ATLAS_G0_ANALOG_TEMP_1 EFI_ADC_28 #define ATLAS_G0_ANALOG_TEMP_2 EFI_ADC_29 #define ATLAS_G0_ANALOG_TEMP_3 EFI_ADC_30 -#define ATLAS_G0_ANALOG_TEMP_4 EFI_ADC_31 \ No newline at end of file +#define ATLAS_G0_ANALOG_TEMP_4 EFI_ADC_31 diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp index 8fb6b67e34..20bdd9c397 100644 --- a/firmware/controllers/system/g0_firmware_loader.cpp +++ b/firmware/controllers/system/g0_firmware_loader.cpp @@ -319,11 +319,11 @@ bool loadG0Firmware(bool forceUpdate) { turnOnSpi(G0_SPI_DEVICE); SPIDriver* spi = getSpiDevice(G0_SPI_DEVICE); + spiAcquireBus(spi); + initSpiCs(&g0SpiConfig, G0_SPI_CS_PIN); palSetPad(g0SpiConfig.ssport, g0SpiConfig.sspad); spiStart(spi, &g0SpiConfig); - - spiAcquireBus(spi); resetG0(false); uint32_t currentVersion = 0; diff --git a/firmware/hw_layer/g0_gpio/g0_analog.cpp b/firmware/hw_layer/g0_gpio/g0_analog.cpp index 681c6a9a06..29a224133d 100644 --- a/firmware/hw_layer/g0_gpio/g0_analog.cpp +++ b/firmware/hw_layer/g0_gpio/g0_analog.cpp @@ -13,6 +13,9 @@ static constexpr uint8_t spiResultRange = 0x02U; static constexpr uint8_t spiResultBusy = 0x03U; static constexpr spi_device_e G0_SPI_DEVICE = SPI_DEVICE_5; static constexpr brain_pin_e G0_SPI_CS_PIN = Gpio::F6; +static constexpr size_t G0_FIRST_ADC_INDEX = EFI_ADC_20 - EFI_ADC_0; +// Edit this value to tune the fixed G0 ADC poll rate. 10 ms = 100 Hz. +static constexpr uint32_t G0_POLL_INTERVAL_MS = 10; static SPIConfig g0AnalogSpiConfig = { .circular = false, @@ -97,8 +100,7 @@ class G070SpiAdcProvider final : public AdcProvider { while (true) { pollAnalog(); - // 50 Hz update rate - chThdSleepMilliseconds(20); + chThdSleepMilliseconds(G0_POLL_INTERVAL_MS); } } @@ -236,5 +238,5 @@ void startG070SpiAdcProvider() { g070SpiAdcProvider.start(); - registerAdcProvider(g070SpiAdcProvider, EFI_ADC_19, G070_ANALOG_CHANNEL_COUNT); + registerAdcProvider(g070SpiAdcProvider, G0_FIRST_ADC_INDEX, G070_ANALOG_CHANNEL_COUNT); } From 0127b65d2a17596417d356ad1d3cc0f70b3a904b Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 12:33:31 +0200 Subject: [PATCH 33/47] implement g0 ditigal inputs --- .../boards/atlas/connectors/analog.yaml | 48 -------- .../config/boards/atlas/connectors/g0.yaml | 64 ++++++++++ firmware/controllers/algo/rusefi_hw_enums.h | 4 + firmware/hw_layer/g0_gpio/g0_analog.cpp | 111 ++++++++++++++++-- firmware/hw_layer/g0_gpio/g0_analog.h | 1 + firmware/hw_layer/io_pins.cpp | 5 + 6 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 firmware/config/boards/atlas/connectors/g0.yaml diff --git a/firmware/config/boards/atlas/connectors/analog.yaml b/firmware/config/boards/atlas/connectors/analog.yaml index 1a47e4a1a8..ab5225eaa6 100644 --- a/firmware/config/boards/atlas/connectors/analog.yaml +++ b/firmware/config/boards/atlas/connectors/analog.yaml @@ -74,51 +74,3 @@ pins: - id: EFI_ADC_18 class: analog_inputs ts_name: VBoost Sense - - - id: EFI_ADC_20 - class: analog_inputs - ts_name: G0 Analog 1 - - - id: EFI_ADC_21 - class: analog_inputs - ts_name: G0 Analog 2 - - - id: EFI_ADC_22 - class: analog_inputs - ts_name: G0 Analog 3 - - - id: EFI_ADC_23 - class: analog_inputs - ts_name: G0 Analog 4 - - - id: EFI_ADC_24 - class: analog_inputs - ts_name: G0 Analog 5 - - - id: EFI_ADC_25 - class: analog_inputs - ts_name: G0 Analog 6 - - - id: EFI_ADC_26 - class: analog_inputs - ts_name: G0 Analog 7 - - - id: EFI_ADC_27 - class: analog_inputs - ts_name: G0 Analog 8 - - - id: EFI_ADC_28 - class: analog_inputs - ts_name: G0 Analog Temp 1 - - - id: EFI_ADC_29 - class: analog_inputs - ts_name: G0 Analog Temp 2 - - - id: EFI_ADC_30 - class: analog_inputs - ts_name: G0 Analog Temp 3 - - - id: EFI_ADC_31 - class: analog_inputs - ts_name: G0 Analog Temp 4 diff --git a/firmware/config/boards/atlas/connectors/g0.yaml b/firmware/config/boards/atlas/connectors/g0.yaml new file mode 100644 index 0000000000..328f6eebf4 --- /dev/null +++ b/firmware/config/boards/atlas/connectors/g0.yaml @@ -0,0 +1,64 @@ +pins: + - id: EFI_ADC_20 + class: analog_inputs + ts_name: G0 Analog 1 + + - id: EFI_ADC_21 + class: analog_inputs + ts_name: G0 Analog 2 + + - id: EFI_ADC_22 + class: analog_inputs + ts_name: G0 Analog 3 + + - id: EFI_ADC_23 + class: analog_inputs + ts_name: G0 Analog 4 + + - id: EFI_ADC_24 + class: analog_inputs + ts_name: G0 Analog 5 + + - id: EFI_ADC_25 + class: analog_inputs + ts_name: G0 Analog 6 + + - id: EFI_ADC_26 + class: analog_inputs + ts_name: G0 Analog 7 + + - id: EFI_ADC_27 + class: analog_inputs + ts_name: G0 Analog 8 + + - id: EFI_ADC_28 + class: analog_inputs + ts_name: G0 Analog Temp 1 + + - id: EFI_ADC_29 + class: analog_inputs + ts_name: G0 Analog Temp 2 + + - id: EFI_ADC_30 + class: analog_inputs + ts_name: G0 Analog Temp 3 + + - id: EFI_ADC_31 + class: analog_inputs + ts_name: G0 Analog Temp 4 + + - id: G0_DIGITAL_IN_0 + class: switch_inputs + ts_name: G0 Digital 1 + + - id: G0_DIGITAL_IN_1 + class: switch_inputs + ts_name: G0 Digital 2 + + - id: G0_DIGITAL_IN_2 + class: switch_inputs + ts_name: G0 Digital 3 + + - id: G0_DIGITAL_IN_3 + class: switch_inputs + ts_name: G0 Digital 4 diff --git a/firmware/controllers/algo/rusefi_hw_enums.h b/firmware/controllers/algo/rusefi_hw_enums.h index 51c1a917d4..fae7095c68 100644 --- a/firmware/controllers/algo/rusefi_hw_enums.h +++ b/firmware/controllers/algo/rusefi_hw_enums.h @@ -246,6 +246,10 @@ enum class Gpio : uint16_t { G0_ANALOG_TEMP_2 = 224, G0_ANALOG_TEMP_3 = 225, G0_ANALOG_TEMP_4 = 226, + G0_DIGITAL_IN_0 = 227, + G0_DIGITAL_IN_1 = 228, + G0_DIGITAL_IN_2 = 229, + G0_DIGITAL_IN_3 = 230, CAN_INPUT_0 = 249, CAN_INPUT_1 = 250, diff --git a/firmware/hw_layer/g0_gpio/g0_analog.cpp b/firmware/hw_layer/g0_gpio/g0_analog.cpp index 29a224133d..2fc9615672 100644 --- a/firmware/hw_layer/g0_gpio/g0_analog.cpp +++ b/firmware/hw_layer/g0_gpio/g0_analog.cpp @@ -3,14 +3,18 @@ namespace { static constexpr size_t G070_ANALOG_CHANNEL_COUNT = 12; +static constexpr size_t G070_DIGITAL_INPUT_COUNT = 4; static constexpr uint8_t spiAppFrameSize = 36U; static constexpr uint8_t spiCmdReadAnalog = 0x10U; +static constexpr uint8_t spiCmdReadDigitalAll = 0x12U; static constexpr uint8_t spiCmdNop = 0x00U; +static constexpr uint8_t spiCmdSetInputMode = 0x30U; static constexpr uint8_t spiAppHeaderSize = 4U; static constexpr uint8_t spiResultOk = 0x00U; static constexpr uint8_t spiResultInvalid = 0x01U; static constexpr uint8_t spiResultRange = 0x02U; static constexpr uint8_t spiResultBusy = 0x03U; +static constexpr uint8_t spiDigitalMode = 0x00U; static constexpr spi_device_e G0_SPI_DEVICE = SPI_DEVICE_5; static constexpr brain_pin_e G0_SPI_CS_PIN = Gpio::F6; static constexpr size_t G0_FIRST_ADC_INDEX = EFI_ADC_20 - EFI_ADC_0; @@ -67,6 +71,18 @@ class G070SpiAdcProvider final : public AdcProvider { return m_millivolts[idx] * 0.001f; } + bool readDigitalInput(size_t idx) const { + if (idx >= G070_DIGITAL_INPUT_COUNT) { + return false; + } + + if (!m_digitalReady) { + return false; + } + + return m_digitalLevels[idx]; + } + void start() { if (m_started) { efiPrintf("G070 SPI ADC provider: already started"); @@ -98,21 +114,19 @@ class G070SpiAdcProvider final : public AdcProvider { chRegSetThreadName("G070 ADC"); while (true) { - pollAnalog(); + pollIo(); chThdSleepMilliseconds(G0_POLL_INTERVAL_MS); } } - void pollAnalog() { + void pollIo() { spiAcquireBus(spiDevice); clearFrame(txBuf); clearFrame(rxBuf); - // Always request the next analog frame. - // The received frame is the response to the previous command. - txBuf[0] = spiCmdReadAnalog; + prepareRequest(txBuf); spiSelect(spiDevice); spiExchange(spiDevice, spiAppFrameSize, txBuf, rxBuf); @@ -120,13 +134,45 @@ class G070SpiAdcProvider final : public AdcProvider { spiReleaseBus(spiDevice); - parseAnalogResponse(rxBuf); + parseResponse(rxBuf); + } + + void prepareRequest(uint8_t* tx) { + // The G0 SPI application protocol is pipelined: the response we receive now + // belongs to the previous request, while this request schedules the next reply. + if (m_nextDigitalInputToConfigure <= G070_DIGITAL_INPUT_COUNT) { + tx[0] = spiCmdSetInputMode; + tx[1] = static_cast(m_nextDigitalInputToConfigure); + tx[2] = spiDigitalMode; + m_nextDigitalInputToConfigure++; + return; + } + + if (m_pollAnalogNext) { + tx[0] = spiCmdReadAnalog; + } else { + tx[0] = spiCmdReadDigitalAll; + } + + m_pollAnalogNext = !m_pollAnalogNext; + } + + void parseResponse(const uint8_t* rx) { + switch (rx[2]) { + case spiCmdReadAnalog: + parseAnalogResponse(rx); + break; + case spiCmdReadDigitalAll: + parseDigitalResponse(rx); + break; + default: + break; + } } void parseAnalogResponse(const uint8_t* rx) { const uint8_t status = rx[0]; const uint8_t result = rx[1]; - const uint8_t command = rx[2]; const uint8_t payloadLength = rx[3]; // static constexpr size_t printThrottle = 50; @@ -157,12 +203,6 @@ class G070SpiAdcProvider final : public AdcProvider { return; } - if (command != spiCmdReadAnalog) { - // This is often a valid NOP response, not necessarily garbage. - m_ready = false; - return; - } - if (payloadLength != 26) { m_ready = false; return; @@ -199,6 +239,43 @@ class G070SpiAdcProvider final : public AdcProvider { m_ready = true; } + void parseDigitalResponse(const uint8_t* rx) { + const uint8_t status = rx[0]; + const uint8_t result = rx[1]; + const uint8_t payloadLength = rx[3]; + + if (status != 0x00 && status != 0x01) { + m_digitalReady = false; + return; + } + + if (result != spiResultOk) { + m_digitalReady = false; + return; + } + + if (payloadLength != 25) { + m_digitalReady = false; + return; + } + + const uint8_t inputCount = rx[spiAppHeaderSize]; + + if (inputCount != G070_DIGITAL_INPUT_COUNT) { + m_digitalReady = false; + return; + } + + for (size_t i = 0; i < G070_DIGITAL_INPUT_COUNT; i++) { + const uint8_t offset = static_cast(spiAppHeaderSize + 1 + i * 6); + const uint8_t flags = rx[offset + 1]; + + m_digitalLevels[i] = (flags & 0x01U) != 0; + } + + m_digitalReady = true; + } + static void clearFrame(uint8_t* frame) { for (size_t i = 0; i < spiAppFrameSize; i++) { frame[i] = 0; @@ -214,6 +291,10 @@ class G070SpiAdcProvider final : public AdcProvider { bool m_enabled[G070_ANALOG_CHANNEL_COUNT] = {}; volatile bool m_ready = false; volatile uint16_t m_millivolts[G070_ANALOG_CHANNEL_COUNT] = {}; + volatile bool m_digitalReady = false; + volatile bool m_digitalLevels[G070_DIGITAL_INPUT_COUNT] = {}; + size_t m_nextDigitalInputToConfigure = 1; + bool m_pollAnalogNext = true; THD_WORKING_AREA(m_threadWa, 512); }; @@ -240,3 +321,7 @@ void startG070SpiAdcProvider() { registerAdcProvider(g070SpiAdcProvider, G0_FIRST_ADC_INDEX, G070_ANALOG_CHANNEL_COUNT); } + +bool readG070DigitalInput(size_t idx) { + return g070SpiAdcProvider.readDigitalInput(idx); +} diff --git a/firmware/hw_layer/g0_gpio/g0_analog.h b/firmware/hw_layer/g0_gpio/g0_analog.h index 309ea7c3a9..5e4fdc314e 100644 --- a/firmware/hw_layer/g0_gpio/g0_analog.h +++ b/firmware/hw_layer/g0_gpio/g0_analog.h @@ -4,3 +4,4 @@ #include "adc_provider.h" void startG070SpiAdcProvider(); +bool readG070DigitalInput(size_t idx); diff --git a/firmware/hw_layer/io_pins.cpp b/firmware/hw_layer/io_pins.cpp index 6884d6e7da..81e915e9cc 100644 --- a/firmware/hw_layer/io_pins.cpp +++ b/firmware/hw_layer/io_pins.cpp @@ -12,6 +12,7 @@ #if EFI_PROD_CODE #include "drivers/gpio/gpio_ext.h" +#include "hw_layer/g0_gpio/g0_analog.h" #include "status_loop.h" #include "console_io.h" @@ -149,6 +150,10 @@ bool efiReadPin(Gpio pin) { return readCanVirtualInput(pin - Gpio::CAN_INPUT_0); } + if (pin >= Gpio::G0_DIGITAL_IN_0 && pin <= Gpio::G0_DIGITAL_IN_3) { + return readG070DigitalInput(pin - Gpio::G0_DIGITAL_IN_0); + } + /* incorrect pin */ return false; } From 7d83165535735c595a102fea790ddc9785e3c492 Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 17:39:35 +0200 Subject: [PATCH 34/47] bump g0 outputs --- firmware/ext/g0_firmware | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index 35156158bb..231a39671b 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit 35156158bb6949cf59a128cf9ba182184c0495e3 +Subproject commit 231a39671b2bc1b85940610b6aa677d68e4d62d6 From fd9078e14fd84d0e22c3f75b36d9ad40a60f46aa Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 17:47:13 +0200 Subject: [PATCH 35/47] bump --- firmware/ext/g0_firmware | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index 231a39671b..887f1aadc2 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit 231a39671b2bc1b85940610b6aa677d68e4d62d6 +Subproject commit 887f1aadc25623f320939c58b9ffc42c31ebca85 From ffc840cdeef3ff8cf7370d55af69c2a6b5ab0c61 Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 17:51:15 +0200 Subject: [PATCH 36/47] working outputs and digital io --- .../config/boards/atlas/connectors/g0.yaml | 16 ++ firmware/controllers/algo/rusefi_hw_enums.h | 4 + firmware/controllers/system/efi_gpio.cpp | 22 ++ .../system/timer/pwm_generator_logic.cpp | 13 +- firmware/hw_layer/g0_gpio/g0_analog.cpp | 230 +++++++++++++++++- firmware/hw_layer/g0_gpio/g0_analog.h | 8 + .../hw_layer/ports/stm32/stm32_common.cpp | 5 + 7 files changed, 292 insertions(+), 6 deletions(-) diff --git a/firmware/config/boards/atlas/connectors/g0.yaml b/firmware/config/boards/atlas/connectors/g0.yaml index 328f6eebf4..749e8cc6e3 100644 --- a/firmware/config/boards/atlas/connectors/g0.yaml +++ b/firmware/config/boards/atlas/connectors/g0.yaml @@ -62,3 +62,19 @@ pins: - id: G0_DIGITAL_IN_3 class: switch_inputs ts_name: G0 Digital 4 + + - id: G0_LOWSIDE_0 + class: outputs + ts_name: G0 Lowside 1 + + - id: G0_LOWSIDE_1 + class: outputs + ts_name: G0 Lowside 2 + + - id: G0_LOWSIDE_2 + class: outputs + ts_name: G0 Lowside 3 + + - id: G0_LOWSIDE_3 + class: outputs + ts_name: G0 Lowside 4 diff --git a/firmware/controllers/algo/rusefi_hw_enums.h b/firmware/controllers/algo/rusefi_hw_enums.h index fae7095c68..33b62cfd28 100644 --- a/firmware/controllers/algo/rusefi_hw_enums.h +++ b/firmware/controllers/algo/rusefi_hw_enums.h @@ -250,6 +250,10 @@ enum class Gpio : uint16_t { G0_DIGITAL_IN_1 = 228, G0_DIGITAL_IN_2 = 229, G0_DIGITAL_IN_3 = 230, + G0_LOWSIDE_0 = 231, + G0_LOWSIDE_1 = 232, + G0_LOWSIDE_2 = 233, + G0_LOWSIDE_3 = 234, CAN_INPUT_0 = 249, CAN_INPUT_1 = 250, diff --git a/firmware/controllers/system/efi_gpio.cpp b/firmware/controllers/system/efi_gpio.cpp index e737f215d8..01c702ea78 100644 --- a/firmware/controllers/system/efi_gpio.cpp +++ b/firmware/controllers/system/efi_gpio.cpp @@ -10,6 +10,9 @@ #include "engine_sniffer.h" #include "drivers/gpio/gpio_ext.h" +#if EFI_PROD_CODE +#include "hw_layer/g0_gpio/g0_analog.h" +#endif #if HW_HELLEN #include "hellen_meta.h" @@ -458,6 +461,11 @@ void OutputPin::setValue(int logicValue) { int electricalValue = getElectricalValue(logicValue, m_mode); #if EFI_PROD_CODE + if (isG070LowsidePin(m_brainPin)) { + setG070LowsideOutput(m_brainPin, electricalValue != 0); + return; + } + #if (BOARD_EXT_GPIOCHIPS > 0) if (!this->ext) { setOnchipValue(electricalValue); @@ -575,6 +583,11 @@ void OutputPin::initPin( setDefaultPinState(outputMode); #if EFI_PROD_CODE + if (isG070LowsidePin(m_brainPin)) { + brain_pin_markUsed(m_brainPin, msg); + return; + } + iomode_t ioMode = (outputMode == OM_DEFAULT || outputMode == OM_INVERTED) ? PAL_MODE_OUTPUT_PUSHPULL : PAL_MODE_OUTPUT_OPENDRAIN; @@ -621,6 +634,15 @@ void OutputPin::deInit() { efiPrintf("unregistering %s", hwPortname(m_brainPin)); +#if EFI_PROD_CODE + if (isG070LowsidePin(m_brainPin)) { + disableG070LowsideOutput(m_brainPin); + brain_pin_markUnused(m_brainPin); + m_brainPin = Gpio::Unassigned; + return; + } +#endif + efiSetPadUnused(m_brainPin); // Clear the pin so that it won't get set any more diff --git a/firmware/controllers/system/timer/pwm_generator_logic.cpp b/firmware/controllers/system/timer/pwm_generator_logic.cpp index b644441164..3ddaf8a1f7 100644 --- a/firmware/controllers/system/timer/pwm_generator_logic.cpp +++ b/firmware/controllers/system/timer/pwm_generator_logic.cpp @@ -320,6 +320,15 @@ void startSimplePwm(SimplePwm* state, const char* msg, OutputPin* output, float return; } +#if EFI_PROD_CODE && HAL_USE_PWM + if (isBrainPinValid(output->m_brainPin)) { + if (auto hardPwm = hardware_pwm::tryInitPin(msg, output->m_brainPin, frequency, dutyCycle)) { + state->hardPwm = hardPwm; + return; + } + } +#endif + state->seq.setSwitchTime(0, dutyCycle); state->seq.setSwitchTime(1, 1); state->seq.setChannelState(0, 0, false); @@ -346,9 +355,7 @@ void startSimplePwmExt( void startSimplePwmHard( SimplePwm* state, const char* msg, brain_pin_e brainPin, OutputPin* output, float frequency, float dutyCycle) { #if EFI_PROD_CODE && HAL_USE_PWM - auto hardPwm = hardware_pwm::tryInitPin(msg, brainPin, frequency, dutyCycle); - - if (hardPwm) { + if (auto hardPwm = hardware_pwm::tryInitPin(msg, brainPin, frequency, dutyCycle)) { state->hardPwm = hardPwm; } else { #endif diff --git a/firmware/hw_layer/g0_gpio/g0_analog.cpp b/firmware/hw_layer/g0_gpio/g0_analog.cpp index 2fc9615672..084157d76a 100644 --- a/firmware/hw_layer/g0_gpio/g0_analog.cpp +++ b/firmware/hw_layer/g0_gpio/g0_analog.cpp @@ -1,13 +1,18 @@ #include "g0_analog.h" +#if EFI_PROD_CODE +#include "ports/mpu_util.h" namespace { static constexpr size_t G070_ANALOG_CHANNEL_COUNT = 12; static constexpr size_t G070_DIGITAL_INPUT_COUNT = 4; +static constexpr size_t G070_OUTPUT_COUNT = 4; static constexpr uint8_t spiAppFrameSize = 36U; static constexpr uint8_t spiCmdReadAnalog = 0x10U; static constexpr uint8_t spiCmdReadDigitalAll = 0x12U; static constexpr uint8_t spiCmdNop = 0x00U; +static constexpr uint8_t spiCmdSetOutput = 0x20U; +static constexpr uint8_t spiCmdDisableOutput = 0x21U; static constexpr uint8_t spiCmdSetInputMode = 0x30U; static constexpr uint8_t spiAppHeaderSize = 4U; static constexpr uint8_t spiResultOk = 0x00U; @@ -15,6 +20,7 @@ static constexpr uint8_t spiResultInvalid = 0x01U; static constexpr uint8_t spiResultRange = 0x02U; static constexpr uint8_t spiResultBusy = 0x03U; static constexpr uint8_t spiDigitalMode = 0x00U; +static constexpr uint16_t G070_OUTPUT_DUTY_MAX = 10000U; static constexpr spi_device_e G0_SPI_DEVICE = SPI_DEVICE_5; static constexpr brain_pin_e G0_SPI_CS_PIN = Gpio::F6; static constexpr size_t G0_FIRST_ADC_INDEX = EFI_ADC_20 - EFI_ADC_0; @@ -32,6 +38,28 @@ static SPIConfig g0AnalogSpiConfig = { static NO_CACHE uint8_t txBuf[spiAppFrameSize]; static NO_CACHE uint8_t rxBuf[spiAppFrameSize]; +struct G070OutputState { + bool enabled = false; + uint32_t frequencyHz = 0; + uint16_t duty = 0; + bool dirty = false; + bool pendingAck = false; + bool sentEnabled = false; + uint32_t sentFrequencyHz = 0; + uint16_t sentDuty = 0; +}; + +enum class G070RequestType : uint8_t { + None, + SetOutput, + DisableOutput, +}; + +struct G070PendingRequest { + G070RequestType type = G070RequestType::None; + size_t outputIndex = 0; +}; + class G070SpiAdcProvider final : public AdcProvider { public: const char* name() const override { @@ -83,6 +111,18 @@ class G070SpiAdcProvider final : public AdcProvider { return m_digitalLevels[idx]; } + void setLowsideOutput(size_t idx, bool value) { + requestOutput(idx, value, 0, value ? G070_OUTPUT_DUTY_MAX : 0); + } + + void disableLowsideOutput(size_t idx) { + requestOutput(idx, false, 0, 0); + } + + void setLowsidePwm(size_t idx, uint32_t frequencyHz, uint16_t duty) { + requestOutput(idx, true, frequencyHz, duty); + } + void start() { if (m_started) { efiPrintf("G070 SPI ADC provider: already started"); @@ -121,12 +161,14 @@ class G070SpiAdcProvider final : public AdcProvider { } void pollIo() { + G070PendingRequest nextRequest; + spiAcquireBus(spiDevice); clearFrame(txBuf); clearFrame(rxBuf); - prepareRequest(txBuf); + prepareRequest(txBuf, nextRequest); spiSelect(spiDevice); spiExchange(spiDevice, spiAppFrameSize, txBuf, rxBuf); @@ -135,9 +177,12 @@ class G070SpiAdcProvider final : public AdcProvider { spiReleaseBus(spiDevice); parseResponse(rxBuf); + m_pendingRequest = nextRequest; } - void prepareRequest(uint8_t* tx) { + void prepareRequest(uint8_t* tx, G070PendingRequest& nextRequest) { + nextRequest = {}; + // The G0 SPI application protocol is pipelined: the response we receive now // belongs to the previous request, while this request schedules the next reply. if (m_nextDigitalInputToConfigure <= G070_DIGITAL_INPUT_COUNT) { @@ -148,6 +193,33 @@ class G070SpiAdcProvider final : public AdcProvider { return; } + for (size_t i = 0; i < G070_OUTPUT_COUNT; i++) { + if (!m_outputs[i].dirty || m_outputs[i].pendingAck) { + continue; + } + + const auto& output = m_outputs[i]; + nextRequest.outputIndex = i; + + if (output.enabled) { + tx[0] = spiCmdSetOutput; + tx[1] = static_cast(i + 1); + putU32(tx, 2, output.frequencyHz); + putU16(tx, 6, output.duty); + nextRequest.type = G070RequestType::SetOutput; + } else { + tx[0] = spiCmdDisableOutput; + tx[1] = static_cast(i + 1); + nextRequest.type = G070RequestType::DisableOutput; + } + + m_outputs[i].pendingAck = true; + m_outputs[i].sentEnabled = output.enabled; + m_outputs[i].sentFrequencyHz = output.frequencyHz; + m_outputs[i].sentDuty = output.duty; + return; + } + if (m_pollAnalogNext) { tx[0] = spiCmdReadAnalog; } else { @@ -158,6 +230,11 @@ class G070SpiAdcProvider final : public AdcProvider { } void parseResponse(const uint8_t* rx) { + if (m_pendingRequest.type == G070RequestType::SetOutput || + m_pendingRequest.type == G070RequestType::DisableOutput) { + parseOutputAck(rx); + } + switch (rx[2]) { case spiCmdReadAnalog: parseAnalogResponse(rx); @@ -170,6 +247,23 @@ class G070SpiAdcProvider final : public AdcProvider { } } + void parseOutputAck(const uint8_t* rx) { + if (m_pendingRequest.outputIndex >= G070_OUTPUT_COUNT) { + return; + } + + auto& output = m_outputs[m_pendingRequest.outputIndex]; + output.pendingAck = false; + + if (rx[1] != spiResultOk) { + output.dirty = true; + return; + } + + output.dirty = output.enabled != output.sentEnabled || output.frequencyHz != output.sentFrequencyHz || + output.duty != output.sentDuty; + } + void parseAnalogResponse(const uint8_t* rx) { const uint8_t status = rx[0]; const uint8_t result = rx[1]; @@ -282,17 +376,51 @@ class G070SpiAdcProvider final : public AdcProvider { } } + static void putU16(uint8_t* buffer, uint8_t offset, uint16_t value) { + buffer[offset] = static_cast(value); + buffer[offset + 1] = static_cast(value >> 8); + } + + static void putU32(uint8_t* buffer, uint8_t offset, uint32_t value) { + buffer[offset] = static_cast(value); + buffer[offset + 1] = static_cast(value >> 8); + buffer[offset + 2] = static_cast(value >> 16); + buffer[offset + 3] = static_cast(value >> 24); + } + static uint16_t getU16(const uint8_t* buffer, uint8_t offset) { return static_cast(buffer[offset]) | static_cast(static_cast(buffer[offset + 1]) << 8); } + void requestOutput(size_t idx, bool enabled, uint32_t frequencyHz, uint16_t duty) { + if (idx >= G070_OUTPUT_COUNT) { + return; + } + + if (duty > G070_OUTPUT_DUTY_MAX) { + duty = G070_OUTPUT_DUTY_MAX; + } + + auto& output = m_outputs[idx]; + const bool changed = output.enabled != enabled || output.frequencyHz != frequencyHz || output.duty != duty; + + output.enabled = enabled; + output.frequencyHz = frequencyHz; + output.duty = duty; + if (changed) { + output.dirty = true; + } + } + private: bool m_enabled[G070_ANALOG_CHANNEL_COUNT] = {}; volatile bool m_ready = false; volatile uint16_t m_millivolts[G070_ANALOG_CHANNEL_COUNT] = {}; volatile bool m_digitalReady = false; volatile bool m_digitalLevels[G070_DIGITAL_INPUT_COUNT] = {}; + G070OutputState m_outputs[G070_OUTPUT_COUNT] = {}; + G070PendingRequest m_pendingRequest = {}; size_t m_nextDigitalInputToConfigure = 1; bool m_pollAnalogNext = true; @@ -305,7 +433,42 @@ namespace { static G070SpiAdcProvider g070SpiAdcProvider; -} +#if EFI_PROD_CODE +class G070LowsideHardwarePwm final : public hardware_pwm { +public: + explicit G070LowsideHardwarePwm(size_t outputIndex) + : m_outputIndex(outputIndex) {} + + void start(float frequencyHz, float duty) { + m_frequencyHz = frequencyHz < 1 ? 1 : static_cast(frequencyHz); + setDuty(duty); + } + + void setDuty(float duty) override { + if (duty < 0) { + duty = 0; + } else if (duty > 1) { + duty = 1; + } + + const auto scaledDuty = static_cast(duty * G070_OUTPUT_DUTY_MAX); + g070SpiAdcProvider.setLowsidePwm(m_outputIndex, m_frequencyHz, scaledDuty); + } + +private: + const size_t m_outputIndex; + uint32_t m_frequencyHz = 1; +}; + +static G070LowsideHardwarePwm g070LowsidePwms[G070_OUTPUT_COUNT] = { + G070LowsideHardwarePwm(0), + G070LowsideHardwarePwm(1), + G070LowsideHardwarePwm(2), + G070LowsideHardwarePwm(3), +}; +#endif + +} // namespace void startG070SpiAdcProvider() { static bool started = false; @@ -325,3 +488,64 @@ void startG070SpiAdcProvider() { bool readG070DigitalInput(size_t idx) { return g070SpiAdcProvider.readDigitalInput(idx); } + +bool isG070LowsidePin(brain_pin_e pin) { + return pin >= Gpio::G0_LOWSIDE_0 && pin <= Gpio::G0_LOWSIDE_3; +} + +void setG070LowsideOutput(brain_pin_e pin, bool value) { + if (!isG070LowsidePin(pin)) { + return; + } + + g070SpiAdcProvider.setLowsideOutput(pin - Gpio::G0_LOWSIDE_0, value); +} + +void disableG070LowsideOutput(brain_pin_e pin) { + if (!isG070LowsidePin(pin)) { + return; + } + + g070SpiAdcProvider.disableLowsideOutput(pin - Gpio::G0_LOWSIDE_0); +} + +hardware_pwm* tryInitG070LowsidePwm(brain_pin_e pin, float frequencyHz, float duty) { + if (!isG070LowsidePin(pin)) { + return nullptr; + } + + auto& pwm = g070LowsidePwms[pin - Gpio::G0_LOWSIDE_0]; + pwm.start(frequencyHz, duty); + return &pwm; +} + +#else + +void startG070SpiAdcProvider() {} + +bool readG070DigitalInput(size_t idx) { + (void)idx; + return false; +} + +bool isG070LowsidePin(brain_pin_e pin) { + return pin >= Gpio::G0_LOWSIDE_0 && pin <= Gpio::G0_LOWSIDE_3; +} + +void setG070LowsideOutput(brain_pin_e pin, bool value) { + (void)pin; + (void)value; +} + +void disableG070LowsideOutput(brain_pin_e pin) { + (void)pin; +} + +hardware_pwm* tryInitG070LowsidePwm(brain_pin_e pin, float frequencyHz, float duty) { + (void)pin; + (void)frequencyHz; + (void)duty; + return nullptr; +} + +#endif diff --git a/firmware/hw_layer/g0_gpio/g0_analog.h b/firmware/hw_layer/g0_gpio/g0_analog.h index 5e4fdc314e..4b4a4c1c7c 100644 --- a/firmware/hw_layer/g0_gpio/g0_analog.h +++ b/firmware/hw_layer/g0_gpio/g0_analog.h @@ -5,3 +5,11 @@ void startG070SpiAdcProvider(); bool readG070DigitalInput(size_t idx); +bool isG070LowsidePin(brain_pin_e pin); +void setG070LowsideOutput(brain_pin_e pin, bool value); +void disableG070LowsideOutput(brain_pin_e pin); + +#ifdef __cplusplus +struct hardware_pwm; +hardware_pwm* tryInitG070LowsidePwm(brain_pin_e pin, float frequencyHz, float duty); +#endif diff --git a/firmware/hw_layer/ports/stm32/stm32_common.cpp b/firmware/hw_layer/ports/stm32/stm32_common.cpp index 74eaee722c..cc0dab4a9b 100644 --- a/firmware/hw_layer/ports/stm32/stm32_common.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_common.cpp @@ -182,6 +182,7 @@ void Stm32AdcProviderBase::disable(size_t idx) { #if EFI_PROD_CODE #if HAL_USE_PWM +#include "hw_layer/g0_gpio/g0_analog.h" namespace { struct stm32_pwm_config { PWMDriver* const Driver; @@ -368,6 +369,10 @@ stm32_hardware_pwm* getNextPwmDevice() { } /*static*/ hardware_pwm* hardware_pwm::tryInitPin(const char* msg, brain_pin_e pin, float frequencyHz, float duty) { + if (auto* g0Pwm = tryInitG070LowsidePwm(pin, frequencyHz, duty)) { + return g0Pwm; + } + // Hardware PWM can't do very slow PWM - the timer counter is only 16 bits, so at 2MHz counting, that's a minimum of // 31hz. if (frequencyHz < 50) { From 168878f7f993df752dac8ed237fe050843758413 Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 18:51:35 +0200 Subject: [PATCH 37/47] split the monolith into readable chunks --- firmware/controllers/system/efi_gpio.cpp | 12 +- .../system/g0_extension_firmware.cpp | 128 ++++ .../system/g0_extension_firmware.h | 3 + .../g0_extension_firmware_bootloader.cpp | 182 ++++++ .../system/g0_extension_firmware_impl.h | 72 +++ .../system/g0_extension_firmware_protocol.cpp | 61 ++ .../controllers/system/g0_firmware_loader.cpp | 373 ------------ .../controllers/system/g0_firmware_loader.h | 3 - firmware/controllers/system/system.mk | 4 +- .../g0_extension_adc_provider.cpp | 281 +++++++++ .../hw_layer/g0_extension/g0_extension_io.cpp | 120 ++++ .../hw_layer/g0_extension/g0_extension_io.h | 15 + .../g0_extension/g0_extension_io_impl.h | 68 +++ .../g0_extension_output_manager.cpp | 92 +++ firmware/hw_layer/g0_gpio/g0_analog.cpp | 551 ------------------ firmware/hw_layer/g0_gpio/g0_analog.h | 15 - firmware/hw_layer/hw_layer.mk | 5 +- firmware/hw_layer/io_pins.cpp | 4 +- .../hw_layer/ports/stm32/stm32_common.cpp | 4 +- firmware/rusefi.cpp | 10 +- 20 files changed, 1043 insertions(+), 960 deletions(-) create mode 100644 firmware/controllers/system/g0_extension_firmware.cpp create mode 100644 firmware/controllers/system/g0_extension_firmware.h create mode 100644 firmware/controllers/system/g0_extension_firmware_bootloader.cpp create mode 100644 firmware/controllers/system/g0_extension_firmware_impl.h create mode 100644 firmware/controllers/system/g0_extension_firmware_protocol.cpp delete mode 100644 firmware/controllers/system/g0_firmware_loader.cpp delete mode 100644 firmware/controllers/system/g0_firmware_loader.h create mode 100644 firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp create mode 100644 firmware/hw_layer/g0_extension/g0_extension_io.cpp create mode 100644 firmware/hw_layer/g0_extension/g0_extension_io.h create mode 100644 firmware/hw_layer/g0_extension/g0_extension_io_impl.h create mode 100644 firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp delete mode 100644 firmware/hw_layer/g0_gpio/g0_analog.cpp delete mode 100644 firmware/hw_layer/g0_gpio/g0_analog.h diff --git a/firmware/controllers/system/efi_gpio.cpp b/firmware/controllers/system/efi_gpio.cpp index 01c702ea78..b1ca700734 100644 --- a/firmware/controllers/system/efi_gpio.cpp +++ b/firmware/controllers/system/efi_gpio.cpp @@ -11,7 +11,7 @@ #include "drivers/gpio/gpio_ext.h" #if EFI_PROD_CODE -#include "hw_layer/g0_gpio/g0_analog.h" +#include "hw_layer/g0_extension/g0_extension_io.h" #endif #if HW_HELLEN @@ -461,8 +461,8 @@ void OutputPin::setValue(int logicValue) { int electricalValue = getElectricalValue(logicValue, m_mode); #if EFI_PROD_CODE - if (isG070LowsidePin(m_brainPin)) { - setG070LowsideOutput(m_brainPin, electricalValue != 0); + if (isG0ExtensionLowsidePin(m_brainPin)) { + setG0ExtensionLowsideOutput(m_brainPin, electricalValue != 0); return; } @@ -583,7 +583,7 @@ void OutputPin::initPin( setDefaultPinState(outputMode); #if EFI_PROD_CODE - if (isG070LowsidePin(m_brainPin)) { + if (isG0ExtensionLowsidePin(m_brainPin)) { brain_pin_markUsed(m_brainPin, msg); return; } @@ -635,8 +635,8 @@ void OutputPin::deInit() { efiPrintf("unregistering %s", hwPortname(m_brainPin)); #if EFI_PROD_CODE - if (isG070LowsidePin(m_brainPin)) { - disableG070LowsideOutput(m_brainPin); + if (isG0ExtensionLowsidePin(m_brainPin)) { + disableG0ExtensionLowsideOutput(m_brainPin); brain_pin_markUnused(m_brainPin); m_brainPin = Gpio::Unassigned; return; diff --git a/firmware/controllers/system/g0_extension_firmware.cpp b/firmware/controllers/system/g0_extension_firmware.cpp new file mode 100644 index 0000000000..cc7298338f --- /dev/null +++ b/firmware/controllers/system/g0_extension_firmware.cpp @@ -0,0 +1,128 @@ +#include "g0_extension_firmware.h" +#include "g0_extension_firmware_impl.h" + +#if HW_ATLAS && HAL_USE_SPI + +#include "hardware.h" +#include "mpu_util.h" + +namespace g0_extension_firmware { +namespace { + +static SPIConfig g0ExtensionSpiConfig = { + .circular = false, + .end_cb = NULL, + .ssport = NULL, + .sspad = 0, + .cfg1 = 7 | SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, + .cfg2 = 0}; + +static NO_CACHE uint8_t g0ExtensionSpiTxBuffer[SpiDmaBufferSize]; +static NO_CACHE uint8_t g0ExtensionSpiRxBuffer[SpiDmaBufferSize]; + +} // namespace + +SPIConfig& spiConfig() { + return g0ExtensionSpiConfig; +} + +uint8_t* txBuffer() { + return g0ExtensionSpiTxBuffer; +} + +uint8_t* rxBuffer() { + return g0ExtensionSpiRxBuffer; +} + +void setControlPin(brain_pin_e pin, bool value) { + palWritePad(getHwPort("g0", pin), getHwPin("g0", pin), value); +} + +void initControlPins() { + efiSetPadMode("G0 BOOT", BootPin, PAL_MODE_OUTPUT_PUSHPULL); + efiSetPadMode("G0 RESET", ResetPin, PAL_MODE_OUTPUT_PUSHPULL); + + setControlPin(BootPin, false); + setControlPin(ResetPin, true); +} + +void releaseControlPins() { + setControlPin(BootPin, false); + setControlPin(ResetPin, true); + + efiSetPadModeWithoutOwnershipAcquisition("G0 BOOT", BootPin, PAL_MODE_INPUT); + efiSetPadModeWithoutOwnershipAcquisition("G0 RESET", ResetPin, PAL_MODE_INPUT); +} + +void resetExtension(bool bootloaderMode) { + setControlPin(BootPin, bootloaderMode); + chThdSleepMilliseconds(5); + + setControlPin(ResetPin, false); + chThdSleepMilliseconds(20); + + setControlPin(ResetPin, true); + chThdSleepMilliseconds(100); +} + +} // namespace g0_extension_firmware + +#endif + +bool loadG0ExtensionFirmware(bool forceUpdate) { +#if HW_ATLAS && HAL_USE_SPI + #if G0_EXTENSION_FIRMWARE_IMAGE_AVAILABLE + g0_extension_firmware::initControlPins(); + + turnOnSpi(g0_extension_firmware::SpiDevice); + + SPIDriver* spi = getSpiDevice(g0_extension_firmware::SpiDevice); + spiAcquireBus(spi); + + auto& spiCfg = g0_extension_firmware::spiConfig(); + initSpiCs(&spiCfg, g0_extension_firmware::SpiCsPin); + palSetPad(spiCfg.ssport, spiCfg.sspad); + spiStart(spi, &spiCfg); + g0_extension_firmware::resetExtension(false); + + uint32_t currentVersion = 0; + bool ok = true; + + if (g0_extension_firmware::readAppVersion(spi, currentVersion)) { + efiPrintf( + "G0 extension firmware: app version %d, bundled version %d", + static_cast(currentVersion), + static_cast(build_g0_extension_version)); + + if (!forceUpdate && currentVersion == build_g0_extension_version) { + efiPrintf("G0 extension firmware: firmware is already current"); + } else { + efiPrintf("G0 extension firmware: requesting app update mode%s", forceUpdate ? " (forced)" : ""); + g0_extension_firmware::requestAppUpdate(spi); + ok = g0_extension_firmware::flashFirmwareImageWithRetry(spi); + } + } else { + efiPrintf("G0 extension firmware: no app version response, forcing update"); + ok = g0_extension_firmware::flashFirmwareImageWithRetry(spi); + } + + spiReleaseBus(spi); + g0_extension_firmware::releaseControlPins(); + + if (ok) { + efiPrintf("G0 extension firmware: complete"); + } else { + efiPrintf("G0 extension firmware: failed"); + } + + return ok; + #else + efiPrintf("G0 extension firmware ERROR: run `make -C firmware/ext/g0_firmware for_fome_image` first"); + return false; + #endif +#else + (void)forceUpdate; + efiPrintf("G0 extension firmware is only available on Atlas with SPI enabled"); + return false; +#endif +} diff --git a/firmware/controllers/system/g0_extension_firmware.h b/firmware/controllers/system/g0_extension_firmware.h new file mode 100644 index 0000000000..d48daa1531 --- /dev/null +++ b/firmware/controllers/system/g0_extension_firmware.h @@ -0,0 +1,3 @@ +#pragma once + +bool loadG0ExtensionFirmware(bool forceUpdate = false); diff --git a/firmware/controllers/system/g0_extension_firmware_bootloader.cpp b/firmware/controllers/system/g0_extension_firmware_bootloader.cpp new file mode 100644 index 0000000000..08ae272f05 --- /dev/null +++ b/firmware/controllers/system/g0_extension_firmware_bootloader.cpp @@ -0,0 +1,182 @@ +#include "g0_extension_firmware_impl.h" + +#if HW_ATLAS && HAL_USE_SPI && G0_EXTENSION_FIRMWARE_IMAGE_AVAILABLE + +namespace g0_extension_firmware { + +uint8_t spiByte(SPIDriver* spi, uint8_t tx) { + txBuffer()[0] = tx; + rxBuffer()[0] = 0; + spiExchange(spi, 1, txBuffer(), rxBuffer()); + return rxBuffer()[0]; +} + +uint8_t bootloaderSpiByte(SPIDriver* spi, uint8_t tx) { + const uint8_t rx = spiByte(spi, tx); + + // AN4286 requires at least 15us between bytes for the STM32 ROM SPI bootloader. + chThdSleepMicroseconds(BootloaderInterByteDelayUs); + return rx; +} + +bool handleBootloaderAck(SPIDriver* spi, uint8_t response) { + if (response == BootloaderAck) { + bootloaderSpiByte(spi, BootloaderAck); + return true; + } + + return false; +} + +bool waitBootloaderAck(SPIDriver* spi, int attempts) { + while (attempts-- > 0) { + const uint8_t response = bootloaderSpiByte(spi, 0x00); + + if (handleBootloaderAck(spi, response)) { + return true; + } + + if (response == BootloaderNack) { + return false; + } + + chThdSleepMilliseconds(1); + } + + return false; +} + +bool sendBootloaderBytesAndWaitAck(SPIDriver* spi, const uint8_t* data, size_t size) { + for (size_t i = 0; i < size; i++) { + const uint8_t response = bootloaderSpiByte(spi, data[i]); + + if (handleBootloaderAck(spi, response)) { + return true; + } + + if (response == BootloaderNack) { + return false; + } + } + + return waitBootloaderAck(spi); +} + +bool sendBootloaderCommand(SPIDriver* spi, uint8_t command) { + const uint8_t bytes[] = {BootloaderSync, command, static_cast(command ^ 0xFF)}; + return sendBootloaderBytesAndWaitAck(spi, bytes, sizeof(bytes)); +} + +bool sendBootloaderAddress(SPIDriver* spi, uint32_t address) { + uint8_t bytes[5] = { + static_cast(address >> 24), + static_cast(address >> 16), + static_cast(address >> 8), + static_cast(address), + 0}; + + bytes[4] = bytes[0] ^ bytes[1] ^ bytes[2] ^ bytes[3]; + return sendBootloaderBytesAndWaitAck(spi, bytes, sizeof(bytes)); +} + +bool bootloaderMassErase(SPIDriver* spi) { + if (!sendBootloaderCommand(spi, BootloaderExtendedErase)) { + return false; + } + + const uint8_t globalMassErase[] = {0xFF, 0xFF, 0x00}; + return sendBootloaderBytesAndWaitAck(spi, globalMassErase, sizeof(globalMassErase)); +} + +bool bootloaderWriteBlock(SPIDriver* spi, uint32_t address, const uint8_t* data, size_t size) { + if (size == 0 || size > 256) { + return false; + } + + if (!sendBootloaderCommand(spi, BootloaderWriteMemory) || !sendBootloaderAddress(spi, address)) { + return false; + } + + uint8_t buffer[258]; + buffer[0] = static_cast(size - 1); + uint8_t checksum = buffer[0]; + + for (size_t i = 0; i < size; i++) { + buffer[1 + i] = data[i]; + checksum ^= data[i]; + } + + buffer[1 + size] = checksum; + return sendBootloaderBytesAndWaitAck(spi, buffer, size + 2); +} + +bool bootloaderGo(SPIDriver* spi, uint32_t address) { + return sendBootloaderCommand(spi, BootloaderGo) && sendBootloaderAddress(spi, address); +} + +bool flashFirmwareImage(SPIDriver* spi) { + const size_t totalSize = sizeof(build_g0_extension_bin); + + if (totalSize == 0) { + efiPrintf("G0 extension firmware ERROR: firmware image header is missing or empty"); + return false; + } + + efiPrintf("G0 extension firmware: entering SPI bootloader"); + resetExtension(true); + + spiSelect(spi); + bootloaderSpiByte(spi, BootloaderSync); + if (!waitBootloaderAck(spi)) { + spiUnselect(spi); + efiPrintf("G0 extension firmware ERROR: no ACK from SPI bootloader"); + return false; + } + + efiPrintf("G0 extension firmware: erasing flash"); + if (!bootloaderMassErase(spi)) { + spiUnselect(spi); + efiPrintf("G0 extension firmware ERROR: flash erase failed"); + return false; + } + + efiPrintf("G0 extension firmware: writing %d bytes", static_cast(totalSize)); + for (size_t offset = 0; offset < totalSize; offset += 256) { + uint8_t block[256]; + const size_t blockSize = minI(256, totalSize - offset); + memset(block, 0xFF, sizeof(block)); + memcpy(block, build_g0_extension_bin + offset, blockSize); + + if (!bootloaderWriteBlock(spi, FlashBase + offset, block, sizeof(block))) { + spiUnselect(spi); + efiPrintf("G0 extension firmware ERROR: write failed at offset %d", static_cast(offset)); + return false; + } + } + + efiPrintf("G0 extension firmware: starting firmware"); + const bool ok = bootloaderGo(spi, FlashBase); + spiUnselect(spi); + + resetExtension(false); + return ok; +} + +bool flashFirmwareImageWithRetry(SPIDriver* spi) { + for (int attempt = 1; attempt <= FlashMaxAttempts; attempt++) { + efiPrintf("G0 extension firmware: flash attempt %d/%d", attempt, FlashMaxAttempts); + + if (flashFirmwareImage(spi)) { + return true; + } + + resetExtension(false); + } + + efiPrintf("G0 extension firmware ERROR: aborting G0 flash after %d failed attempts", FlashMaxAttempts); + return false; +} + +} // namespace g0_extension_firmware + +#endif diff --git a/firmware/controllers/system/g0_extension_firmware_impl.h b/firmware/controllers/system/g0_extension_firmware_impl.h new file mode 100644 index 0000000000..1c9ee956b2 --- /dev/null +++ b/firmware/controllers/system/g0_extension_firmware_impl.h @@ -0,0 +1,72 @@ +#pragma once + +#include "pch.h" + +#if HW_ATLAS && HAL_USE_SPI + +#if __has_include("../../ext/g0_firmware/for_fome/g0_firmware_image.h") +#include "../../ext/g0_firmware/for_fome/g0_firmware_image.h" +#define G0_EXTENSION_FIRMWARE_IMAGE_AVAILABLE TRUE +#else +#define G0_EXTENSION_FIRMWARE_IMAGE_AVAILABLE FALSE +#endif + +namespace g0_extension_firmware { + +static constexpr spi_device_e SpiDevice = SPI_DEVICE_5; +static constexpr brain_pin_e ResetPin = Gpio::B14; +static constexpr brain_pin_e BootPin = Gpio::B15; +static constexpr brain_pin_e SpiCsPin = Gpio::F6; +static constexpr uint32_t FlashBase = 0x08000000; + +static constexpr uint8_t BootloaderAck = 0x79; +static constexpr uint8_t BootloaderNack = 0x1F; +static constexpr uint8_t BootloaderSync = 0x5A; +static constexpr uint8_t BootloaderExtendedErase = 0x44; +static constexpr uint8_t BootloaderWriteMemory = 0x31; +static constexpr uint8_t BootloaderGo = 0x21; +static constexpr int BootloaderInterByteDelayUs = 20; +static constexpr int FlashMaxAttempts = 2; + +static constexpr uint8_t AppCmdNop = 0x00; +static constexpr uint8_t AppCmdReadVersion = 0x01; +static constexpr uint8_t AppCmdEnterUpdate = 0xA5; + +static constexpr uint8_t AppStatusReady = 0x00; +static constexpr uint8_t AppStatusUpdateMode = 0x01; +static constexpr size_t AppFrameSize = 36; +static constexpr size_t SpiDmaBufferSize = 258; +static constexpr uint8_t AppResultOk = 0x0; +static constexpr uint8_t AppHeaderSize = 4; + +SPIConfig& spiConfig(); +uint8_t* txBuffer(); +uint8_t* rxBuffer(); + +void setControlPin(brain_pin_e pin, bool value); +void initControlPins(); +void releaseControlPins(); +void resetExtension(bool bootloaderMode); + +uint8_t spiByte(SPIDriver* spi, uint8_t tx); +uint8_t bootloaderSpiByte(SPIDriver* spi, uint8_t tx); +bool handleBootloaderAck(SPIDriver* spi, uint8_t response); +bool waitBootloaderAck(SPIDriver* spi, int attempts = 200); +bool sendBootloaderBytesAndWaitAck(SPIDriver* spi, const uint8_t* data, size_t size); +bool sendBootloaderCommand(SPIDriver* spi, uint8_t command); +bool sendBootloaderAddress(SPIDriver* spi, uint32_t address); +bool bootloaderMassErase(SPIDriver* spi); +bool bootloaderWriteBlock(SPIDriver* spi, uint32_t address, const uint8_t* data, size_t size); +bool bootloaderGo(SPIDriver* spi, uint32_t address); +bool flashFirmwareImage(SPIDriver* spi); +bool flashFirmwareImageWithRetry(SPIDriver* spi); + +uint32_t readLe32(const uint8_t* data); +void exchangeAppFrame(SPIDriver* spi, uint8_t command, uint8_t* rx); +bool isAppResponse(const uint8_t* rx, uint8_t expectedCommand, uint8_t expectedPayloadLength); +bool readAppVersion(SPIDriver* spi, uint32_t& version); +void requestAppUpdate(SPIDriver* spi); + +} // namespace g0_extension_firmware + +#endif diff --git a/firmware/controllers/system/g0_extension_firmware_protocol.cpp b/firmware/controllers/system/g0_extension_firmware_protocol.cpp new file mode 100644 index 0000000000..cf85d33845 --- /dev/null +++ b/firmware/controllers/system/g0_extension_firmware_protocol.cpp @@ -0,0 +1,61 @@ +#include "g0_extension_firmware_impl.h" + +#if HW_ATLAS && HAL_USE_SPI && G0_EXTENSION_FIRMWARE_IMAGE_AVAILABLE + +namespace g0_extension_firmware { + +uint32_t readLe32(const uint8_t* data) { + return static_cast(data[0]) | (static_cast(data[1]) << 8) | + (static_cast(data[2]) << 16) | (static_cast(data[3]) << 24); +} + +void exchangeAppFrame(SPIDriver* spi, uint8_t command, uint8_t* rx) { + memset(txBuffer(), 0, AppFrameSize); + txBuffer()[0] = command; + memset(rxBuffer(), 0, AppFrameSize); + memset(rx, 0, AppFrameSize); + + spiSelect(spi); + spiExchange(spi, AppFrameSize, txBuffer(), rxBuffer()); + spiUnselect(spi); + + memcpy(rx, rxBuffer(), AppFrameSize); +} + +bool isAppResponse(const uint8_t* rx, uint8_t expectedCommand, uint8_t expectedPayloadLength) { + const bool knownStatus = rx[0] == AppStatusReady || rx[0] == AppStatusUpdateMode; + return knownStatus && rx[1] == AppResultOk && rx[2] == expectedCommand && rx[3] == expectedPayloadLength; +} + +bool readAppVersion(SPIDriver* spi, uint32_t& version) { + uint8_t rx[AppFrameSize]; + + exchangeAppFrame(spi, AppCmdReadVersion, rx); + chThdSleepMilliseconds(1); + exchangeAppFrame(spi, AppCmdNop, rx); + + if (!isAppResponse(rx, AppCmdReadVersion, 4)) { + efiPrintf( + "G0 extension firmware: invalid version response %02X %02X %02X %02X", + rx[0], + rx[1], + rx[2], + rx[3]); + return false; + } + + version = readLe32(&rx[AppHeaderSize]); + return true; +} + +void requestAppUpdate(SPIDriver* spi) { + uint8_t rx[AppFrameSize]; + + exchangeAppFrame(spi, AppCmdEnterUpdate, rx); + chThdSleepMilliseconds(1); + exchangeAppFrame(spi, AppCmdNop, rx); +} + +} // namespace g0_extension_firmware + +#endif diff --git a/firmware/controllers/system/g0_firmware_loader.cpp b/firmware/controllers/system/g0_firmware_loader.cpp deleted file mode 100644 index 20bdd9c397..0000000000 --- a/firmware/controllers/system/g0_firmware_loader.cpp +++ /dev/null @@ -1,373 +0,0 @@ -#include "pch.h" - -#if HW_ATLAS && HAL_USE_SPI - -#include "g0_firmware_loader.h" -#include "hardware.h" -#include "mpu_util.h" - -#if __has_include("../../ext/g0_firmware/for_fome/g0_firmware_image.h") -#include "../../ext/g0_firmware/for_fome/g0_firmware_image.h" -#define G0_FIRMWARE_IMAGE_AVAILABLE TRUE -#else -#define G0_FIRMWARE_IMAGE_AVAILABLE FALSE -#endif - -#if G0_FIRMWARE_IMAGE_AVAILABLE - -static constexpr spi_device_e G0_SPI_DEVICE = SPI_DEVICE_5; -static constexpr brain_pin_e G0_RESET_PIN = Gpio::B14; -static constexpr brain_pin_e G0_BOOT_PIN = Gpio::B15; -static constexpr brain_pin_e G0_SPI_CS_PIN = Gpio::F6; -static constexpr uint32_t G0_FLASH_BASE = 0x08000000; - -static constexpr uint8_t STM_BL_ACK = 0x79; -static constexpr uint8_t STM_BL_NACK = 0x1F; -static constexpr uint8_t STM_BL_SYNC = 0x5A; -static constexpr uint8_t STM_BL_EXTENDED_ERASE = 0x44; -static constexpr uint8_t STM_BL_WRITE_MEMORY = 0x31; -static constexpr uint8_t STM_BL_GO = 0x21; -static constexpr int STM_BL_INTER_BYTE_DELAY_US = 20; -static constexpr int G0_FLASH_MAX_ATTEMPTS = 2; - -static constexpr uint8_t G0_APP_CMD_NOP = 0x00; -static constexpr uint8_t G0_APP_CMD_READ_VERSION = 0x01; -static constexpr uint8_t G0_APP_CMD_ENTER_UPDATE = 0xA5; - -static constexpr uint8_t G0_APP_STATUS_READY = 0x00; -static constexpr uint8_t G0_APP_STATUS_UPDATE_MODE = 0x01; -static constexpr size_t G0_APP_FRAME_SIZE = 36; -static constexpr size_t G0_SPI_DMA_BUFFER_SIZE = 258; -static constexpr uint8_t G0_APP_RESULT_OK = 0x0; -static constexpr uint8_t G0_APP_HEADER_SIZE = 4; - -static NO_CACHE uint8_t g0SpiTxBuffer[G0_SPI_DMA_BUFFER_SIZE]; -static NO_CACHE uint8_t g0SpiRxBuffer[G0_SPI_DMA_BUFFER_SIZE]; - -static SPIConfig g0SpiConfig = { - .circular = false, - .end_cb = NULL, - .ssport = NULL, - .sspad = 0, - .cfg1 = 7 | SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, - .cfg2 = 0}; - -static void setPin(brain_pin_e pin, bool value) { - palWritePad(getHwPort("g0", pin), getHwPin("g0", pin), value); -} - -static void initControlPins() { - efiSetPadMode("G0 BOOT", G0_BOOT_PIN, PAL_MODE_OUTPUT_PUSHPULL); - efiSetPadMode("G0 RESET", G0_RESET_PIN, PAL_MODE_OUTPUT_PUSHPULL); - - setPin(G0_BOOT_PIN, false); - setPin(G0_RESET_PIN, true); -} - -static void releaseControlPins() { - setPin(G0_BOOT_PIN, false); - setPin(G0_RESET_PIN, true); - - efiSetPadModeWithoutOwnershipAcquisition("G0 BOOT", G0_BOOT_PIN, PAL_MODE_INPUT); - efiSetPadModeWithoutOwnershipAcquisition("G0 RESET", G0_RESET_PIN, PAL_MODE_INPUT); -} - -static void resetG0(bool bootloaderMode) { - setPin(G0_BOOT_PIN, bootloaderMode); - chThdSleepMilliseconds(5); - - setPin(G0_RESET_PIN, false); - chThdSleepMilliseconds(20); - - setPin(G0_RESET_PIN, true); - chThdSleepMilliseconds(100); -} - -static uint8_t spiByte(SPIDriver* spi, uint8_t tx) { - g0SpiTxBuffer[0] = tx; - g0SpiRxBuffer[0] = 0; - spiExchange(spi, 1, g0SpiTxBuffer, g0SpiRxBuffer); - return g0SpiRxBuffer[0]; -} - -static uint8_t bootloaderSpiByte(SPIDriver* spi, uint8_t tx) { - const uint8_t rx = spiByte(spi, tx); - - // AN4286 requires at least 15us between bytes for the STM32 ROM SPI bootloader. - chThdSleepMicroseconds(STM_BL_INTER_BYTE_DELAY_US); - - return rx; -} - -static bool handleBootloaderAck(SPIDriver* spi, uint8_t response) { - if (response == STM_BL_ACK) { - bootloaderSpiByte(spi, STM_BL_ACK); - return true; - } - - return false; -} - -static bool waitAck(SPIDriver* spi, int attempts = 200) { - while (attempts-- > 0) { - const uint8_t response = bootloaderSpiByte(spi, 0x00); - - if (handleBootloaderAck(spi, response)) { - return true; - } - - if (response == STM_BL_NACK) { - return false; - } - - chThdSleepMilliseconds(1); - } - - return false; -} - -static bool sendBytesAndWaitAck(SPIDriver* spi, const uint8_t* data, size_t size) { - for (size_t i = 0; i < size; i++) { - const uint8_t response = bootloaderSpiByte(spi, data[i]); - - if (handleBootloaderAck(spi, response)) { - return true; - } - - if (response == STM_BL_NACK) { - return false; - } - } - - return waitAck(spi); -} - -static bool sendCommand(SPIDriver* spi, uint8_t command) { - const uint8_t bytes[] = {STM_BL_SYNC, command, static_cast(command ^ 0xFF)}; - return sendBytesAndWaitAck(spi, bytes, sizeof(bytes)); -} - -static bool sendAddress(SPIDriver* spi, uint32_t address) { - uint8_t bytes[5] = { - static_cast(address >> 24), - static_cast(address >> 16), - static_cast(address >> 8), - static_cast(address), - 0}; - - bytes[4] = bytes[0] ^ bytes[1] ^ bytes[2] ^ bytes[3]; - - return sendBytesAndWaitAck(spi, bytes, sizeof(bytes)); -} - -static bool massErase(SPIDriver* spi) { - if (!sendCommand(spi, STM_BL_EXTENDED_ERASE)) { - return false; - } - - const uint8_t globalMassErase[] = {0xFF, 0xFF, 0x00}; - return sendBytesAndWaitAck(spi, globalMassErase, sizeof(globalMassErase)); -} - -static bool writeBlock(SPIDriver* spi, uint32_t address, const uint8_t* data, size_t size) { - if (size == 0 || size > 256) { - return false; - } - - if (!sendCommand(spi, STM_BL_WRITE_MEMORY) || !sendAddress(spi, address)) { - return false; - } - - uint8_t buffer[258]; - buffer[0] = static_cast(size - 1); - uint8_t checksum = buffer[0]; - - for (size_t i = 0; i < size; i++) { - buffer[1 + i] = data[i]; - checksum ^= data[i]; - } - - buffer[1 + size] = checksum; - - return sendBytesAndWaitAck(spi, buffer, size + 2); -} - -static bool go(SPIDriver* spi, uint32_t address) { - return sendCommand(spi, STM_BL_GO) && sendAddress(spi, address); -} - -static uint32_t readLe32(const uint8_t* data) { - return static_cast(data[0]) | (static_cast(data[1]) << 8) | - (static_cast(data[2]) << 16) | (static_cast(data[3]) << 24); -} - -static void exchangeG0AppFrame(SPIDriver* spi, uint8_t command, uint8_t* rx) { - memset(g0SpiTxBuffer, 0, G0_APP_FRAME_SIZE); - g0SpiTxBuffer[0] = command; - memset(g0SpiRxBuffer, 0, G0_APP_FRAME_SIZE); - memset(rx, 0, G0_APP_FRAME_SIZE); - - spiSelect(spi); - spiExchange(spi, G0_APP_FRAME_SIZE, g0SpiTxBuffer, g0SpiRxBuffer); - spiUnselect(spi); - - memcpy(rx, g0SpiRxBuffer, G0_APP_FRAME_SIZE); -} - -static bool isG0AppResponse(const uint8_t* rx, uint8_t expectedCommand, uint8_t expectedPayloadLength) { - const bool knownStatus = rx[0] == G0_APP_STATUS_READY || rx[0] == G0_APP_STATUS_UPDATE_MODE; - - return knownStatus && rx[1] == G0_APP_RESULT_OK && rx[2] == expectedCommand && rx[3] == expectedPayloadLength; -} - -static bool readG0AppVersion(SPIDriver* spi, uint32_t& version) { - uint8_t rx[G0_APP_FRAME_SIZE]; - - exchangeG0AppFrame(spi, G0_APP_CMD_READ_VERSION, rx); - chThdSleepMilliseconds(1); - - exchangeG0AppFrame(spi, G0_APP_CMD_NOP, rx); - - if (!isG0AppResponse(rx, G0_APP_CMD_READ_VERSION, 4)) { - efiPrintf("G0 firmware load: invalid version response %02X %02X %02X %02X", rx[0], rx[1], rx[2], rx[3]); - - return false; - } - - version = readLe32(&rx[G0_APP_HEADER_SIZE]); - return true; -} - -static void requestG0AppUpdate(SPIDriver* spi) { - uint8_t rx[G0_APP_FRAME_SIZE]; - - exchangeG0AppFrame(spi, G0_APP_CMD_ENTER_UPDATE, rx); - chThdSleepMilliseconds(1); - exchangeG0AppFrame(spi, G0_APP_CMD_NOP, rx); -} - -static bool injectG0Firmware(SPIDriver* spi) { - const size_t totalSize = sizeof(build_g0_extension_bin); - - if (totalSize == 0) { - efiPrintf("G0 firmware load ERROR: firmware image header is missing or empty"); - return false; - } - - efiPrintf("G0 firmware load: entering SPI bootloader"); - resetG0(true); - - spiSelect(spi); - - bootloaderSpiByte(spi, STM_BL_SYNC); - if (!waitAck(spi)) { - spiUnselect(spi); - efiPrintf("G0 firmware load ERROR: no ACK from SPI bootloader"); - return false; - } - - efiPrintf("G0 firmware load: erasing flash"); - if (!massErase(spi)) { - spiUnselect(spi); - efiPrintf("G0 firmware load ERROR: flash erase failed"); - return false; - } - - efiPrintf("G0 firmware load: writing %d bytes", static_cast(totalSize)); - for (size_t offset = 0; offset < totalSize; offset += 256) { - uint8_t block[256]; - const size_t blockSize = minI(256, totalSize - offset); - memset(block, 0xFF, sizeof(block)); - memcpy(block, build_g0_extension_bin + offset, blockSize); - - if (!writeBlock(spi, G0_FLASH_BASE + offset, block, sizeof(block))) { - spiUnselect(spi); - efiPrintf("G0 firmware load ERROR: write failed at offset %d", static_cast(offset)); - return false; - } - } - - efiPrintf("G0 firmware load: starting firmware"); - const bool ok = go(spi, G0_FLASH_BASE); - spiUnselect(spi); - - resetG0(false); - return ok; -} - -static bool injectG0FirmwareWithRetry(SPIDriver* spi) { - for (int attempt = 1; attempt <= G0_FLASH_MAX_ATTEMPTS; attempt++) { - efiPrintf("G0 firmware load: flash attempt %d/%d", attempt, G0_FLASH_MAX_ATTEMPTS); - - if (injectG0Firmware(spi)) { - return true; - } - - resetG0(false); - } - - efiPrintf("G0 firmware load ERROR: aborting G0 flash after %d failed attempts", G0_FLASH_MAX_ATTEMPTS); - return false; -} - -#endif // G0_FIRMWARE_IMAGE_AVAILABLE - -bool loadG0Firmware(bool forceUpdate) { -#if G0_FIRMWARE_IMAGE_AVAILABLE - initControlPins(); - - turnOnSpi(G0_SPI_DEVICE); - - SPIDriver* spi = getSpiDevice(G0_SPI_DEVICE); - spiAcquireBus(spi); - - initSpiCs(&g0SpiConfig, G0_SPI_CS_PIN); - palSetPad(g0SpiConfig.ssport, g0SpiConfig.sspad); - spiStart(spi, &g0SpiConfig); - resetG0(false); - - uint32_t currentVersion = 0; - bool ok = true; - - if (readG0AppVersion(spi, currentVersion)) { - efiPrintf( - "G0 firmware load: app version %d, bundled version %d", - static_cast(currentVersion), - static_cast(build_g0_extension_version)); - - if (!forceUpdate && currentVersion == build_g0_extension_version) { - efiPrintf("G0 firmware load: firmware is already current"); - } else { - efiPrintf("G0 firmware load: requesting app update mode%s", forceUpdate ? " (forced)" : ""); - requestG0AppUpdate(spi); - ok = injectG0FirmwareWithRetry(spi); - } - } else { - efiPrintf("G0 firmware load: no app version response, forcing update"); - ok = injectG0FirmwareWithRetry(spi); - } - - spiReleaseBus(spi); - releaseControlPins(); - - if (ok) { - efiPrintf("G0 firmware load: complete"); - } else { - efiPrintf("G0 firmware load: failed"); - } - - return ok; -#else - efiPrintf("G0 firmware load ERROR: run `make -C firmware/ext/g0_firmware for_fome_image` first"); - return false; -#endif -} - -#else - -bool loadG0Firmware(bool) { - efiPrintf("G0 firmware load is only available on Atlas with SPI enabled"); - return false; -} - -#endif diff --git a/firmware/controllers/system/g0_firmware_loader.h b/firmware/controllers/system/g0_firmware_loader.h deleted file mode 100644 index d9827c8aaf..0000000000 --- a/firmware/controllers/system/g0_firmware_loader.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -bool loadG0Firmware(bool forceUpdate = false); diff --git a/firmware/controllers/system/system.mk b/firmware/controllers/system/system.mk index 3416e59cff..cd5b7c56fa 100644 --- a/firmware/controllers/system/system.mk +++ b/firmware/controllers/system/system.mk @@ -6,4 +6,6 @@ SYSTEMSRC_CPP = \ $(PROJECT_DIR)/controllers/system/dc_motor.cpp \ $(PROJECT_DIR)/controllers/system/timer/scheduler.cpp \ $(PROJECT_DIR)/controllers/system/timer/trigger_scheduler.cpp \ - $(PROJECT_DIR)/controllers/system/g0_firmware_loader.cpp + $(PROJECT_DIR)/controllers/system/g0_extension_firmware.cpp \ + $(PROJECT_DIR)/controllers/system/g0_extension_firmware_protocol.cpp \ + $(PROJECT_DIR)/controllers/system/g0_extension_firmware_bootloader.cpp diff --git a/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp b/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp new file mode 100644 index 0000000000..3f82a4abed --- /dev/null +++ b/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp @@ -0,0 +1,281 @@ +#include "g0_extension_io_impl.h" + +#if EFI_PROD_CODE + +#include "ports/mpu_util.h" + +namespace g0_extension { +namespace { + +static SPIConfig g0ExtensionSpiConfig = { + .circular = false, + .end_cb = NULL, + .ssport = NULL, + .sspad = 0, + .cfg1 = 7 | SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, + .cfg2 = 0}; + +static NO_CACHE uint8_t txBuf[SpiAppFrameSize]; +static NO_CACHE uint8_t rxBuf[SpiAppFrameSize]; + +static void clearFrame(uint8_t* frame) { + for (size_t i = 0; i < SpiAppFrameSize; i++) { + frame[i] = 0; + } +} + +static uint16_t getU16(const uint8_t* buffer, uint8_t offset) { + return static_cast(buffer[offset]) | + static_cast(static_cast(buffer[offset + 1]) << 8); +} + +class G0ExtensionSpiAdcProvider final : public AdcProvider { +public: + const char* name() const override { + return "G0ExtensionSpiAdc"; + } + + bool enable(const char* name, size_t idx) override { + (void)name; + + if (idx >= AnalogChannelCount) { + return false; + } + + m_enabled[idx] = true; + return true; + } + + void disable(size_t idx) override { + if (idx >= AnalogChannelCount) { + return; + } + + m_enabled[idx] = false; + } + + float get(size_t idx) const override { + if (idx >= AnalogChannelCount || !m_ready) { + return 0.0f; + } + + return m_millivolts[idx] * 0.001f; + } + + bool readDigitalInput(size_t idx) const { + if (idx >= DigitalInputCount || !m_digitalReady) { + return false; + } + + return m_digitalLevels[idx]; + } + + void setLowsideOutput(size_t idx, bool value) { + m_outputs.requestOutput(idx, value, 0, value ? OutputDutyMax : 0); + } + + void disableLowsideOutput(size_t idx) { + m_outputs.requestOutput(idx, false, 0, 0); + } + + void setLowsidePwm(size_t idx, uint32_t frequencyHz, uint16_t duty) { + m_outputs.requestOutput(idx, true, frequencyHz, duty); + } + + void start() { + if (m_started) { + efiPrintf("G0 extension I/O: already started"); + return; + } + + m_started = true; + + turnOnSpi(SpiDevice); + m_spiDevice = getSpiDevice(SpiDevice); + + initSpiCs(&g0ExtensionSpiConfig, SpiCsPin); + palSetPad(g0ExtensionSpiConfig.ssport, g0ExtensionSpiConfig.sspad); + spiStart(m_spiDevice, &g0ExtensionSpiConfig); + + chThdCreateStatic(m_threadWa, sizeof(m_threadWa), NORMALPRIO, threadEntry, this); + } + +private: + static THD_FUNCTION(threadEntry, arg) { + static_cast(arg)->thread(); + } + + void thread() { + chRegSetThreadName("G0 Ext IO"); + + while (true) { + pollIo(); + chThdSleepMilliseconds(PollIntervalMs); + } + } + + void pollIo() { + PendingRequest nextRequest; + + spiAcquireBus(m_spiDevice); + clearFrame(txBuf); + clearFrame(rxBuf); + prepareRequest(txBuf, nextRequest); + + spiSelect(m_spiDevice); + spiExchange(m_spiDevice, SpiAppFrameSize, txBuf, rxBuf); + spiUnselect(m_spiDevice); + spiReleaseBus(m_spiDevice); + + parseResponse(rxBuf); + m_pendingRequest = nextRequest; + } + + void prepareRequest(uint8_t* tx, PendingRequest& nextRequest) { + nextRequest = {}; + + // The G0 app protocol is pipelined: the response we receive now + // belongs to the previous request, while this request schedules the next reply. + if (m_nextDigitalInputToConfigure <= DigitalInputCount) { + tx[0] = SpiCmdSetInputMode; + tx[1] = static_cast(m_nextDigitalInputToConfigure); + tx[2] = SpiDigitalMode; + m_nextDigitalInputToConfigure++; + return; + } + + m_outputs.prepareRequest(tx, nextRequest); + if (nextRequest.type != RequestType::None) { + return; + } + + tx[0] = m_pollAnalogNext ? SpiCmdReadAnalog : SpiCmdReadDigitalAll; + m_pollAnalogNext = !m_pollAnalogNext; + } + + void parseResponse(const uint8_t* rx) { + if (m_pendingRequest.type == RequestType::SetOutput || + m_pendingRequest.type == RequestType::DisableOutput) { + m_outputs.parseAck(m_pendingRequest, rx); + } + + switch (rx[2]) { + case SpiCmdReadAnalog: + parseAnalogResponse(rx); + break; + case SpiCmdReadDigitalAll: + parseDigitalResponse(rx); + break; + default: + break; + } + } + + void parseAnalogResponse(const uint8_t* rx) { + const uint8_t status = rx[0]; + const uint8_t result = rx[1]; + const uint8_t payloadLength = rx[3]; + + if (status != 0x00 && status != 0x01) { + m_ready = false; + return; + } + + if (result != SpiResultOk || payloadLength != 26) { + m_ready = false; + return; + } + + const bool analogReady = rx[SpiAppHeaderSize] != 0; + const uint8_t channelCount = rx[SpiAppHeaderSize + 1]; + + if (!analogReady) { + m_ready = false; + return; + } + + const size_t count = channelCount < AnalogChannelCount ? channelCount : AnalogChannelCount; + for (size_t i = 0; i < count; i++) { + const uint8_t offset = static_cast(SpiAppHeaderSize + 2 + i * 2); + m_millivolts[i] = getU16(rx, offset); + } + + m_ready = true; + } + + void parseDigitalResponse(const uint8_t* rx) { + const uint8_t status = rx[0]; + const uint8_t result = rx[1]; + const uint8_t payloadLength = rx[3]; + + if (status != 0x00 && status != 0x01) { + m_digitalReady = false; + return; + } + + if (result != SpiResultOk || payloadLength != 25) { + m_digitalReady = false; + return; + } + + const uint8_t inputCount = rx[SpiAppHeaderSize]; + if (inputCount != DigitalInputCount) { + m_digitalReady = false; + return; + } + + for (size_t i = 0; i < DigitalInputCount; i++) { + const uint8_t offset = static_cast(SpiAppHeaderSize + 1 + i * 6); + const uint8_t flags = rx[offset + 1]; + m_digitalLevels[i] = (flags & 0x01U) != 0; + } + + m_digitalReady = true; + } + +private: + bool m_started = false; + SPIDriver* m_spiDevice = nullptr; + bool m_enabled[AnalogChannelCount] = {}; + volatile bool m_ready = false; + volatile uint16_t m_millivolts[AnalogChannelCount] = {}; + volatile bool m_digitalReady = false; + volatile bool m_digitalLevels[DigitalInputCount] = {}; + OutputManager m_outputs; + PendingRequest m_pendingRequest = {}; + size_t m_nextDigitalInputToConfigure = 1; + bool m_pollAnalogNext = true; + THD_WORKING_AREA(m_threadWa, 512); +}; + +static G0ExtensionSpiAdcProvider g0ExtensionSpiAdcProvider; + +} // namespace + +AdcProvider& adcProvider() { + return g0ExtensionSpiAdcProvider; +} + +void startProvider() { + g0ExtensionSpiAdcProvider.start(); +} + +bool readDigitalInput(size_t idx) { + return g0ExtensionSpiAdcProvider.readDigitalInput(idx); +} + +void setLowsideOutput(size_t idx, bool value) { + g0ExtensionSpiAdcProvider.setLowsideOutput(idx, value); +} + +void disableLowsideOutput(size_t idx) { + g0ExtensionSpiAdcProvider.disableLowsideOutput(idx); +} + +void setLowsidePwm(size_t idx, uint32_t frequencyHz, uint16_t duty) { + g0ExtensionSpiAdcProvider.setLowsidePwm(idx, frequencyHz, duty); +} + +} // namespace g0_extension + +#endif diff --git a/firmware/hw_layer/g0_extension/g0_extension_io.cpp b/firmware/hw_layer/g0_extension/g0_extension_io.cpp new file mode 100644 index 0000000000..f6b34f60b8 --- /dev/null +++ b/firmware/hw_layer/g0_extension/g0_extension_io.cpp @@ -0,0 +1,120 @@ +#include "g0_extension_io.h" +#include "g0_extension_io_impl.h" + +#if EFI_PROD_CODE + +namespace { + +class G0ExtensionLowsideHardwarePwm final : public hardware_pwm { +public: + explicit G0ExtensionLowsideHardwarePwm(size_t outputIndex) + : m_outputIndex(outputIndex) {} + + void start(float frequencyHz, float duty) { + m_frequencyHz = frequencyHz < 1 ? 1 : static_cast(frequencyHz); + setDuty(duty); + } + + void setDuty(float duty) override { + if (duty < 0) { + duty = 0; + } else if (duty > 1) { + duty = 1; + } + + const auto scaledDuty = static_cast(duty * g0_extension::OutputDutyMax); + g0_extension::setLowsidePwm(m_outputIndex, m_frequencyHz, scaledDuty); + } + +private: + const size_t m_outputIndex; + uint32_t m_frequencyHz = 1; +}; + +static G0ExtensionLowsideHardwarePwm g0ExtensionLowsidePwms[g0_extension::OutputCount] = { + G0ExtensionLowsideHardwarePwm(0), + G0ExtensionLowsideHardwarePwm(1), + G0ExtensionLowsideHardwarePwm(2), + G0ExtensionLowsideHardwarePwm(3), +}; + +} // namespace + +void startG0ExtensionIo() { + static bool started = false; + + if (started) { + efiPrintf("G0 extension I/O: already started"); + return; + } + + started = true; + + g0_extension::startProvider(); + registerAdcProvider(g0_extension::adcProvider(), g0_extension::FirstAdcIndex, g0_extension::AnalogChannelCount); +} + +bool readG0ExtensionDigitalInput(size_t idx) { + return g0_extension::readDigitalInput(idx); +} + +bool isG0ExtensionLowsidePin(brain_pin_e pin) { + return pin >= Gpio::G0_LOWSIDE_0 && pin <= Gpio::G0_LOWSIDE_3; +} + +void setG0ExtensionLowsideOutput(brain_pin_e pin, bool value) { + if (!isG0ExtensionLowsidePin(pin)) { + return; + } + + g0_extension::setLowsideOutput(pin - Gpio::G0_LOWSIDE_0, value); +} + +void disableG0ExtensionLowsideOutput(brain_pin_e pin) { + if (!isG0ExtensionLowsidePin(pin)) { + return; + } + + g0_extension::disableLowsideOutput(pin - Gpio::G0_LOWSIDE_0); +} + +hardware_pwm* tryInitG0ExtensionLowsidePwm(brain_pin_e pin, float frequencyHz, float duty) { + if (!isG0ExtensionLowsidePin(pin)) { + return nullptr; + } + + auto& pwm = g0ExtensionLowsidePwms[pin - Gpio::G0_LOWSIDE_0]; + pwm.start(frequencyHz, duty); + return &pwm; +} + +#else + +void startG0ExtensionIo() {} + +bool readG0ExtensionDigitalInput(size_t idx) { + (void)idx; + return false; +} + +bool isG0ExtensionLowsidePin(brain_pin_e pin) { + return pin >= Gpio::G0_LOWSIDE_0 && pin <= Gpio::G0_LOWSIDE_3; +} + +void setG0ExtensionLowsideOutput(brain_pin_e pin, bool value) { + (void)pin; + (void)value; +} + +void disableG0ExtensionLowsideOutput(brain_pin_e pin) { + (void)pin; +} + +hardware_pwm* tryInitG0ExtensionLowsidePwm(brain_pin_e pin, float frequencyHz, float duty) { + (void)pin; + (void)frequencyHz; + (void)duty; + return nullptr; +} + +#endif diff --git a/firmware/hw_layer/g0_extension/g0_extension_io.h b/firmware/hw_layer/g0_extension/g0_extension_io.h new file mode 100644 index 0000000000..fee53b6483 --- /dev/null +++ b/firmware/hw_layer/g0_extension/g0_extension_io.h @@ -0,0 +1,15 @@ +#pragma once + +#include "pch.h" +#include "adc_provider.h" + +void startG0ExtensionIo(); +bool readG0ExtensionDigitalInput(size_t idx); +bool isG0ExtensionLowsidePin(brain_pin_e pin); +void setG0ExtensionLowsideOutput(brain_pin_e pin, bool value); +void disableG0ExtensionLowsideOutput(brain_pin_e pin); + +#ifdef __cplusplus +struct hardware_pwm; +hardware_pwm* tryInitG0ExtensionLowsidePwm(brain_pin_e pin, float frequencyHz, float duty); +#endif diff --git a/firmware/hw_layer/g0_extension/g0_extension_io_impl.h b/firmware/hw_layer/g0_extension/g0_extension_io_impl.h new file mode 100644 index 0000000000..e74eac6b75 --- /dev/null +++ b/firmware/hw_layer/g0_extension/g0_extension_io_impl.h @@ -0,0 +1,68 @@ +#pragma once + +#include "g0_extension_io.h" + +#if EFI_PROD_CODE + +namespace g0_extension { + +static constexpr size_t AnalogChannelCount = 12; +static constexpr size_t DigitalInputCount = 4; +static constexpr size_t OutputCount = 4; +static constexpr size_t SpiAppFrameSize = 36U; +static constexpr uint8_t SpiCmdReadAnalog = 0x10U; +static constexpr uint8_t SpiCmdReadDigitalAll = 0x12U; +static constexpr uint8_t SpiCmdSetOutput = 0x20U; +static constexpr uint8_t SpiCmdDisableOutput = 0x21U; +static constexpr uint8_t SpiCmdSetInputMode = 0x30U; +static constexpr uint8_t SpiAppHeaderSize = 4U; +static constexpr uint8_t SpiResultOk = 0x00U; +static constexpr uint8_t SpiDigitalMode = 0x00U; +static constexpr uint16_t OutputDutyMax = 10000U; +static constexpr spi_device_e SpiDevice = SPI_DEVICE_5; +static constexpr brain_pin_e SpiCsPin = Gpio::F6; +static constexpr size_t FirstAdcIndex = EFI_ADC_20 - EFI_ADC_0; +static constexpr uint32_t PollIntervalMs = 10; + +struct OutputState { + bool enabled = false; + uint32_t frequencyHz = 0; + uint16_t duty = 0; + bool dirty = false; + bool pendingAck = false; + bool sentEnabled = false; + uint32_t sentFrequencyHz = 0; + uint16_t sentDuty = 0; +}; + +enum class RequestType : uint8_t { + None, + SetOutput, + DisableOutput, +}; + +struct PendingRequest { + RequestType type = RequestType::None; + size_t outputIndex = 0; +}; + +class OutputManager { +public: + void prepareRequest(uint8_t* tx, PendingRequest& nextRequest); + void parseAck(const PendingRequest& pendingRequest, const uint8_t* rx); + void requestOutput(size_t idx, bool enabled, uint32_t frequencyHz, uint16_t duty); + +private: + OutputState m_outputs[OutputCount] = {}; +}; + +AdcProvider& adcProvider(); +void startProvider(); +bool readDigitalInput(size_t idx); +void setLowsideOutput(size_t idx, bool value); +void disableLowsideOutput(size_t idx); +void setLowsidePwm(size_t idx, uint32_t frequencyHz, uint16_t duty); + +} // namespace g0_extension + +#endif diff --git a/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp b/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp new file mode 100644 index 0000000000..33ada89d69 --- /dev/null +++ b/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp @@ -0,0 +1,92 @@ +#include "g0_extension_io_impl.h" + +#if EFI_PROD_CODE + +namespace g0_extension { +namespace { + +static void putU16(uint8_t* buffer, uint8_t offset, uint16_t value) { + buffer[offset] = static_cast(value); + buffer[offset + 1] = static_cast(value >> 8); +} + +static void putU32(uint8_t* buffer, uint8_t offset, uint32_t value) { + buffer[offset] = static_cast(value); + buffer[offset + 1] = static_cast(value >> 8); + buffer[offset + 2] = static_cast(value >> 16); + buffer[offset + 3] = static_cast(value >> 24); +} + +} // namespace + +void OutputManager::prepareRequest(uint8_t* tx, PendingRequest& nextRequest) { + nextRequest = {}; + + for (size_t i = 0; i < OutputCount; i++) { + if (!m_outputs[i].dirty || m_outputs[i].pendingAck) { + continue; + } + + const auto& output = m_outputs[i]; + nextRequest.outputIndex = i; + + if (output.enabled) { + tx[0] = SpiCmdSetOutput; + tx[1] = static_cast(i + 1); + putU32(tx, 2, output.frequencyHz); + putU16(tx, 6, output.duty); + nextRequest.type = RequestType::SetOutput; + } else { + tx[0] = SpiCmdDisableOutput; + tx[1] = static_cast(i + 1); + nextRequest.type = RequestType::DisableOutput; + } + + m_outputs[i].pendingAck = true; + m_outputs[i].sentEnabled = output.enabled; + m_outputs[i].sentFrequencyHz = output.frequencyHz; + m_outputs[i].sentDuty = output.duty; + return; + } +} + +void OutputManager::parseAck(const PendingRequest& pendingRequest, const uint8_t* rx) { + if (pendingRequest.outputIndex >= OutputCount) { + return; + } + + auto& output = m_outputs[pendingRequest.outputIndex]; + output.pendingAck = false; + + if (rx[1] != SpiResultOk) { + output.dirty = true; + return; + } + + output.dirty = output.enabled != output.sentEnabled || output.frequencyHz != output.sentFrequencyHz || + output.duty != output.sentDuty; +} + +void OutputManager::requestOutput(size_t idx, bool enabled, uint32_t frequencyHz, uint16_t duty) { + if (idx >= OutputCount) { + return; + } + + if (duty > OutputDutyMax) { + duty = OutputDutyMax; + } + + auto& output = m_outputs[idx]; + const bool changed = output.enabled != enabled || output.frequencyHz != frequencyHz || output.duty != duty; + + output.enabled = enabled; + output.frequencyHz = frequencyHz; + output.duty = duty; + if (changed) { + output.dirty = true; + } +} + +} // namespace g0_extension + +#endif diff --git a/firmware/hw_layer/g0_gpio/g0_analog.cpp b/firmware/hw_layer/g0_gpio/g0_analog.cpp deleted file mode 100644 index 084157d76a..0000000000 --- a/firmware/hw_layer/g0_gpio/g0_analog.cpp +++ /dev/null @@ -1,551 +0,0 @@ -#include "g0_analog.h" -#if EFI_PROD_CODE -#include "ports/mpu_util.h" - -namespace { - -static constexpr size_t G070_ANALOG_CHANNEL_COUNT = 12; -static constexpr size_t G070_DIGITAL_INPUT_COUNT = 4; -static constexpr size_t G070_OUTPUT_COUNT = 4; -static constexpr uint8_t spiAppFrameSize = 36U; -static constexpr uint8_t spiCmdReadAnalog = 0x10U; -static constexpr uint8_t spiCmdReadDigitalAll = 0x12U; -static constexpr uint8_t spiCmdNop = 0x00U; -static constexpr uint8_t spiCmdSetOutput = 0x20U; -static constexpr uint8_t spiCmdDisableOutput = 0x21U; -static constexpr uint8_t spiCmdSetInputMode = 0x30U; -static constexpr uint8_t spiAppHeaderSize = 4U; -static constexpr uint8_t spiResultOk = 0x00U; -static constexpr uint8_t spiResultInvalid = 0x01U; -static constexpr uint8_t spiResultRange = 0x02U; -static constexpr uint8_t spiResultBusy = 0x03U; -static constexpr uint8_t spiDigitalMode = 0x00U; -static constexpr uint16_t G070_OUTPUT_DUTY_MAX = 10000U; -static constexpr spi_device_e G0_SPI_DEVICE = SPI_DEVICE_5; -static constexpr brain_pin_e G0_SPI_CS_PIN = Gpio::F6; -static constexpr size_t G0_FIRST_ADC_INDEX = EFI_ADC_20 - EFI_ADC_0; -// Edit this value to tune the fixed G0 ADC poll rate. 10 ms = 100 Hz. -static constexpr uint32_t G0_POLL_INTERVAL_MS = 10; - -static SPIConfig g0AnalogSpiConfig = { - .circular = false, - .end_cb = NULL, - .ssport = NULL, - .sspad = 0, - .cfg1 = 7 | SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, - .cfg2 = 0}; - -static NO_CACHE uint8_t txBuf[spiAppFrameSize]; -static NO_CACHE uint8_t rxBuf[spiAppFrameSize]; - -struct G070OutputState { - bool enabled = false; - uint32_t frequencyHz = 0; - uint16_t duty = 0; - bool dirty = false; - bool pendingAck = false; - bool sentEnabled = false; - uint32_t sentFrequencyHz = 0; - uint16_t sentDuty = 0; -}; - -enum class G070RequestType : uint8_t { - None, - SetOutput, - DisableOutput, -}; - -struct G070PendingRequest { - G070RequestType type = G070RequestType::None; - size_t outputIndex = 0; -}; - -class G070SpiAdcProvider final : public AdcProvider { -public: - const char* name() const override { - return "G070SpiAdc"; - } - - SPIDriver* spiDevice = nullptr; - - bool enable(const char* name, size_t idx) override { - (void)name; - - if (idx >= G070_ANALOG_CHANNEL_COUNT) { - return false; - } - - m_enabled[idx] = true; - return true; - } - - void disable(size_t idx) override { - if (idx >= G070_ANALOG_CHANNEL_COUNT) { - return; - } - - m_enabled[idx] = false; - } - - float get(size_t idx) const override { - if (idx >= G070_ANALOG_CHANNEL_COUNT) { - return 0.0f; - } - - if (!m_ready) { - return 0.0f; - } - - return m_millivolts[idx] * 0.001f; - } - - bool readDigitalInput(size_t idx) const { - if (idx >= G070_DIGITAL_INPUT_COUNT) { - return false; - } - - if (!m_digitalReady) { - return false; - } - - return m_digitalLevels[idx]; - } - - void setLowsideOutput(size_t idx, bool value) { - requestOutput(idx, value, 0, value ? G070_OUTPUT_DUTY_MAX : 0); - } - - void disableLowsideOutput(size_t idx) { - requestOutput(idx, false, 0, 0); - } - - void setLowsidePwm(size_t idx, uint32_t frequencyHz, uint16_t duty) { - requestOutput(idx, true, frequencyHz, duty); - } - - void start() { - if (m_started) { - efiPrintf("G070 SPI ADC provider: already started"); - return; - } - - m_started = true; - - turnOnSpi(G0_SPI_DEVICE); - - spiDevice = getSpiDevice(G0_SPI_DEVICE); - - initSpiCs(&g0AnalogSpiConfig, G0_SPI_CS_PIN); - palSetPad(g0AnalogSpiConfig.ssport, g0AnalogSpiConfig.sspad); - - spiStart(spiDevice, &g0AnalogSpiConfig); - - chThdCreateStatic(m_threadWa, sizeof(m_threadWa), NORMALPRIO, threadEntry, this); - } - -private: - bool m_started = false; - - static THD_FUNCTION(threadEntry, arg) { - static_cast(arg)->thread(); - } - - void thread() { - chRegSetThreadName("G070 ADC"); - - while (true) { - pollIo(); - - chThdSleepMilliseconds(G0_POLL_INTERVAL_MS); - } - } - - void pollIo() { - G070PendingRequest nextRequest; - - spiAcquireBus(spiDevice); - - clearFrame(txBuf); - clearFrame(rxBuf); - - prepareRequest(txBuf, nextRequest); - - spiSelect(spiDevice); - spiExchange(spiDevice, spiAppFrameSize, txBuf, rxBuf); - spiUnselect(spiDevice); - - spiReleaseBus(spiDevice); - - parseResponse(rxBuf); - m_pendingRequest = nextRequest; - } - - void prepareRequest(uint8_t* tx, G070PendingRequest& nextRequest) { - nextRequest = {}; - - // The G0 SPI application protocol is pipelined: the response we receive now - // belongs to the previous request, while this request schedules the next reply. - if (m_nextDigitalInputToConfigure <= G070_DIGITAL_INPUT_COUNT) { - tx[0] = spiCmdSetInputMode; - tx[1] = static_cast(m_nextDigitalInputToConfigure); - tx[2] = spiDigitalMode; - m_nextDigitalInputToConfigure++; - return; - } - - for (size_t i = 0; i < G070_OUTPUT_COUNT; i++) { - if (!m_outputs[i].dirty || m_outputs[i].pendingAck) { - continue; - } - - const auto& output = m_outputs[i]; - nextRequest.outputIndex = i; - - if (output.enabled) { - tx[0] = spiCmdSetOutput; - tx[1] = static_cast(i + 1); - putU32(tx, 2, output.frequencyHz); - putU16(tx, 6, output.duty); - nextRequest.type = G070RequestType::SetOutput; - } else { - tx[0] = spiCmdDisableOutput; - tx[1] = static_cast(i + 1); - nextRequest.type = G070RequestType::DisableOutput; - } - - m_outputs[i].pendingAck = true; - m_outputs[i].sentEnabled = output.enabled; - m_outputs[i].sentFrequencyHz = output.frequencyHz; - m_outputs[i].sentDuty = output.duty; - return; - } - - if (m_pollAnalogNext) { - tx[0] = spiCmdReadAnalog; - } else { - tx[0] = spiCmdReadDigitalAll; - } - - m_pollAnalogNext = !m_pollAnalogNext; - } - - void parseResponse(const uint8_t* rx) { - if (m_pendingRequest.type == G070RequestType::SetOutput || - m_pendingRequest.type == G070RequestType::DisableOutput) { - parseOutputAck(rx); - } - - switch (rx[2]) { - case spiCmdReadAnalog: - parseAnalogResponse(rx); - break; - case spiCmdReadDigitalAll: - parseDigitalResponse(rx); - break; - default: - break; - } - } - - void parseOutputAck(const uint8_t* rx) { - if (m_pendingRequest.outputIndex >= G070_OUTPUT_COUNT) { - return; - } - - auto& output = m_outputs[m_pendingRequest.outputIndex]; - output.pendingAck = false; - - if (rx[1] != spiResultOk) { - output.dirty = true; - return; - } - - output.dirty = output.enabled != output.sentEnabled || output.frequencyHz != output.sentFrequencyHz || - output.duty != output.sentDuty; - } - - void parseAnalogResponse(const uint8_t* rx) { - const uint8_t status = rx[0]; - const uint8_t result = rx[1]; - const uint8_t payloadLength = rx[3]; - - // static constexpr size_t printThrottle = 50; - // static size_t printCounter = 0; - - // const bool shouldPrint = (++printCounter >= printThrottle); - - /* - if (shouldPrint) { - printCounter = 0; - - efiPrintf( - "G070 ADC response: status=0x%02X result=0x%02X command=0x%02X payloadLength=%d", - status, - result, - command, - payloadLength); - } - */ - - if (status != 0x00 && status != 0x01) { - m_ready = false; - return; - } - - if (result != spiResultOk) { - m_ready = false; - return; - } - - if (payloadLength != 26) { - m_ready = false; - return; - } - - const bool analogReady = rx[spiAppHeaderSize] != 0; - const uint8_t channelCount = rx[spiAppHeaderSize + 1]; - - if (!analogReady) { - m_ready = false; - return; - } - - const size_t count = channelCount < G070_ANALOG_CHANNEL_COUNT ? channelCount : G070_ANALOG_CHANNEL_COUNT; - - for (size_t i = 0; i < count; i++) { - const uint8_t offset = static_cast(spiAppHeaderSize + 2 + i * 2); - - m_millivolts[i] = getU16(rx, offset); - } - - /* - if (shouldPrint) { - efiPrintf("G070 ADC values (mV):"); - - for (size_t i = 0; i < count; i++) { - efiPrintf(" %d", m_millivolts[i]); - } - - efiPrintf(""); - } - */ - - m_ready = true; - } - - void parseDigitalResponse(const uint8_t* rx) { - const uint8_t status = rx[0]; - const uint8_t result = rx[1]; - const uint8_t payloadLength = rx[3]; - - if (status != 0x00 && status != 0x01) { - m_digitalReady = false; - return; - } - - if (result != spiResultOk) { - m_digitalReady = false; - return; - } - - if (payloadLength != 25) { - m_digitalReady = false; - return; - } - - const uint8_t inputCount = rx[spiAppHeaderSize]; - - if (inputCount != G070_DIGITAL_INPUT_COUNT) { - m_digitalReady = false; - return; - } - - for (size_t i = 0; i < G070_DIGITAL_INPUT_COUNT; i++) { - const uint8_t offset = static_cast(spiAppHeaderSize + 1 + i * 6); - const uint8_t flags = rx[offset + 1]; - - m_digitalLevels[i] = (flags & 0x01U) != 0; - } - - m_digitalReady = true; - } - - static void clearFrame(uint8_t* frame) { - for (size_t i = 0; i < spiAppFrameSize; i++) { - frame[i] = 0; - } - } - - static void putU16(uint8_t* buffer, uint8_t offset, uint16_t value) { - buffer[offset] = static_cast(value); - buffer[offset + 1] = static_cast(value >> 8); - } - - static void putU32(uint8_t* buffer, uint8_t offset, uint32_t value) { - buffer[offset] = static_cast(value); - buffer[offset + 1] = static_cast(value >> 8); - buffer[offset + 2] = static_cast(value >> 16); - buffer[offset + 3] = static_cast(value >> 24); - } - - static uint16_t getU16(const uint8_t* buffer, uint8_t offset) { - return static_cast(buffer[offset]) | - static_cast(static_cast(buffer[offset + 1]) << 8); - } - - void requestOutput(size_t idx, bool enabled, uint32_t frequencyHz, uint16_t duty) { - if (idx >= G070_OUTPUT_COUNT) { - return; - } - - if (duty > G070_OUTPUT_DUTY_MAX) { - duty = G070_OUTPUT_DUTY_MAX; - } - - auto& output = m_outputs[idx]; - const bool changed = output.enabled != enabled || output.frequencyHz != frequencyHz || output.duty != duty; - - output.enabled = enabled; - output.frequencyHz = frequencyHz; - output.duty = duty; - if (changed) { - output.dirty = true; - } - } - -private: - bool m_enabled[G070_ANALOG_CHANNEL_COUNT] = {}; - volatile bool m_ready = false; - volatile uint16_t m_millivolts[G070_ANALOG_CHANNEL_COUNT] = {}; - volatile bool m_digitalReady = false; - volatile bool m_digitalLevels[G070_DIGITAL_INPUT_COUNT] = {}; - G070OutputState m_outputs[G070_OUTPUT_COUNT] = {}; - G070PendingRequest m_pendingRequest = {}; - size_t m_nextDigitalInputToConfigure = 1; - bool m_pollAnalogNext = true; - - THD_WORKING_AREA(m_threadWa, 512); -}; - -} // namespace - -namespace { - -static G070SpiAdcProvider g070SpiAdcProvider; - -#if EFI_PROD_CODE -class G070LowsideHardwarePwm final : public hardware_pwm { -public: - explicit G070LowsideHardwarePwm(size_t outputIndex) - : m_outputIndex(outputIndex) {} - - void start(float frequencyHz, float duty) { - m_frequencyHz = frequencyHz < 1 ? 1 : static_cast(frequencyHz); - setDuty(duty); - } - - void setDuty(float duty) override { - if (duty < 0) { - duty = 0; - } else if (duty > 1) { - duty = 1; - } - - const auto scaledDuty = static_cast(duty * G070_OUTPUT_DUTY_MAX); - g070SpiAdcProvider.setLowsidePwm(m_outputIndex, m_frequencyHz, scaledDuty); - } - -private: - const size_t m_outputIndex; - uint32_t m_frequencyHz = 1; -}; - -static G070LowsideHardwarePwm g070LowsidePwms[G070_OUTPUT_COUNT] = { - G070LowsideHardwarePwm(0), - G070LowsideHardwarePwm(1), - G070LowsideHardwarePwm(2), - G070LowsideHardwarePwm(3), -}; -#endif - -} // namespace - -void startG070SpiAdcProvider() { - static bool started = false; - - if (started) { - efiPrintf("startG070SpiAdcProvider: already started"); - return; - } - - started = true; - - g070SpiAdcProvider.start(); - - registerAdcProvider(g070SpiAdcProvider, G0_FIRST_ADC_INDEX, G070_ANALOG_CHANNEL_COUNT); -} - -bool readG070DigitalInput(size_t idx) { - return g070SpiAdcProvider.readDigitalInput(idx); -} - -bool isG070LowsidePin(brain_pin_e pin) { - return pin >= Gpio::G0_LOWSIDE_0 && pin <= Gpio::G0_LOWSIDE_3; -} - -void setG070LowsideOutput(brain_pin_e pin, bool value) { - if (!isG070LowsidePin(pin)) { - return; - } - - g070SpiAdcProvider.setLowsideOutput(pin - Gpio::G0_LOWSIDE_0, value); -} - -void disableG070LowsideOutput(brain_pin_e pin) { - if (!isG070LowsidePin(pin)) { - return; - } - - g070SpiAdcProvider.disableLowsideOutput(pin - Gpio::G0_LOWSIDE_0); -} - -hardware_pwm* tryInitG070LowsidePwm(brain_pin_e pin, float frequencyHz, float duty) { - if (!isG070LowsidePin(pin)) { - return nullptr; - } - - auto& pwm = g070LowsidePwms[pin - Gpio::G0_LOWSIDE_0]; - pwm.start(frequencyHz, duty); - return &pwm; -} - -#else - -void startG070SpiAdcProvider() {} - -bool readG070DigitalInput(size_t idx) { - (void)idx; - return false; -} - -bool isG070LowsidePin(brain_pin_e pin) { - return pin >= Gpio::G0_LOWSIDE_0 && pin <= Gpio::G0_LOWSIDE_3; -} - -void setG070LowsideOutput(brain_pin_e pin, bool value) { - (void)pin; - (void)value; -} - -void disableG070LowsideOutput(brain_pin_e pin) { - (void)pin; -} - -hardware_pwm* tryInitG070LowsidePwm(brain_pin_e pin, float frequencyHz, float duty) { - (void)pin; - (void)frequencyHz; - (void)duty; - return nullptr; -} - -#endif diff --git a/firmware/hw_layer/g0_gpio/g0_analog.h b/firmware/hw_layer/g0_gpio/g0_analog.h deleted file mode 100644 index 4b4a4c1c7c..0000000000 --- a/firmware/hw_layer/g0_gpio/g0_analog.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "pch.h" -#include "adc_provider.h" - -void startG070SpiAdcProvider(); -bool readG070DigitalInput(size_t idx); -bool isG070LowsidePin(brain_pin_e pin); -void setG070LowsideOutput(brain_pin_e pin, bool value); -void disableG070LowsideOutput(brain_pin_e pin); - -#ifdef __cplusplus -struct hardware_pwm; -hardware_pwm* tryInitG070LowsidePwm(brain_pin_e pin, float frequencyHz, float duty); -#endif diff --git a/firmware/hw_layer/hw_layer.mk b/firmware/hw_layer/hw_layer.mk index 0690e6cd2e..2b1aaa9941 100644 --- a/firmware/hw_layer/hw_layer.mk +++ b/firmware/hw_layer/hw_layer.mk @@ -30,7 +30,9 @@ HW_LAYER_EMS_CPP = \ $(PROJECT_DIR)/hw_layer/io_pins.cpp \ $(PROJECT_DIR)/hw_layer/rtc_helper.cpp \ $(PROJECT_DIR)/hw_layer/debounce.cpp \ - $(PROJECT_DIR)/hw_layer/g0_gpio/g0_analog.cpp + $(PROJECT_DIR)/hw_layer/g0_extension/g0_extension_io.cpp \ + $(PROJECT_DIR)/hw_layer/g0_extension/g0_extension_adc_provider.cpp \ + $(PROJECT_DIR)/hw_layer/g0_extension/g0_extension_output_manager.cpp ifeq ($(USE_OPENBLT),yes) HW_LAYER_EMS += \ @@ -41,4 +43,3 @@ endif # '-include' is a magic kind of 'include' which would survive if file to be included is not found # -include $(PROJECT_DIR)/hw_layer/$(CPU_HWLAYER)/hw_ports.mk - diff --git a/firmware/hw_layer/io_pins.cpp b/firmware/hw_layer/io_pins.cpp index 81e915e9cc..6e99541d7f 100644 --- a/firmware/hw_layer/io_pins.cpp +++ b/firmware/hw_layer/io_pins.cpp @@ -12,7 +12,7 @@ #if EFI_PROD_CODE #include "drivers/gpio/gpio_ext.h" -#include "hw_layer/g0_gpio/g0_analog.h" +#include "hw_layer/g0_extension/g0_extension_io.h" #include "status_loop.h" #include "console_io.h" @@ -151,7 +151,7 @@ bool efiReadPin(Gpio pin) { } if (pin >= Gpio::G0_DIGITAL_IN_0 && pin <= Gpio::G0_DIGITAL_IN_3) { - return readG070DigitalInput(pin - Gpio::G0_DIGITAL_IN_0); + return readG0ExtensionDigitalInput(pin - Gpio::G0_DIGITAL_IN_0); } /* incorrect pin */ diff --git a/firmware/hw_layer/ports/stm32/stm32_common.cpp b/firmware/hw_layer/ports/stm32/stm32_common.cpp index cc0dab4a9b..26d243784b 100644 --- a/firmware/hw_layer/ports/stm32/stm32_common.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_common.cpp @@ -182,7 +182,7 @@ void Stm32AdcProviderBase::disable(size_t idx) { #if EFI_PROD_CODE #if HAL_USE_PWM -#include "hw_layer/g0_gpio/g0_analog.h" +#include "hw_layer/g0_extension/g0_extension_io.h" namespace { struct stm32_pwm_config { PWMDriver* const Driver; @@ -369,7 +369,7 @@ stm32_hardware_pwm* getNextPwmDevice() { } /*static*/ hardware_pwm* hardware_pwm::tryInitPin(const char* msg, brain_pin_e pin, float frequencyHz, float duty) { - if (auto* g0Pwm = tryInitG070LowsidePwm(pin, frequencyHz, duty)) { + if (auto* g0Pwm = tryInitG0ExtensionLowsidePwm(pin, frequencyHz, duty)) { return g0Pwm; } diff --git a/firmware/rusefi.cpp b/firmware/rusefi.cpp index 817a2b9c87..62604ff26d 100644 --- a/firmware/rusefi.cpp +++ b/firmware/rusefi.cpp @@ -116,8 +116,8 @@ #include "trigger_emulator_algo.h" #include "rusefi_lua.h" #include "bootloader_updater.h" -#include "g0_firmware_loader.h" -#include "hw_layer/g0_gpio/g0_analog.h" +#include "g0_extension_firmware.h" +#include "hw_layer/g0_extension/g0_extension_io.h" #include @@ -175,7 +175,7 @@ void runRusEfi() { addConsoleAction(CMD_REBOOT, scheduleReboot); addConsoleAction(CMD_REBOOT_DFU, jump_to_bootloader); - addConsoleAction("force_g0_update", []() { loadG0Firmware(true); }); + addConsoleAction("force_g0_update", []() { loadG0ExtensionFirmware(true); }); #if EFI_USE_OPENBLT addConsoleAction(CMD_REBOOT_OPENBLT, jump_to_openblt); @@ -210,8 +210,8 @@ void runRusEfi() { loadConfiguration(); #if HW_ATLAS - loadG0Firmware(); - startG070SpiAdcProvider(); + loadG0ExtensionFirmware(); + startG0ExtensionIo(); #endif #if EFI_TUNER_STUDIO From b35b82b39109671e5d76f74fbc54071c5319520d Mon Sep 17 00:00:00 2001 From: dynfer Date: Sun, 10 May 2026 19:09:52 +0200 Subject: [PATCH 38/47] add g0 detection --- firmware/console/binary/output_channels.txt | 1 + firmware/console/status_loop.cpp | 2 + .../hw_layer/g0_extension/g0_extension_io.cpp | 18 ++ .../hw_layer/g0_extension/g0_extension_io.h | 2 + firmware/rusefi.cpp | 8 +- .../com/rusefi/output/TSProjectConsumer.java | 258 +++++++++++++++++- .../rusefi/test/TSProjectConsumerTest.java | 24 ++ 7 files changed, 300 insertions(+), 13 deletions(-) diff --git a/firmware/console/binary/output_channels.txt b/firmware/console/binary/output_channels.txt index 6f5f8674c7..574a3aec9a 100644 --- a/firmware/console/binary/output_channels.txt +++ b/firmware/console/binary/output_channels.txt @@ -29,6 +29,7 @@ struct_no_prefix output_channels_s bit ignitionFault;Error: Ignition Fault bit isUsbConnected;isUsbConnected\nOriginal reason for this is to check if USB is connected from Lua bit dfcoActive;DFCO: Active + bit g0Present;G0 extension: Present uint16_t RPMValue;@@GAUGE_NAME_RPM@@;"RPM",1, 0, 0, 8000, 0 diff --git a/firmware/console/status_loop.cpp b/firmware/console/status_loop.cpp index 570dcf4676..987b7560c3 100644 --- a/firmware/console/status_loop.cpp +++ b/firmware/console/status_loop.cpp @@ -38,6 +38,7 @@ #include "main_trigger_callback.h" #include "spark_logic.h" #include "gitversion.h" +#include "hw_layer/g0_extension/g0_extension_io.h" #include "can_hw.h" #include "periodic_thread_controller.h" #include "binary_logging.h" @@ -492,6 +493,7 @@ void updateTunerStudioState() { tsOutputChannels->gyroYaw = engine->sensors.accelerometer.yawRate; tsOutputChannels->turboSpeed = Sensor::getOrZero(SensorType::TurbochargerSpeed); + tsOutputChannels->g0Present = isG0ExtensionPresent(); extern FrequencySensor vehicleSpeedSensor; tsOutputChannels->vssEdgeCounter = vehicleSpeedSensor.eventCounter; diff --git a/firmware/hw_layer/g0_extension/g0_extension_io.cpp b/firmware/hw_layer/g0_extension/g0_extension_io.cpp index f6b34f60b8..be738fd4f2 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_io.cpp +++ b/firmware/hw_layer/g0_extension/g0_extension_io.cpp @@ -5,6 +5,8 @@ namespace { +static bool g0ExtensionPresent = false; + class G0ExtensionLowsideHardwarePwm final : public hardware_pwm { public: explicit G0ExtensionLowsideHardwarePwm(size_t outputIndex) @@ -40,6 +42,14 @@ static G0ExtensionLowsideHardwarePwm g0ExtensionLowsidePwms[g0_extension::Output } // namespace +void setG0ExtensionPresent(bool present) { + g0ExtensionPresent = present; +} + +bool isG0ExtensionPresent() { + return g0ExtensionPresent; +} + void startG0ExtensionIo() { static bool started = false; @@ -92,6 +102,14 @@ hardware_pwm* tryInitG0ExtensionLowsidePwm(brain_pin_e pin, float frequencyHz, f void startG0ExtensionIo() {} +void setG0ExtensionPresent(bool present) { + (void)present; +} + +bool isG0ExtensionPresent() { + return false; +} + bool readG0ExtensionDigitalInput(size_t idx) { (void)idx; return false; diff --git a/firmware/hw_layer/g0_extension/g0_extension_io.h b/firmware/hw_layer/g0_extension/g0_extension_io.h index fee53b6483..2c22b9fe10 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_io.h +++ b/firmware/hw_layer/g0_extension/g0_extension_io.h @@ -4,6 +4,8 @@ #include "adc_provider.h" void startG0ExtensionIo(); +void setG0ExtensionPresent(bool present); +bool isG0ExtensionPresent(); bool readG0ExtensionDigitalInput(size_t idx); bool isG0ExtensionLowsidePin(brain_pin_e pin); void setG0ExtensionLowsideOutput(brain_pin_e pin, bool value); diff --git a/firmware/rusefi.cpp b/firmware/rusefi.cpp index 62604ff26d..b945b7df3d 100644 --- a/firmware/rusefi.cpp +++ b/firmware/rusefi.cpp @@ -175,7 +175,7 @@ void runRusEfi() { addConsoleAction(CMD_REBOOT, scheduleReboot); addConsoleAction(CMD_REBOOT_DFU, jump_to_bootloader); - addConsoleAction("force_g0_update", []() { loadG0ExtensionFirmware(true); }); + addConsoleAction("force_g0_update", []() { setG0ExtensionPresent(loadG0ExtensionFirmware(true)); }); #if EFI_USE_OPENBLT addConsoleAction(CMD_REBOOT_OPENBLT, jump_to_openblt); @@ -210,8 +210,10 @@ void runRusEfi() { loadConfiguration(); #if HW_ATLAS - loadG0ExtensionFirmware(); - startG0ExtensionIo(); + setG0ExtensionPresent(loadG0ExtensionFirmware()); + if (isG0ExtensionPresent()) { + startG0ExtensionIo(); + } #endif #if EFI_TUNER_STUDIO diff --git a/java_tools/configuration_definition/src/main/java/com/rusefi/output/TSProjectConsumer.java b/java_tools/configuration_definition/src/main/java/com/rusefi/output/TSProjectConsumer.java index 2f8b720f6f..349c37cafc 100644 --- a/java_tools/configuration_definition/src/main/java/com/rusefi/output/TSProjectConsumer.java +++ b/java_tools/configuration_definition/src/main/java/com/rusefi/output/TSProjectConsumer.java @@ -8,6 +8,10 @@ import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import static com.rusefi.util.IoUtils.CHARSET; @@ -18,6 +22,9 @@ public class TSProjectConsumer implements ConfigurationConsumer { private static final String CONFIG_DEFINITION_START = "CONFIG_DEFINITION_START"; private static final String CONFIG_DEFINITION_END = "CONFIG_DEFINITION_END"; private static final String TS_CONDITION = "@@if_"; + private static final String FIELD_DIRECTIVE = "field ="; + private static final String G0_PRESENT_FIELD = "g0Present"; + private static final String NO_G0_SUFFIX = "_nog0"; public static final String SETTING_CONTEXT_HELP_END = "SettingContextHelpEnd"; public static final String SETTING_CONTEXT_HELP = "SettingContextHelp"; @@ -48,22 +55,59 @@ protected void writeTunerStudioFile(String inputFile, String fieldsSection) thro } protected void writeContent(String fieldsSection, TsFileContent tsContent, Output tsHeader) throws IOException { - tsHeader.write(tsContent.getPrefix()); + StringBuilder content = new StringBuilder(); - tsHeader.write("; " + CONFIG_DEFINITION_START + ToolUtil.EOL); - tsHeader.write("pageSize = " + totalTsSize + ToolUtil.EOL); - tsHeader.write("page = 1" + ToolUtil.EOL); - tsHeader.write(fieldsSection); + content.append(tsContent.getPrefix()); + + content.append("; ").append(CONFIG_DEFINITION_START).append(ToolUtil.EOL); + content.append("pageSize = ").append(totalTsSize).append(ToolUtil.EOL); + content.append("page = 1").append(ToolUtil.EOL); + content.append(fieldsSection); if (!tsOutput.getSettingContextHelp().isEmpty()) { - tsHeader.write("[" + SETTING_CONTEXT_HELP + "]" + ToolUtil.EOL); - tsHeader.write(tsOutput.getSettingContextHelp() + ToolUtil.EOL + ToolUtil.EOL); - tsHeader.write("; " + SETTING_CONTEXT_HELP_END + ToolUtil.EOL); + content.append("[").append(SETTING_CONTEXT_HELP).append("]").append(ToolUtil.EOL); + content.append(tsOutput.getSettingContextHelp()).append(ToolUtil.EOL).append(ToolUtil.EOL); + content.append("; ").append(SETTING_CONTEXT_HELP_END).append(ToolUtil.EOL); } - tsHeader.write("; " + CONFIG_DEFINITION_END + ToolUtil.EOL); - tsHeader.write(tsContent.getPostfix()); + content.append("; ").append(CONFIG_DEFINITION_END).append(ToolUtil.EOL); + content.append(tsContent.getPostfix()); + + tsHeader.write(applyG0PinVisibility(content.toString())); tsHeader.close(); } + public static String applyG0PinVisibility(String content) { + String[] lines = content.split("\\r?\\n", -1); + Map g0PinFields = new LinkedHashMap<>(); + StringBuilder withAlternatePinDefs = new StringBuilder(); + + for (String line : lines) { + withAlternatePinDefs.append(line).append(ToolUtil.EOL); + + AlternatePinField alternateField = createAlternatePinField(line); + if (alternateField != null) { + g0PinFields.put(alternateField.originalName, alternateField.alternateName); + withAlternatePinDefs.append(alternateField.definitionLine).append(ToolUtil.EOL); + } + } + + if (g0PinFields.isEmpty()) { + return content; + } + + StringBuilder result = new StringBuilder(); + for (String line : withAlternatePinDefs.toString().split("\\r?\\n", -1)) { + DialogFieldReplacement replacement = createDialogFieldReplacement(line, g0PinFields); + if (replacement != null) { + result.append(replacement.whenPresent).append(ToolUtil.EOL); + result.append(replacement.whenMissing).append(ToolUtil.EOL); + } else { + result.append(line).append(ToolUtil.EOL); + } + } + + return result.toString(); + } + /** * tunerstudio.template.ini has all the content of the future .ini file with the exception of data page * TODO: start generating [outputs] section as well @@ -132,6 +176,200 @@ public static String getToken(String line) { return token.toString(); } + private static AlternatePinField createAlternatePinField(String line) { + int equalsIndex = line.indexOf('='); + if (equalsIndex < 0) { + return null; + } + + String fieldName = line.substring(0, equalsIndex).trim(); + if (fieldName.isEmpty() || fieldName.startsWith(";")) { + return null; + } + + List tokens = splitTopLevelCsv(line.substring(equalsIndex + 1)); + if (tokens.size() < 6) { + return null; + } + + String typeToken = tokens.get(0).trim(); + String storageToken = tokens.get(1).trim(); + if (!"bits".equals(typeToken) || !(storageToken.equals("U08") || storageToken.equals("U16"))) { + return null; + } + + boolean containsG0Option = false; + List filteredTokens = new ArrayList<>(); + for (String token : tokens) { + if (isG0OptionToken(token)) { + containsG0Option = true; + continue; + } + filteredTokens.add(token.trim()); + } + + if (!containsG0Option) { + return null; + } + + String alternateName = fieldName + NO_G0_SUFFIX; + String definitionLine = alternateName + " = " + String.join(", ", filteredTokens); + return new AlternatePinField(fieldName, alternateName, definitionLine); + } + + private static DialogFieldReplacement createDialogFieldReplacement(String line, Map g0PinFields) { + int fieldDirectiveIndex = line.indexOf(FIELD_DIRECTIVE); + if (fieldDirectiveIndex < 0) { + return null; + } + + String indent = line.substring(0, fieldDirectiveIndex); + List tokens = splitTopLevelCsv(line.substring(fieldDirectiveIndex + FIELD_DIRECTIVE.length())); + if (tokens.size() < 2) { + return null; + } + + String fieldName = tokens.get(1).trim(); + String alternateFieldName = g0PinFields.get(fieldName); + if (alternateFieldName == null) { + return null; + } + + List whenPresent = new ArrayList<>(trimmedCopy(tokens)); + List whenMissing = new ArrayList<>(whenPresent); + whenMissing.set(1, alternateFieldName); + + applyVisibilityCondition(whenPresent, G0_PRESENT_FIELD); + applyVisibilityCondition(whenMissing, "!" + G0_PRESENT_FIELD); + + return new DialogFieldReplacement( + indent + FIELD_DIRECTIVE + " " + String.join(", ", whenPresent), + indent + FIELD_DIRECTIVE + " " + String.join(", ", whenMissing) + ); + } + + private static List trimmedCopy(List tokens) { + List result = new ArrayList<>(tokens.size()); + for (String token : tokens) { + result.add(token.trim()); + } + return result; + } + + private static void applyVisibilityCondition(List tokens, String condition) { + if (tokens.size() == 2) { + tokens.add(wrapCondition(condition, null)); + return; + } + + int visibilityIndex = -1; + if (isExpressionToken(tokens.get(2))) { + visibilityIndex = 2; + } else if (tokens.size() > 3 && isExpressionToken(tokens.get(3))) { + visibilityIndex = 3; + } + + if (visibilityIndex == -1) { + tokens.add(wrapCondition(condition, null)); + } else { + tokens.set(visibilityIndex, wrapCondition(condition, tokens.get(visibilityIndex))); + } + } + + private static boolean isExpressionToken(String token) { + String trimmed = token.trim(); + return trimmed.startsWith("{") && trimmed.endsWith("}"); + } + + private static String wrapCondition(String condition, String existingToken) { + if (existingToken == null) { + return "{ " + condition + " }"; + } + + String trimmed = existingToken.trim(); + String expression = trimmed.substring(1, trimmed.length() - 1).trim(); + return "{ (" + condition + ") && (" + expression + ") }"; + } + + private static boolean isG0OptionToken(String token) { + String trimmed = token.trim(); + return trimmed.matches("\\d+=\"G0 [^\"]+\""); + } + + private static List splitTopLevelCsv(String line) { + List tokens = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inQuotes = false; + int braceDepth = 0; + int bracketDepth = 0; + int parenDepth = 0; + + for (int i = 0; i < line.length(); i++) { + char ch = line.charAt(i); + + if (ch == '"' && (i == 0 || line.charAt(i - 1) != '\\')) { + inQuotes = !inQuotes; + } else if (!inQuotes) { + switch (ch) { + case '{': + braceDepth++; + break; + case '}': + braceDepth--; + break; + case '[': + bracketDepth++; + break; + case ']': + bracketDepth--; + break; + case '(': + parenDepth++; + break; + case ')': + parenDepth--; + break; + case ',': + if (braceDepth == 0 && bracketDepth == 0 && parenDepth == 0) { + tokens.add(current.toString().trim()); + current.setLength(0); + continue; + } + break; + default: + break; + } + } + + current.append(ch); + } + + tokens.add(current.toString().trim()); + return tokens; + } + + private static class AlternatePinField { + private final String originalName; + private final String alternateName; + private final String definitionLine; + + private AlternatePinField(String originalName, String alternateName, String definitionLine) { + this.originalName = originalName; + this.alternateName = alternateName; + this.definitionLine = definitionLine; + } + } + + private static class DialogFieldReplacement { + private final String whenPresent; + private final String whenMissing; + + private DialogFieldReplacement(String whenPresent, String whenMissing) { + this.whenPresent = whenPresent; + this.whenMissing = whenMissing; + } + } + @Override public void endFile() throws IOException { writeTunerStudioFile(tsPath, getContent()); diff --git a/java_tools/configuration_definition/src/test/java/com/rusefi/test/TSProjectConsumerTest.java b/java_tools/configuration_definition/src/test/java/com/rusefi/test/TSProjectConsumerTest.java index 22857499a6..60cde94351 100644 --- a/java_tools/configuration_definition/src/test/java/com/rusefi/test/TSProjectConsumerTest.java +++ b/java_tools/configuration_definition/src/test/java/com/rusefi/test/TSProjectConsumerTest.java @@ -83,4 +83,28 @@ public void conditionalField() { "static_assert(sizeof(pid_s) == 24);\n" + "\n", consumer.getContent()); } + + @Test + public void g0PinVisibilityPostProcessing() { + String input = "" + + "fanPin = bits, U16, 522, [0:7], 0=\"NONE\", 231=\"G0 Lowside 1\", 103=\"Lowside 1\"\n" + + "acSwitch = bits, U16, 1056, [0:7], 0=\"NONE\", 227=\"G0 Digital 1\", 98=\"Digital 1\"\n" + + "\tfield = \"Output\", fanPin\n" + + "\tfield = \"A/C switch\", acSwitch, { acSwitch != 0 }, { someEnable }\n"; + + String result = TSProjectConsumer.applyG0PinVisibility(input); + + assertEquals( + "fanPin = bits, U16, 522, [0:7], 0=\"NONE\", 231=\"G0 Lowside 1\", 103=\"Lowside 1\"\n" + + "fanPin_nog0 = bits, U16, 522, [0:7], 0=\"NONE\", 103=\"Lowside 1\"\n" + + "acSwitch = bits, U16, 1056, [0:7], 0=\"NONE\", 227=\"G0 Digital 1\", 98=\"Digital 1\"\n" + + "acSwitch_nog0 = bits, U16, 1056, [0:7], 0=\"NONE\", 98=\"Digital 1\"\n" + + "\tfield = \"Output\", fanPin, { g0Present }\n" + + "\tfield = \"Output\", fanPin_nog0, { !g0Present }\n" + + "\tfield = \"A/C switch\", acSwitch, { (g0Present) && (acSwitch != 0) }, { someEnable }\n" + + "\tfield = \"A/C switch\", acSwitch_nog0, { (!g0Present) && (acSwitch != 0) }, { someEnable }\n" + + "\n" + + "\n", + result); + } } From 066d0a935063152105ef67b06b4dcf0177011bc8 Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 12 May 2026 19:35:35 +0200 Subject: [PATCH 39/47] shared header --- .../system/g0_extension_firmware_impl.h | 12 +--- .../system/g0_extension_firmware_protocol.cpp | 30 +++++----- firmware/ext/g0_firmware | 2 +- .../g0_extension_adc_provider.cpp | 60 +++++++++---------- .../hw_layer/g0_extension/g0_extension_io.cpp | 6 +- .../g0_extension/g0_extension_io_impl.h | 18 ++---- .../g0_extension_output_manager.cpp | 16 ++--- 7 files changed, 64 insertions(+), 80 deletions(-) diff --git a/firmware/controllers/system/g0_extension_firmware_impl.h b/firmware/controllers/system/g0_extension_firmware_impl.h index 1c9ee956b2..ccf9cd8749 100644 --- a/firmware/controllers/system/g0_extension_firmware_impl.h +++ b/firmware/controllers/system/g0_extension_firmware_impl.h @@ -1,6 +1,7 @@ #pragma once #include "pch.h" +#include "../../ext/g0_firmware/for_fome/g0_spi_protocol.h" #if HW_ATLAS && HAL_USE_SPI @@ -13,6 +14,8 @@ namespace g0_extension_firmware { +namespace app = ::g0_spi_protocol; + static constexpr spi_device_e SpiDevice = SPI_DEVICE_5; static constexpr brain_pin_e ResetPin = Gpio::B14; static constexpr brain_pin_e BootPin = Gpio::B15; @@ -28,16 +31,7 @@ static constexpr uint8_t BootloaderGo = 0x21; static constexpr int BootloaderInterByteDelayUs = 20; static constexpr int FlashMaxAttempts = 2; -static constexpr uint8_t AppCmdNop = 0x00; -static constexpr uint8_t AppCmdReadVersion = 0x01; -static constexpr uint8_t AppCmdEnterUpdate = 0xA5; - -static constexpr uint8_t AppStatusReady = 0x00; -static constexpr uint8_t AppStatusUpdateMode = 0x01; -static constexpr size_t AppFrameSize = 36; static constexpr size_t SpiDmaBufferSize = 258; -static constexpr uint8_t AppResultOk = 0x0; -static constexpr uint8_t AppHeaderSize = 4; SPIConfig& spiConfig(); uint8_t* txBuffer(); diff --git a/firmware/controllers/system/g0_extension_firmware_protocol.cpp b/firmware/controllers/system/g0_extension_firmware_protocol.cpp index cf85d33845..13f9386789 100644 --- a/firmware/controllers/system/g0_extension_firmware_protocol.cpp +++ b/firmware/controllers/system/g0_extension_firmware_protocol.cpp @@ -10,31 +10,31 @@ uint32_t readLe32(const uint8_t* data) { } void exchangeAppFrame(SPIDriver* spi, uint8_t command, uint8_t* rx) { - memset(txBuffer(), 0, AppFrameSize); + memset(txBuffer(), 0, app::appFrameSize); txBuffer()[0] = command; - memset(rxBuffer(), 0, AppFrameSize); - memset(rx, 0, AppFrameSize); + memset(rxBuffer(), 0, app::appFrameSize); + memset(rx, 0, app::appFrameSize); spiSelect(spi); - spiExchange(spi, AppFrameSize, txBuffer(), rxBuffer()); + spiExchange(spi, app::appFrameSize, txBuffer(), rxBuffer()); spiUnselect(spi); - memcpy(rx, rxBuffer(), AppFrameSize); + memcpy(rx, rxBuffer(), app::appFrameSize); } bool isAppResponse(const uint8_t* rx, uint8_t expectedCommand, uint8_t expectedPayloadLength) { - const bool knownStatus = rx[0] == AppStatusReady || rx[0] == AppStatusUpdateMode; - return knownStatus && rx[1] == AppResultOk && rx[2] == expectedCommand && rx[3] == expectedPayloadLength; + const bool knownStatus = rx[0] == app::statusReady || rx[0] == app::statusUpdateMode; + return knownStatus && rx[1] == app::resultOk && rx[2] == expectedCommand && rx[3] == expectedPayloadLength; } bool readAppVersion(SPIDriver* spi, uint32_t& version) { - uint8_t rx[AppFrameSize]; + uint8_t rx[app::appFrameSize]; - exchangeAppFrame(spi, AppCmdReadVersion, rx); + exchangeAppFrame(spi, app::cmdReadVersion, rx); chThdSleepMilliseconds(1); - exchangeAppFrame(spi, AppCmdNop, rx); + exchangeAppFrame(spi, app::cmdNop, rx); - if (!isAppResponse(rx, AppCmdReadVersion, 4)) { + if (!isAppResponse(rx, app::cmdReadVersion, app::versionPayloadLength)) { efiPrintf( "G0 extension firmware: invalid version response %02X %02X %02X %02X", rx[0], @@ -44,16 +44,16 @@ bool readAppVersion(SPIDriver* spi, uint32_t& version) { return false; } - version = readLe32(&rx[AppHeaderSize]); + version = readLe32(&rx[app::appHeaderSize]); return true; } void requestAppUpdate(SPIDriver* spi) { - uint8_t rx[AppFrameSize]; + uint8_t rx[app::appFrameSize]; - exchangeAppFrame(spi, AppCmdEnterUpdate, rx); + exchangeAppFrame(spi, app::cmdEnterUpdate, rx); chThdSleepMilliseconds(1); - exchangeAppFrame(spi, AppCmdNop, rx); + exchangeAppFrame(spi, app::cmdNop, rx); } } // namespace g0_extension_firmware diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index 887f1aadc2..06b43ce887 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit 887f1aadc25623f320939c58b9ffc42c31ebca85 +Subproject commit 06b43ce887215b9f6f8ddcd346d91e4e9464bb2e diff --git a/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp b/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp index 3f82a4abed..c8740740e1 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp +++ b/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp @@ -15,11 +15,11 @@ static SPIConfig g0ExtensionSpiConfig = { .cfg1 = 7 | SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, .cfg2 = 0}; -static NO_CACHE uint8_t txBuf[SpiAppFrameSize]; -static NO_CACHE uint8_t rxBuf[SpiAppFrameSize]; +static NO_CACHE uint8_t txBuf[protocol::appFrameSize]; +static NO_CACHE uint8_t rxBuf[protocol::appFrameSize]; static void clearFrame(uint8_t* frame) { - for (size_t i = 0; i < SpiAppFrameSize; i++) { + for (size_t i = 0; i < protocol::appFrameSize; i++) { frame[i] = 0; } } @@ -38,7 +38,7 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { bool enable(const char* name, size_t idx) override { (void)name; - if (idx >= AnalogChannelCount) { + if (idx >= protocol::analogChannelCount) { return false; } @@ -47,7 +47,7 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { } void disable(size_t idx) override { - if (idx >= AnalogChannelCount) { + if (idx >= protocol::analogChannelCount) { return; } @@ -55,7 +55,7 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { } float get(size_t idx) const override { - if (idx >= AnalogChannelCount || !m_ready) { + if (idx >= protocol::analogChannelCount || !m_ready) { return 0.0f; } @@ -63,7 +63,7 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { } bool readDigitalInput(size_t idx) const { - if (idx >= DigitalInputCount || !m_digitalReady) { + if (idx >= protocol::digitalInputCount || !m_digitalReady) { return false; } @@ -71,7 +71,7 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { } void setLowsideOutput(size_t idx, bool value) { - m_outputs.requestOutput(idx, value, 0, value ? OutputDutyMax : 0); + m_outputs.requestOutput(idx, value, 0, value ? protocol::outputDutyMax : 0); } void disableLowsideOutput(size_t idx) { @@ -123,7 +123,7 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { prepareRequest(txBuf, nextRequest); spiSelect(m_spiDevice); - spiExchange(m_spiDevice, SpiAppFrameSize, txBuf, rxBuf); + spiExchange(m_spiDevice, protocol::appFrameSize, txBuf, rxBuf); spiUnselect(m_spiDevice); spiReleaseBus(m_spiDevice); @@ -136,10 +136,10 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { // The G0 app protocol is pipelined: the response we receive now // belongs to the previous request, while this request schedules the next reply. - if (m_nextDigitalInputToConfigure <= DigitalInputCount) { - tx[0] = SpiCmdSetInputMode; + if (m_nextDigitalInputToConfigure <= protocol::digitalInputCount) { + tx[0] = protocol::cmdSetInputMode; tx[1] = static_cast(m_nextDigitalInputToConfigure); - tx[2] = SpiDigitalMode; + tx[2] = protocol::digitalMode; m_nextDigitalInputToConfigure++; return; } @@ -149,7 +149,7 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { return; } - tx[0] = m_pollAnalogNext ? SpiCmdReadAnalog : SpiCmdReadDigitalAll; + tx[0] = m_pollAnalogNext ? protocol::cmdReadAnalog : protocol::cmdReadDigitalAll; m_pollAnalogNext = !m_pollAnalogNext; } @@ -160,10 +160,10 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { } switch (rx[2]) { - case SpiCmdReadAnalog: + case protocol::cmdReadAnalog: parseAnalogResponse(rx); break; - case SpiCmdReadDigitalAll: + case protocol::cmdReadDigitalAll: parseDigitalResponse(rx); break; default: @@ -176,27 +176,27 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { const uint8_t result = rx[1]; const uint8_t payloadLength = rx[3]; - if (status != 0x00 && status != 0x01) { + if (status != protocol::statusReady && status != protocol::statusUpdateMode) { m_ready = false; return; } - if (result != SpiResultOk || payloadLength != 26) { + if (result != protocol::resultOk || payloadLength != protocol::analogPayloadLength) { m_ready = false; return; } - const bool analogReady = rx[SpiAppHeaderSize] != 0; - const uint8_t channelCount = rx[SpiAppHeaderSize + 1]; + const bool analogReady = rx[protocol::appHeaderSize] != 0; + const uint8_t channelCount = rx[protocol::appHeaderSize + 1]; if (!analogReady) { m_ready = false; return; } - const size_t count = channelCount < AnalogChannelCount ? channelCount : AnalogChannelCount; + const size_t count = channelCount < protocol::analogChannelCount ? channelCount : protocol::analogChannelCount; for (size_t i = 0; i < count; i++) { - const uint8_t offset = static_cast(SpiAppHeaderSize + 2 + i * 2); + const uint8_t offset = static_cast(protocol::appHeaderSize + 2 + i * 2); m_millivolts[i] = getU16(rx, offset); } @@ -208,24 +208,24 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { const uint8_t result = rx[1]; const uint8_t payloadLength = rx[3]; - if (status != 0x00 && status != 0x01) { + if (status != protocol::statusReady && status != protocol::statusUpdateMode) { m_digitalReady = false; return; } - if (result != SpiResultOk || payloadLength != 25) { + if (result != protocol::resultOk || payloadLength != protocol::digitalAllPayloadLength) { m_digitalReady = false; return; } - const uint8_t inputCount = rx[SpiAppHeaderSize]; - if (inputCount != DigitalInputCount) { + const uint8_t inputCount = rx[protocol::appHeaderSize]; + if (inputCount != protocol::digitalInputCount) { m_digitalReady = false; return; } - for (size_t i = 0; i < DigitalInputCount; i++) { - const uint8_t offset = static_cast(SpiAppHeaderSize + 1 + i * 6); + for (size_t i = 0; i < protocol::digitalInputCount; i++) { + const uint8_t offset = static_cast(protocol::appHeaderSize + 1 + i * 6); const uint8_t flags = rx[offset + 1]; m_digitalLevels[i] = (flags & 0x01U) != 0; } @@ -236,11 +236,11 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { private: bool m_started = false; SPIDriver* m_spiDevice = nullptr; - bool m_enabled[AnalogChannelCount] = {}; + bool m_enabled[protocol::analogChannelCount] = {}; volatile bool m_ready = false; - volatile uint16_t m_millivolts[AnalogChannelCount] = {}; + volatile uint16_t m_millivolts[protocol::analogChannelCount] = {}; volatile bool m_digitalReady = false; - volatile bool m_digitalLevels[DigitalInputCount] = {}; + volatile bool m_digitalLevels[protocol::digitalInputCount] = {}; OutputManager m_outputs; PendingRequest m_pendingRequest = {}; size_t m_nextDigitalInputToConfigure = 1; diff --git a/firmware/hw_layer/g0_extension/g0_extension_io.cpp b/firmware/hw_layer/g0_extension/g0_extension_io.cpp index be738fd4f2..c3df77dbd1 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_io.cpp +++ b/firmware/hw_layer/g0_extension/g0_extension_io.cpp @@ -24,7 +24,7 @@ class G0ExtensionLowsideHardwarePwm final : public hardware_pwm { duty = 1; } - const auto scaledDuty = static_cast(duty * g0_extension::OutputDutyMax); + const auto scaledDuty = static_cast(duty * g0_extension::protocol::outputDutyMax); g0_extension::setLowsidePwm(m_outputIndex, m_frequencyHz, scaledDuty); } @@ -33,7 +33,7 @@ class G0ExtensionLowsideHardwarePwm final : public hardware_pwm { uint32_t m_frequencyHz = 1; }; -static G0ExtensionLowsideHardwarePwm g0ExtensionLowsidePwms[g0_extension::OutputCount] = { +static G0ExtensionLowsideHardwarePwm g0ExtensionLowsidePwms[g0_extension::protocol::outputCount] = { G0ExtensionLowsideHardwarePwm(0), G0ExtensionLowsideHardwarePwm(1), G0ExtensionLowsideHardwarePwm(2), @@ -61,7 +61,7 @@ void startG0ExtensionIo() { started = true; g0_extension::startProvider(); - registerAdcProvider(g0_extension::adcProvider(), g0_extension::FirstAdcIndex, g0_extension::AnalogChannelCount); + registerAdcProvider(g0_extension::adcProvider(), g0_extension::FirstAdcIndex, g0_extension::protocol::analogChannelCount); } bool readG0ExtensionDigitalInput(size_t idx) { diff --git a/firmware/hw_layer/g0_extension/g0_extension_io_impl.h b/firmware/hw_layer/g0_extension/g0_extension_io_impl.h index e74eac6b75..ae3922dc1a 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_io_impl.h +++ b/firmware/hw_layer/g0_extension/g0_extension_io_impl.h @@ -1,24 +1,14 @@ #pragma once #include "g0_extension_io.h" +#include "../../ext/g0_firmware/for_fome/g0_spi_protocol.h" #if EFI_PROD_CODE namespace g0_extension { -static constexpr size_t AnalogChannelCount = 12; -static constexpr size_t DigitalInputCount = 4; -static constexpr size_t OutputCount = 4; -static constexpr size_t SpiAppFrameSize = 36U; -static constexpr uint8_t SpiCmdReadAnalog = 0x10U; -static constexpr uint8_t SpiCmdReadDigitalAll = 0x12U; -static constexpr uint8_t SpiCmdSetOutput = 0x20U; -static constexpr uint8_t SpiCmdDisableOutput = 0x21U; -static constexpr uint8_t SpiCmdSetInputMode = 0x30U; -static constexpr uint8_t SpiAppHeaderSize = 4U; -static constexpr uint8_t SpiResultOk = 0x00U; -static constexpr uint8_t SpiDigitalMode = 0x00U; -static constexpr uint16_t OutputDutyMax = 10000U; +namespace protocol = ::g0_spi_protocol; + static constexpr spi_device_e SpiDevice = SPI_DEVICE_5; static constexpr brain_pin_e SpiCsPin = Gpio::F6; static constexpr size_t FirstAdcIndex = EFI_ADC_20 - EFI_ADC_0; @@ -53,7 +43,7 @@ class OutputManager { void requestOutput(size_t idx, bool enabled, uint32_t frequencyHz, uint16_t duty); private: - OutputState m_outputs[OutputCount] = {}; + OutputState m_outputs[protocol::outputCount] = {}; }; AdcProvider& adcProvider(); diff --git a/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp b/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp index 33ada89d69..b775b8d6b7 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp +++ b/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp @@ -22,7 +22,7 @@ static void putU32(uint8_t* buffer, uint8_t offset, uint32_t value) { void OutputManager::prepareRequest(uint8_t* tx, PendingRequest& nextRequest) { nextRequest = {}; - for (size_t i = 0; i < OutputCount; i++) { + for (size_t i = 0; i < protocol::outputCount; i++) { if (!m_outputs[i].dirty || m_outputs[i].pendingAck) { continue; } @@ -31,13 +31,13 @@ void OutputManager::prepareRequest(uint8_t* tx, PendingRequest& nextRequest) { nextRequest.outputIndex = i; if (output.enabled) { - tx[0] = SpiCmdSetOutput; + tx[0] = protocol::cmdSetOutput; tx[1] = static_cast(i + 1); putU32(tx, 2, output.frequencyHz); putU16(tx, 6, output.duty); nextRequest.type = RequestType::SetOutput; } else { - tx[0] = SpiCmdDisableOutput; + tx[0] = protocol::cmdDisableOutput; tx[1] = static_cast(i + 1); nextRequest.type = RequestType::DisableOutput; } @@ -51,14 +51,14 @@ void OutputManager::prepareRequest(uint8_t* tx, PendingRequest& nextRequest) { } void OutputManager::parseAck(const PendingRequest& pendingRequest, const uint8_t* rx) { - if (pendingRequest.outputIndex >= OutputCount) { + if (pendingRequest.outputIndex >= protocol::outputCount) { return; } auto& output = m_outputs[pendingRequest.outputIndex]; output.pendingAck = false; - if (rx[1] != SpiResultOk) { + if (rx[1] != protocol::resultOk) { output.dirty = true; return; } @@ -68,12 +68,12 @@ void OutputManager::parseAck(const PendingRequest& pendingRequest, const uint8_t } void OutputManager::requestOutput(size_t idx, bool enabled, uint32_t frequencyHz, uint16_t duty) { - if (idx >= OutputCount) { + if (idx >= protocol::outputCount) { return; } - if (duty > OutputDutyMax) { - duty = OutputDutyMax; + if (duty > protocol::outputDutyMax) { + duty = protocol::outputDutyMax; } auto& output = m_outputs[idx]; From e92e3bb2c0997467b50fd3d38710872df3b9df4f Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 12 May 2026 19:49:50 +0200 Subject: [PATCH 40/47] frame views --- .../system/g0_extension_firmware_impl.h | 5 +- .../system/g0_extension_firmware_protocol.cpp | 35 +++++----- firmware/ext/g0_firmware | 2 +- .../g0_extension_adc_provider.cpp | 68 ++++++++----------- .../g0_extension/g0_extension_io_impl.h | 4 +- .../g0_extension_output_manager.cpp | 34 +++------- 6 files changed, 57 insertions(+), 91 deletions(-) diff --git a/firmware/controllers/system/g0_extension_firmware_impl.h b/firmware/controllers/system/g0_extension_firmware_impl.h index ccf9cd8749..10341befbf 100644 --- a/firmware/controllers/system/g0_extension_firmware_impl.h +++ b/firmware/controllers/system/g0_extension_firmware_impl.h @@ -55,9 +55,8 @@ bool bootloaderGo(SPIDriver* spi, uint32_t address); bool flashFirmwareImage(SPIDriver* spi); bool flashFirmwareImageWithRetry(SPIDriver* spi); -uint32_t readLe32(const uint8_t* data); -void exchangeAppFrame(SPIDriver* spi, uint8_t command, uint8_t* rx); -bool isAppResponse(const uint8_t* rx, uint8_t expectedCommand, uint8_t expectedPayloadLength); +void exchangeAppFrame(SPIDriver* spi, uint8_t command, app::AppFrame& rx); +bool isAppResponse(const app::AppFrame& rx, uint8_t expectedCommand, uint8_t expectedPayloadLength); bool readAppVersion(SPIDriver* spi, uint32_t& version); void requestAppUpdate(SPIDriver* spi); diff --git a/firmware/controllers/system/g0_extension_firmware_protocol.cpp b/firmware/controllers/system/g0_extension_firmware_protocol.cpp index 13f9386789..a23804937e 100644 --- a/firmware/controllers/system/g0_extension_firmware_protocol.cpp +++ b/firmware/controllers/system/g0_extension_firmware_protocol.cpp @@ -4,31 +4,28 @@ namespace g0_extension_firmware { -uint32_t readLe32(const uint8_t* data) { - return static_cast(data[0]) | (static_cast(data[1]) << 8) | - (static_cast(data[2]) << 16) | (static_cast(data[3]) << 24); -} - -void exchangeAppFrame(SPIDriver* spi, uint8_t command, uint8_t* rx) { +void exchangeAppFrame(SPIDriver* spi, uint8_t command, app::AppFrame& rx) { memset(txBuffer(), 0, app::appFrameSize); - txBuffer()[0] = command; + reinterpret_cast(txBuffer())->commandRequest.command = command; memset(rxBuffer(), 0, app::appFrameSize); - memset(rx, 0, app::appFrameSize); + memset(rx.bytes, 0, app::appFrameSize); spiSelect(spi); spiExchange(spi, app::appFrameSize, txBuffer(), rxBuffer()); spiUnselect(spi); - memcpy(rx, rxBuffer(), app::appFrameSize); + memcpy(rx.bytes, rxBuffer(), app::appFrameSize); } -bool isAppResponse(const uint8_t* rx, uint8_t expectedCommand, uint8_t expectedPayloadLength) { - const bool knownStatus = rx[0] == app::statusReady || rx[0] == app::statusUpdateMode; - return knownStatus && rx[1] == app::resultOk && rx[2] == expectedCommand && rx[3] == expectedPayloadLength; +bool isAppResponse(const app::AppFrame& rx, uint8_t expectedCommand, uint8_t expectedPayloadLength) { + const auto& header = rx.responseHeader; + const bool knownStatus = header.status == app::statusReady || header.status == app::statusUpdateMode; + return knownStatus && header.result == app::resultOk && header.command == expectedCommand && + header.payloadLength == expectedPayloadLength; } bool readAppVersion(SPIDriver* spi, uint32_t& version) { - uint8_t rx[app::appFrameSize]; + app::AppFrame rx; exchangeAppFrame(spi, app::cmdReadVersion, rx); chThdSleepMilliseconds(1); @@ -37,19 +34,19 @@ bool readAppVersion(SPIDriver* spi, uint32_t& version) { if (!isAppResponse(rx, app::cmdReadVersion, app::versionPayloadLength)) { efiPrintf( "G0 extension firmware: invalid version response %02X %02X %02X %02X", - rx[0], - rx[1], - rx[2], - rx[3]); + rx.responseHeader.status, + rx.responseHeader.result, + rx.responseHeader.command, + rx.responseHeader.payloadLength); return false; } - version = readLe32(&rx[app::appHeaderSize]); + version = rx.versionResponse.version; return true; } void requestAppUpdate(SPIDriver* spi) { - uint8_t rx[app::appFrameSize]; + app::AppFrame rx; exchangeAppFrame(spi, app::cmdEnterUpdate, rx); chThdSleepMilliseconds(1); diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index 06b43ce887..244dcede0d 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit 06b43ce887215b9f6f8ddcd346d91e4e9464bb2e +Subproject commit 244dcede0dc8a1db365228a65d5004944abbb9c7 diff --git a/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp b/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp index c8740740e1..59118610ac 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp +++ b/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp @@ -15,20 +15,15 @@ static SPIConfig g0ExtensionSpiConfig = { .cfg1 = 7 | SPI_CFG1_MBR_2 | SPI_CFG1_MBR_1 | SPI_CFG1_MBR_0, .cfg2 = 0}; -static NO_CACHE uint8_t txBuf[protocol::appFrameSize]; -static NO_CACHE uint8_t rxBuf[protocol::appFrameSize]; +static NO_CACHE protocol::AppFrame txBuf; +static NO_CACHE protocol::AppFrame rxBuf; -static void clearFrame(uint8_t* frame) { +static void clearFrame(protocol::AppFrame& frame) { for (size_t i = 0; i < protocol::appFrameSize; i++) { - frame[i] = 0; + frame.bytes[i] = 0; } } -static uint16_t getU16(const uint8_t* buffer, uint8_t offset) { - return static_cast(buffer[offset]) | - static_cast(static_cast(buffer[offset + 1]) << 8); -} - class G0ExtensionSpiAdcProvider final : public AdcProvider { public: const char* name() const override { @@ -123,7 +118,7 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { prepareRequest(txBuf, nextRequest); spiSelect(m_spiDevice); - spiExchange(m_spiDevice, protocol::appFrameSize, txBuf, rxBuf); + spiExchange(m_spiDevice, protocol::appFrameSize, txBuf.bytes, rxBuf.bytes); spiUnselect(m_spiDevice); spiReleaseBus(m_spiDevice); @@ -131,15 +126,15 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { m_pendingRequest = nextRequest; } - void prepareRequest(uint8_t* tx, PendingRequest& nextRequest) { + void prepareRequest(protocol::AppFrame& tx, PendingRequest& nextRequest) { nextRequest = {}; // The G0 app protocol is pipelined: the response we receive now // belongs to the previous request, while this request schedules the next reply. if (m_nextDigitalInputToConfigure <= protocol::digitalInputCount) { - tx[0] = protocol::cmdSetInputMode; - tx[1] = static_cast(m_nextDigitalInputToConfigure); - tx[2] = protocol::digitalMode; + tx.setInputModeRequest.command = protocol::cmdSetInputMode; + tx.setInputModeRequest.input = static_cast(m_nextDigitalInputToConfigure); + tx.setInputModeRequest.mode = protocol::digitalMode; m_nextDigitalInputToConfigure++; return; } @@ -149,17 +144,17 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { return; } - tx[0] = m_pollAnalogNext ? protocol::cmdReadAnalog : protocol::cmdReadDigitalAll; + tx.commandRequest.command = m_pollAnalogNext ? protocol::cmdReadAnalog : protocol::cmdReadDigitalAll; m_pollAnalogNext = !m_pollAnalogNext; } - void parseResponse(const uint8_t* rx) { + void parseResponse(const protocol::AppFrame& rx) { if (m_pendingRequest.type == RequestType::SetOutput || m_pendingRequest.type == RequestType::DisableOutput) { m_outputs.parseAck(m_pendingRequest, rx); } - switch (rx[2]) { + switch (rx.responseHeader.command) { case protocol::cmdReadAnalog: parseAnalogResponse(rx); break; @@ -171,63 +166,54 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { } } - void parseAnalogResponse(const uint8_t* rx) { - const uint8_t status = rx[0]; - const uint8_t result = rx[1]; - const uint8_t payloadLength = rx[3]; + void parseAnalogResponse(const protocol::AppFrame& rx) { + const auto& response = rx.analogResponse; + const auto& header = response.header; - if (status != protocol::statusReady && status != protocol::statusUpdateMode) { + if (header.status != protocol::statusReady && header.status != protocol::statusUpdateMode) { m_ready = false; return; } - if (result != protocol::resultOk || payloadLength != protocol::analogPayloadLength) { + if (header.result != protocol::resultOk || header.payloadLength != protocol::analogPayloadLength) { m_ready = false; return; } - const bool analogReady = rx[protocol::appHeaderSize] != 0; - const uint8_t channelCount = rx[protocol::appHeaderSize + 1]; - - if (!analogReady) { + if (!response.ready) { m_ready = false; return; } - const size_t count = channelCount < protocol::analogChannelCount ? channelCount : protocol::analogChannelCount; + const size_t count = response.channelCount < protocol::analogChannelCount ? response.channelCount : protocol::analogChannelCount; for (size_t i = 0; i < count; i++) { - const uint8_t offset = static_cast(protocol::appHeaderSize + 2 + i * 2); - m_millivolts[i] = getU16(rx, offset); + m_millivolts[i] = response.millivolts[i]; } m_ready = true; } - void parseDigitalResponse(const uint8_t* rx) { - const uint8_t status = rx[0]; - const uint8_t result = rx[1]; - const uint8_t payloadLength = rx[3]; + void parseDigitalResponse(const protocol::AppFrame& rx) { + const auto& response = rx.digitalAllResponse; + const auto& header = response.header; - if (status != protocol::statusReady && status != protocol::statusUpdateMode) { + if (header.status != protocol::statusReady && header.status != protocol::statusUpdateMode) { m_digitalReady = false; return; } - if (result != protocol::resultOk || payloadLength != protocol::digitalAllPayloadLength) { + if (header.result != protocol::resultOk || header.payloadLength != protocol::digitalAllPayloadLength) { m_digitalReady = false; return; } - const uint8_t inputCount = rx[protocol::appHeaderSize]; - if (inputCount != protocol::digitalInputCount) { + if (response.inputCount != protocol::digitalInputCount) { m_digitalReady = false; return; } for (size_t i = 0; i < protocol::digitalInputCount; i++) { - const uint8_t offset = static_cast(protocol::appHeaderSize + 1 + i * 6); - const uint8_t flags = rx[offset + 1]; - m_digitalLevels[i] = (flags & 0x01U) != 0; + m_digitalLevels[i] = (response.inputs[i].flags & 0x01U) != 0; } m_digitalReady = true; diff --git a/firmware/hw_layer/g0_extension/g0_extension_io_impl.h b/firmware/hw_layer/g0_extension/g0_extension_io_impl.h index ae3922dc1a..b97e403617 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_io_impl.h +++ b/firmware/hw_layer/g0_extension/g0_extension_io_impl.h @@ -38,8 +38,8 @@ struct PendingRequest { class OutputManager { public: - void prepareRequest(uint8_t* tx, PendingRequest& nextRequest); - void parseAck(const PendingRequest& pendingRequest, const uint8_t* rx); + void prepareRequest(protocol::AppFrame& tx, PendingRequest& nextRequest); + void parseAck(const PendingRequest& pendingRequest, const protocol::AppFrame& rx); void requestOutput(size_t idx, bool enabled, uint32_t frequencyHz, uint16_t duty); private: diff --git a/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp b/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp index b775b8d6b7..dee89b9f68 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp +++ b/firmware/hw_layer/g0_extension/g0_extension_output_manager.cpp @@ -3,23 +3,7 @@ #if EFI_PROD_CODE namespace g0_extension { -namespace { - -static void putU16(uint8_t* buffer, uint8_t offset, uint16_t value) { - buffer[offset] = static_cast(value); - buffer[offset + 1] = static_cast(value >> 8); -} - -static void putU32(uint8_t* buffer, uint8_t offset, uint32_t value) { - buffer[offset] = static_cast(value); - buffer[offset + 1] = static_cast(value >> 8); - buffer[offset + 2] = static_cast(value >> 16); - buffer[offset + 3] = static_cast(value >> 24); -} - -} // namespace - -void OutputManager::prepareRequest(uint8_t* tx, PendingRequest& nextRequest) { +void OutputManager::prepareRequest(protocol::AppFrame& tx, PendingRequest& nextRequest) { nextRequest = {}; for (size_t i = 0; i < protocol::outputCount; i++) { @@ -31,14 +15,14 @@ void OutputManager::prepareRequest(uint8_t* tx, PendingRequest& nextRequest) { nextRequest.outputIndex = i; if (output.enabled) { - tx[0] = protocol::cmdSetOutput; - tx[1] = static_cast(i + 1); - putU32(tx, 2, output.frequencyHz); - putU16(tx, 6, output.duty); + tx.setOutputRequest.command = protocol::cmdSetOutput; + tx.setOutputRequest.output = static_cast(i + 1); + tx.setOutputRequest.frequencyHz = output.frequencyHz; + tx.setOutputRequest.duty = output.duty; nextRequest.type = RequestType::SetOutput; } else { - tx[0] = protocol::cmdDisableOutput; - tx[1] = static_cast(i + 1); + tx.indexedRequest.command = protocol::cmdDisableOutput; + tx.indexedRequest.index = static_cast(i + 1); nextRequest.type = RequestType::DisableOutput; } @@ -50,7 +34,7 @@ void OutputManager::prepareRequest(uint8_t* tx, PendingRequest& nextRequest) { } } -void OutputManager::parseAck(const PendingRequest& pendingRequest, const uint8_t* rx) { +void OutputManager::parseAck(const PendingRequest& pendingRequest, const protocol::AppFrame& rx) { if (pendingRequest.outputIndex >= protocol::outputCount) { return; } @@ -58,7 +42,7 @@ void OutputManager::parseAck(const PendingRequest& pendingRequest, const uint8_t auto& output = m_outputs[pendingRequest.outputIndex]; output.pendingAck = false; - if (rx[1] != protocol::resultOk) { + if (rx.responseHeader.result != protocol::resultOk) { output.dirty = true; return; } From 9b3b6cc7fb40879671cfa867f23ddc721f0eea57 Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 12 May 2026 19:53:58 +0200 Subject: [PATCH 41/47] formatting --- firmware/controllers/system/g0_extension_firmware.cpp | 6 +++--- .../hw_layer/g0_extension/g0_extension_adc_provider.cpp | 6 +++--- firmware/hw_layer/g0_extension/g0_extension_io.cpp | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/firmware/controllers/system/g0_extension_firmware.cpp b/firmware/controllers/system/g0_extension_firmware.cpp index cc7298338f..8b58d97a6d 100644 --- a/firmware/controllers/system/g0_extension_firmware.cpp +++ b/firmware/controllers/system/g0_extension_firmware.cpp @@ -71,7 +71,7 @@ void resetExtension(bool bootloaderMode) { bool loadG0ExtensionFirmware(bool forceUpdate) { #if HW_ATLAS && HAL_USE_SPI - #if G0_EXTENSION_FIRMWARE_IMAGE_AVAILABLE +#if G0_EXTENSION_FIRMWARE_IMAGE_AVAILABLE g0_extension_firmware::initControlPins(); turnOnSpi(g0_extension_firmware::SpiDevice); @@ -116,10 +116,10 @@ bool loadG0ExtensionFirmware(bool forceUpdate) { } return ok; - #else +#else efiPrintf("G0 extension firmware ERROR: run `make -C firmware/ext/g0_firmware for_fome_image` first"); return false; - #endif +#endif #else (void)forceUpdate; efiPrintf("G0 extension firmware is only available on Atlas with SPI enabled"); diff --git a/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp b/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp index 59118610ac..ff7dd4a242 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp +++ b/firmware/hw_layer/g0_extension/g0_extension_adc_provider.cpp @@ -149,8 +149,7 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { } void parseResponse(const protocol::AppFrame& rx) { - if (m_pendingRequest.type == RequestType::SetOutput || - m_pendingRequest.type == RequestType::DisableOutput) { + if (m_pendingRequest.type == RequestType::SetOutput || m_pendingRequest.type == RequestType::DisableOutput) { m_outputs.parseAck(m_pendingRequest, rx); } @@ -185,7 +184,8 @@ class G0ExtensionSpiAdcProvider final : public AdcProvider { return; } - const size_t count = response.channelCount < protocol::analogChannelCount ? response.channelCount : protocol::analogChannelCount; + const size_t count = response.channelCount < protocol::analogChannelCount ? response.channelCount + : protocol::analogChannelCount; for (size_t i = 0; i < count; i++) { m_millivolts[i] = response.millivolts[i]; } diff --git a/firmware/hw_layer/g0_extension/g0_extension_io.cpp b/firmware/hw_layer/g0_extension/g0_extension_io.cpp index c3df77dbd1..bc5edca27d 100644 --- a/firmware/hw_layer/g0_extension/g0_extension_io.cpp +++ b/firmware/hw_layer/g0_extension/g0_extension_io.cpp @@ -61,7 +61,8 @@ void startG0ExtensionIo() { started = true; g0_extension::startProvider(); - registerAdcProvider(g0_extension::adcProvider(), g0_extension::FirstAdcIndex, g0_extension::protocol::analogChannelCount); + registerAdcProvider( + g0_extension::adcProvider(), g0_extension::FirstAdcIndex, g0_extension::protocol::analogChannelCount); } bool readG0ExtensionDigitalInput(size_t idx) { From db9a23a93cde73ee7f52ce273e063b940f3bcf7f Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 12 May 2026 20:05:00 +0200 Subject: [PATCH 42/47] build guards for non atlas builds --- firmware/console/status_loop.cpp | 6 ++++++ firmware/controllers/system/efi_gpio.cpp | 8 +++++++- firmware/controllers/system/system.mk | 6 +++++- firmware/hw_layer/hw_layer.mk | 6 +++++- firmware/hw_layer/io_pins.cpp | 4 ++++ firmware/hw_layer/ports/stm32/stm32_common.cpp | 4 ++++ firmware/rusefi.cpp | 4 ++++ 7 files changed, 35 insertions(+), 3 deletions(-) diff --git a/firmware/console/status_loop.cpp b/firmware/console/status_loop.cpp index 987b7560c3..c35edd0a2e 100644 --- a/firmware/console/status_loop.cpp +++ b/firmware/console/status_loop.cpp @@ -38,7 +38,9 @@ #include "main_trigger_callback.h" #include "spark_logic.h" #include "gitversion.h" +#if HW_ATLAS #include "hw_layer/g0_extension/g0_extension_io.h" +#endif #include "can_hw.h" #include "periodic_thread_controller.h" #include "binary_logging.h" @@ -493,7 +495,11 @@ void updateTunerStudioState() { tsOutputChannels->gyroYaw = engine->sensors.accelerometer.yawRate; tsOutputChannels->turboSpeed = Sensor::getOrZero(SensorType::TurbochargerSpeed); +#if HW_ATLAS tsOutputChannels->g0Present = isG0ExtensionPresent(); +#else + tsOutputChannels->g0Present = false; +#endif extern FrequencySensor vehicleSpeedSensor; tsOutputChannels->vssEdgeCounter = vehicleSpeedSensor.eventCounter; diff --git a/firmware/controllers/system/efi_gpio.cpp b/firmware/controllers/system/efi_gpio.cpp index b1ca700734..33d4b45822 100644 --- a/firmware/controllers/system/efi_gpio.cpp +++ b/firmware/controllers/system/efi_gpio.cpp @@ -10,7 +10,7 @@ #include "engine_sniffer.h" #include "drivers/gpio/gpio_ext.h" -#if EFI_PROD_CODE +#if EFI_PROD_CODE && HW_ATLAS #include "hw_layer/g0_extension/g0_extension_io.h" #endif @@ -461,10 +461,12 @@ void OutputPin::setValue(int logicValue) { int electricalValue = getElectricalValue(logicValue, m_mode); #if EFI_PROD_CODE +#if HW_ATLAS if (isG0ExtensionLowsidePin(m_brainPin)) { setG0ExtensionLowsideOutput(m_brainPin, electricalValue != 0); return; } +#endif #if (BOARD_EXT_GPIOCHIPS > 0) if (!this->ext) { @@ -583,10 +585,12 @@ void OutputPin::initPin( setDefaultPinState(outputMode); #if EFI_PROD_CODE +#if HW_ATLAS if (isG0ExtensionLowsidePin(m_brainPin)) { brain_pin_markUsed(m_brainPin, msg); return; } +#endif iomode_t ioMode = (outputMode == OM_DEFAULT || outputMode == OM_INVERTED) ? PAL_MODE_OUTPUT_PUSHPULL : PAL_MODE_OUTPUT_OPENDRAIN; @@ -635,12 +639,14 @@ void OutputPin::deInit() { efiPrintf("unregistering %s", hwPortname(m_brainPin)); #if EFI_PROD_CODE +#if HW_ATLAS if (isG0ExtensionLowsidePin(m_brainPin)) { disableG0ExtensionLowsideOutput(m_brainPin); brain_pin_markUnused(m_brainPin); m_brainPin = Gpio::Unassigned; return; } +#endif #endif efiSetPadUnused(m_brainPin); diff --git a/firmware/controllers/system/system.mk b/firmware/controllers/system/system.mk index cd5b7c56fa..0d4aaf380c 100644 --- a/firmware/controllers/system/system.mk +++ b/firmware/controllers/system/system.mk @@ -5,7 +5,11 @@ SYSTEMSRC_CPP = \ $(PROJECT_DIR)/controllers/system/efi_gpio.cpp \ $(PROJECT_DIR)/controllers/system/dc_motor.cpp \ $(PROJECT_DIR)/controllers/system/timer/scheduler.cpp \ - $(PROJECT_DIR)/controllers/system/timer/trigger_scheduler.cpp \ + $(PROJECT_DIR)/controllers/system/timer/trigger_scheduler.cpp + +ifeq ($(SHORT_BOARD_NAME),atlas) +SYSTEMSRC_CPP += \ $(PROJECT_DIR)/controllers/system/g0_extension_firmware.cpp \ $(PROJECT_DIR)/controllers/system/g0_extension_firmware_protocol.cpp \ $(PROJECT_DIR)/controllers/system/g0_extension_firmware_bootloader.cpp +endif diff --git a/firmware/hw_layer/hw_layer.mk b/firmware/hw_layer/hw_layer.mk index 2b1aaa9941..c83846e2dc 100644 --- a/firmware/hw_layer/hw_layer.mk +++ b/firmware/hw_layer/hw_layer.mk @@ -29,10 +29,14 @@ HW_LAYER_EMS_CPP = \ $(PROJECT_DIR)/hw_layer/stepper_dual_hbridge.cpp \ $(PROJECT_DIR)/hw_layer/io_pins.cpp \ $(PROJECT_DIR)/hw_layer/rtc_helper.cpp \ - $(PROJECT_DIR)/hw_layer/debounce.cpp \ + $(PROJECT_DIR)/hw_layer/debounce.cpp + +ifeq ($(SHORT_BOARD_NAME),atlas) +HW_LAYER_EMS_CPP += \ $(PROJECT_DIR)/hw_layer/g0_extension/g0_extension_io.cpp \ $(PROJECT_DIR)/hw_layer/g0_extension/g0_extension_adc_provider.cpp \ $(PROJECT_DIR)/hw_layer/g0_extension/g0_extension_output_manager.cpp +endif ifeq ($(USE_OPENBLT),yes) HW_LAYER_EMS += \ diff --git a/firmware/hw_layer/io_pins.cpp b/firmware/hw_layer/io_pins.cpp index 6e99541d7f..bbb709e1b6 100644 --- a/firmware/hw_layer/io_pins.cpp +++ b/firmware/hw_layer/io_pins.cpp @@ -12,7 +12,9 @@ #if EFI_PROD_CODE #include "drivers/gpio/gpio_ext.h" +#if HW_ATLAS #include "hw_layer/g0_extension/g0_extension_io.h" +#endif #include "status_loop.h" #include "console_io.h" @@ -150,9 +152,11 @@ bool efiReadPin(Gpio pin) { return readCanVirtualInput(pin - Gpio::CAN_INPUT_0); } + #if HW_ATLAS if (pin >= Gpio::G0_DIGITAL_IN_0 && pin <= Gpio::G0_DIGITAL_IN_3) { return readG0ExtensionDigitalInput(pin - Gpio::G0_DIGITAL_IN_0); } + #endif /* incorrect pin */ return false; diff --git a/firmware/hw_layer/ports/stm32/stm32_common.cpp b/firmware/hw_layer/ports/stm32/stm32_common.cpp index 26d243784b..29f00a8a16 100644 --- a/firmware/hw_layer/ports/stm32/stm32_common.cpp +++ b/firmware/hw_layer/ports/stm32/stm32_common.cpp @@ -182,7 +182,9 @@ void Stm32AdcProviderBase::disable(size_t idx) { #if EFI_PROD_CODE #if HAL_USE_PWM +#if HW_ATLAS #include "hw_layer/g0_extension/g0_extension_io.h" +#endif namespace { struct stm32_pwm_config { PWMDriver* const Driver; @@ -369,9 +371,11 @@ stm32_hardware_pwm* getNextPwmDevice() { } /*static*/ hardware_pwm* hardware_pwm::tryInitPin(const char* msg, brain_pin_e pin, float frequencyHz, float duty) { +#if HW_ATLAS if (auto* g0Pwm = tryInitG0ExtensionLowsidePwm(pin, frequencyHz, duty)) { return g0Pwm; } +#endif // Hardware PWM can't do very slow PWM - the timer counter is only 16 bits, so at 2MHz counting, that's a minimum of // 31hz. diff --git a/firmware/rusefi.cpp b/firmware/rusefi.cpp index b945b7df3d..c1f68f7d22 100644 --- a/firmware/rusefi.cpp +++ b/firmware/rusefi.cpp @@ -116,8 +116,10 @@ #include "trigger_emulator_algo.h" #include "rusefi_lua.h" #include "bootloader_updater.h" +#if HW_ATLAS #include "g0_extension_firmware.h" #include "hw_layer/g0_extension/g0_extension_io.h" +#endif #include @@ -175,7 +177,9 @@ void runRusEfi() { addConsoleAction(CMD_REBOOT, scheduleReboot); addConsoleAction(CMD_REBOOT_DFU, jump_to_bootloader); +#if HW_ATLAS addConsoleAction("force_g0_update", []() { setG0ExtensionPresent(loadG0ExtensionFirmware(true)); }); +#endif #if EFI_USE_OPENBLT addConsoleAction(CMD_REBOOT_OPENBLT, jump_to_openblt); From 1ca23612156080cd6af3d3ea2e1916dc7a68ba0b Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 12 May 2026 20:07:42 +0200 Subject: [PATCH 43/47] format --- firmware/hw_layer/io_pins.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/firmware/hw_layer/io_pins.cpp b/firmware/hw_layer/io_pins.cpp index bbb709e1b6..a77993ec29 100644 --- a/firmware/hw_layer/io_pins.cpp +++ b/firmware/hw_layer/io_pins.cpp @@ -152,11 +152,11 @@ bool efiReadPin(Gpio pin) { return readCanVirtualInput(pin - Gpio::CAN_INPUT_0); } - #if HW_ATLAS +#if HW_ATLAS if (pin >= Gpio::G0_DIGITAL_IN_0 && pin <= Gpio::G0_DIGITAL_IN_3) { return readG0ExtensionDigitalInput(pin - Gpio::G0_DIGITAL_IN_0); } - #endif +#endif /* incorrect pin */ return false; From 670939d7ad9ba92ebce7de7c16d59178b43a855d Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 12 May 2026 20:15:28 +0200 Subject: [PATCH 44/47] g0 ini support only for atlas --- .../java/com/rusefi/ConfigDefinition.java | 1 + .../com/rusefi/output/TSProjectConsumer.java | 29 ++++++++- .../rusefi/test/TSProjectConsumerTest.java | 62 +++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/java_tools/configuration_definition/src/main/java/com/rusefi/ConfigDefinition.java b/java_tools/configuration_definition/src/main/java/com/rusefi/ConfigDefinition.java index fecf31a5b6..9feb676893 100644 --- a/java_tools/configuration_definition/src/main/java/com/rusefi/ConfigDefinition.java +++ b/java_tools/configuration_definition/src/main/java/com/rusefi/ConfigDefinition.java @@ -148,6 +148,7 @@ public static void doJob(String[] args, ReaderStateImpl state) throws IOExceptio break; case "-boardName": shortBoardName = args[i + 1]; + state.getVariableRegistry().register("SHORT_BOARD_NAME", shortBoardName); break; case "-makefileDep": makefileDepsDestination = args[i + 1]; diff --git a/java_tools/configuration_definition/src/main/java/com/rusefi/output/TSProjectConsumer.java b/java_tools/configuration_definition/src/main/java/com/rusefi/output/TSProjectConsumer.java index 349c37cafc..8be58fd86c 100644 --- a/java_tools/configuration_definition/src/main/java/com/rusefi/output/TSProjectConsumer.java +++ b/java_tools/configuration_definition/src/main/java/com/rusefi/output/TSProjectConsumer.java @@ -25,6 +25,8 @@ public class TSProjectConsumer implements ConfigurationConsumer { private static final String FIELD_DIRECTIVE = "field ="; private static final String G0_PRESENT_FIELD = "g0Present"; private static final String NO_G0_SUFFIX = "_nog0"; + private static final String SHORT_BOARD_NAME = "SHORT_BOARD_NAME"; + private static final String ATLAS_BOARD_NAME = "atlas"; public static final String SETTING_CONTEXT_HELP_END = "SettingContextHelpEnd"; public static final String SETTING_CONTEXT_HELP = "SettingContextHelp"; @@ -71,10 +73,35 @@ protected void writeContent(String fieldsSection, TsFileContent tsContent, Outpu content.append("; ").append(CONFIG_DEFINITION_END).append(ToolUtil.EOL); content.append(tsContent.getPostfix()); - tsHeader.write(applyG0PinVisibility(content.toString())); + String renderedContent = content.toString(); + if (shouldApplyAtlasG0PinVisibility()) { + renderedContent = applyG0PinVisibility(renderedContent); + } else { + renderedContent = stripAtlasOnlyG0Content(renderedContent); + } + + tsHeader.write(renderedContent); tsHeader.close(); } + private boolean shouldApplyAtlasG0PinVisibility() { + return ATLAS_BOARD_NAME.equals(state.getVariableRegistry().get(SHORT_BOARD_NAME)); + } + + private static String stripAtlasOnlyG0Content(String content) { + StringBuilder result = new StringBuilder(); + + for (String line : content.split("\\r?\\n", -1)) { + if (line.contains(G0_PRESENT_FIELD)) { + continue; + } + + result.append(line).append(ToolUtil.EOL); + } + + return result.toString(); + } + public static String applyG0PinVisibility(String content) { String[] lines = content.split("\\r?\\n", -1); Map g0PinFields = new LinkedHashMap<>(); diff --git a/java_tools/configuration_definition/src/test/java/com/rusefi/test/TSProjectConsumerTest.java b/java_tools/configuration_definition/src/test/java/com/rusefi/test/TSProjectConsumerTest.java index 60cde94351..714772dfc0 100644 --- a/java_tools/configuration_definition/src/test/java/com/rusefi/test/TSProjectConsumerTest.java +++ b/java_tools/configuration_definition/src/test/java/com/rusefi/test/TSProjectConsumerTest.java @@ -1,12 +1,19 @@ package com.rusefi.test; import com.rusefi.ReaderStateImpl; +import com.rusefi.TsFileContent; import com.rusefi.output.BaseCHeaderConsumer; import com.rusefi.output.JavaFieldsConsumer; import com.rusefi.output.TSProjectConsumer; +import com.rusefi.util.Output; import org.junit.Test; +import java.io.IOException; +import java.io.StringWriter; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; public class TSProjectConsumerTest { @Test @@ -107,4 +114,59 @@ public void g0PinVisibilityPostProcessing() { "\n", result); } + + @Test + public void g0PinVisibilityAppliedOnlyForAtlas() throws IOException { + String fieldsSection = "" + + "fanPin = bits, U16, 522, [0:7], 0=\"NONE\", 231=\"G0 Lowside 1\", 103=\"Lowside 1\"\n" + + "\tfield = \"Output\", fanPin\n"; + + ReaderStateImpl atlasState = new ReaderStateImpl(); + atlasState.getVariableRegistry().register("SHORT_BOARD_NAME", "atlas"); + + ReaderStateImpl nonAtlasState = new ReaderStateImpl(); + nonAtlasState.getVariableRegistry().register("SHORT_BOARD_NAME", "f407-discovery"); + + String atlasOutput = renderTsContent(atlasState, fieldsSection); + assertTrue(atlasOutput.contains("fanPin_nog0 = bits, U16, 522, [0:7], 0=\"NONE\", 103=\"Lowside 1\"")); + assertTrue(atlasOutput.contains("\tfield = \"Output\", fanPin, { g0Present }")); + assertTrue(atlasOutput.contains("\tfield = \"Output\", fanPin_nog0, { !g0Present }")); + + String nonAtlasOutput = renderTsContent(nonAtlasState, fieldsSection); + assertFalse(nonAtlasOutput.contains("fanPin_nog0")); + assertFalse(nonAtlasOutput.contains("g0Present")); + assertTrue(nonAtlasOutput.contains(fieldsSection)); + } + + private static String renderTsContent(ReaderStateImpl state, String fieldsSection) throws IOException { + StringWriter sw = new StringWriter(); + + class RenderableTSProjectConsumer extends TSProjectConsumer { + private RenderableTSProjectConsumer() { + super("", state); + } + + public void render(String generatedFieldsSection) throws IOException { + writeTunerStudioFile("", generatedFieldsSection); + } + + @Override + protected void writeTunerStudioFile(String tsPath, String generatedFieldsSection) throws IOException { + writeContent(generatedFieldsSection, new TsFileContent("", ""), new Output() { + @Override + public void write(String line) { + sw.write(line); + } + + @Override + public void close() { + } + }); + } + } + + RenderableTSProjectConsumer consumer = new RenderableTSProjectConsumer(); + consumer.render(fieldsSection); + return sw.toString(); + } } From 9c3ff3ba19e2c9c6a5399156b2426ebe8b9a0adb Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 12 May 2026 22:26:20 +0200 Subject: [PATCH 45/47] enable aux spi max31855 on atlas --- firmware/config/boards/atlas/board.mk | 2 +- firmware/config/boards/atlas/prepend.txt | 2 +- firmware/hw_layer/ports/stm32/stm32h7/efifeatures.h | 5 +++-- firmware/hw_layer/sensors/max31855.cpp | 5 +++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/firmware/config/boards/atlas/board.mk b/firmware/config/boards/atlas/board.mk index 13acecfceb..382f7ca585 100644 --- a/firmware/config/boards/atlas/board.mk +++ b/firmware/config/boards/atlas/board.mk @@ -13,7 +13,7 @@ DDEFS += -DFIRMWARE_ID=\"atlas\" # DDEFS += -DTRIGGER_SCOPE DDEFS += -DEFI_SOFTWARE_KNOCK=TRUE -DSTM32_ADC_USE_ADC3=TRUE -DDEFS += -DEFI_SDC_DEVICE=SDCD1 +DDEFS += -DEFI_SDC_DEVICE=SDCD1 -DEFI_MAX_31855=TRUE # Atlas's LSE runs in bypass mode (doesn't use OSC IN pin) DDEFS += -DSTM32_LSE_BYPASS diff --git a/firmware/config/boards/atlas/prepend.txt b/firmware/config/boards/atlas/prepend.txt index a10469f971..82720084b5 100644 --- a/firmware/config/boards/atlas/prepend.txt +++ b/firmware/config/boards/atlas/prepend.txt @@ -1,5 +1,5 @@ #define ts_show_full_pinout false -#define ts_show_egt false +#define ts_show_egt true #define ts_show_analog_divider false #define ts_show_spi false #define ts_show_can_pins false diff --git a/firmware/hw_layer/ports/stm32/stm32h7/efifeatures.h b/firmware/hw_layer/ports/stm32/stm32h7/efifeatures.h index 0c6fac4231..af159dd7de 100644 --- a/firmware/hw_layer/ports/stm32/stm32h7/efifeatures.h +++ b/firmware/hw_layer/ports/stm32/stm32h7/efifeatures.h @@ -17,8 +17,9 @@ #define BOARD_MC33972_COUNT 0 #define BOARD_TLE8888_COUNT 0 -#undef EFI_MAX_31855 -#define EFI_MAX_31855 FALSE +#ifndef EFI_MAX_31855 +#define EFI_MAX_31855 TRUE +#endif #define EFI_USE_COMPRESSED_INI_MSD diff --git a/firmware/hw_layer/sensors/max31855.cpp b/firmware/hw_layer/sensors/max31855.cpp index 810c45b440..717e1739c9 100644 --- a/firmware/hw_layer/sensors/max31855.cpp +++ b/firmware/hw_layer/sensors/max31855.cpp @@ -161,7 +161,12 @@ void initMax31855(spi_device_e device, egt_cs_array_t max31855_cs) { initSpiCs(&spiConfig[i], max31855_cs[i]); +#ifdef STM32H7XX + spiConfig[i].cfg1 = 7 // 8 bits per byte + | SPI_CFG1_MBR_DIV16; +#else spiConfig[i].cr1 = getSpiPrescaler(_5MHz, device); +#endif } } } From ed3f84a96eb0786d0868283b3bca2bdbca8ff440 Mon Sep 17 00:00:00 2001 From: dynfer Date: Tue, 12 May 2026 22:34:44 +0200 Subject: [PATCH 46/47] static device on h7 --- firmware/hw_layer/sensors/max31855.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/firmware/hw_layer/sensors/max31855.cpp b/firmware/hw_layer/sensors/max31855.cpp index 717e1739c9..d8e6304192 100644 --- a/firmware/hw_layer/sensors/max31855.cpp +++ b/firmware/hw_layer/sensors/max31855.cpp @@ -144,12 +144,15 @@ static void egtRead() { } void initMax31855(spi_device_e device, egt_cs_array_t max31855_cs) { +#ifdef STM32H7XX + driver = getSpiDevice(SPI_DEVICE_5); // H7 has only SPI5 available for aux peripherals +#else driver = getSpiDevice(device); if (driver == NULL) { // error already reported return; } - +#endif // todo:spi device is now enabled separately - should probably be enabled here addConsoleAction("egtinfo", (Void)showEgtInfo); @@ -163,7 +166,7 @@ void initMax31855(spi_device_e device, egt_cs_array_t max31855_cs) { #ifdef STM32H7XX spiConfig[i].cfg1 = 7 // 8 bits per byte - | SPI_CFG1_MBR_DIV16; + | SPI_CFG1_MBR_DIV16; #else spiConfig[i].cr1 = getSpiPrescaler(_5MHz, device); #endif From 939c60ad21c166998ea81df3610edd1147f73730 Mon Sep 17 00:00:00 2001 From: dynfer Date: Thu, 14 May 2026 15:23:29 +0200 Subject: [PATCH 47/47] bump g0 firmware withh gpt fallback for different frequencies --- firmware/ext/g0_firmware | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/ext/g0_firmware b/firmware/ext/g0_firmware index 244dcede0d..0652794c54 160000 --- a/firmware/ext/g0_firmware +++ b/firmware/ext/g0_firmware @@ -1 +1 @@ -Subproject commit 244dcede0dc8a1db365228a65d5004944abbb9c7 +Subproject commit 0652794c54404527fa8eb5b6bd1f3a9336c8a311