diff --git a/.github/workflows/build-firmware.yaml b/.github/workflows/build-firmware.yaml index 432dc5cd..9c210d96 100644 --- a/.github/workflows/build-firmware.yaml +++ b/.github/workflows/build-firmware.yaml @@ -48,3 +48,12 @@ jobs: with: name: Wideband ${{matrix.build-target}} path: ./firmware/deliver/${{matrix.build-target}}/wideband* + + - name: Zip artifacts + run: zip Wideband\ ${{matrix.build-target}}.zip ./firmware/deliver/${{matrix.build-target}}/wideband* + + - name: Make a new Release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: Wideband\ ${{matrix.build-target}}.zip diff --git a/.gitmodules b/.gitmodules index cb879803..dc501e6d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,7 +10,7 @@ url = https://github.com/rusefi/kicad-libraries [submodule "firmware/ext/openblt"] path = firmware/ext/openblt - url = https://github.com/rusefi/openblt + url = https://github.com/dron0gus/openblt [submodule "firmware/libfirmware"] path = firmware/libfirmware url = https://github.com/rusefi/libfirmware.git diff --git a/README.md b/README.md index 10126927..98662061 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ +# Wideband + +This fork is for development of FW for [RusEFI](https://github.com/rusefi) [dual channel wideband](https://github.com/rusefi/rusefi-hardware/tree/main/lambda-x2). Based on [original desing](https://github.com/mck1117/wideband) from [mck1117](https://github.com/mck1117) + +For stm32f042 FW please refer to original repo. While I try to keep it buildable and unaffected by my modification I can not guaranty its functionality and provide any support. + +# Initial flashing + +For initial flashing of newly assembled device under Windows please refer to [this instruction](https://rusefi.com/forum/viewtopic.php?p=48379#p48379). + +I hope Linux users are experience enough to know how to use [stm32flash tool](https://github.com/ARMinARM/stm32flash). There are two sample scripts to [flash OpenBLT only](/firmware/dfu_flash_openblt.sh) and [flash combined image of OpenBLT + main FW](/firmware/dfu_flash.sh). + +# Update using OpenBLT + +[OpenBLT](https://github.com/feaser/openblt) bootloader is used for FW update functionality. Please reffer to original [documentation](https://www.feaser.com/openblt/doku.php?id=faq) on how to compile host tools and use it. + +There are few sample linux scripts for updating device over [CAN](/firmware/flash_can.sh) or [UART](/firmware/flash_uart.sh). + +Linux users can use almost any USB to CAN adapter supported by linux and providing SocketCAN interface ([for example](https://rusefi.com/forum/viewtopic.php?f=13&t=2209)). Windows users please check OpenBLT documentation. + +# Original readme + [![Build Firmware](https://github.com/mck1117/wideband/actions/workflows/build-firmware.yaml/badge.svg)](https://github.com/mck1117/wideband/actions/workflows/build-firmware.yaml) ![license](https://img.shields.io/github/license/mck1117/wideband) # rusEFI Wideband Controller diff --git a/firmware/.gdbinit b/firmware/.gdbinit new file mode 100644 index 00000000..9719b679 --- /dev/null +++ b/firmware/.gdbinit @@ -0,0 +1,6 @@ +target extended-remote localhost:3333 +file build/wideband.elf +load +set breakpoint auto-hw on +set remote hardware-breakpoint-limit 8 +set remote hardware-watchpoint-limit 4 diff --git a/firmware/ChibiOS b/firmware/ChibiOS index dfdb730e..88cef96d 160000 --- a/firmware/ChibiOS +++ b/firmware/ChibiOS @@ -1 +1 @@ -Subproject commit dfdb730ecf5e536174e37dd9b5ff37e6bb5a59da +Subproject commit 88cef96dd5f86fc5a73e10fc5f3d13ab6030b9b2 diff --git a/firmware/Makefile b/firmware/Makefile index a543e67c..bb32ac90 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -136,7 +136,9 @@ LDSCRIPT=app.ld # C sources that can be compiled in ARM or THUMB mode depending on the global # setting. -CSRC = $(ALLCSRC) $(BOARDDIR)/board.c +CSRC = $(ALLCSRC) \ + $(BOARDDIR)/board.c \ + boards/f1_common/openblt/shared_params.c # C++ sources that can be compiled in ARM or THUMB mode depending on the global # setting. @@ -148,6 +150,8 @@ CPPSRC = $(ALLCPPSRC) \ shared/flash.cpp \ can.cpp \ can_helper.cpp \ + can_aemnet.cpp \ + can_iobox.cpp \ fault.cpp \ lambda_conversion.cpp \ pwm.cpp \ @@ -187,7 +191,8 @@ INCDIR = $(CONFDIR) \ console/binary/ \ boards/ \ shared/ \ - $(BOARDDIR)/io/ + $(BOARDDIR)/io/ \ + boards/f1_common/openblt # Define C warning options here. CWARN = -Wall -Wextra -Wundef -Wstrict-prototypes @@ -236,9 +241,6 @@ ifeq ($(USE_OPENBLT),yes) # Reserve start of flash for OpenBLT USE_OPT += -Wl,--defsym=USE_BOOTLOADER=1 DDEFS += -DUSE_OPENBLT=TRUE - # Shared params - INCDIR += boards/f1_common/openblt - CSRC += boards/f1_common/openblt/shared_params.c endif # diff --git a/firmware/Wideband f1_dual.zip b/firmware/Wideband f1_dual.zip new file mode 100644 index 00000000..87ff8435 Binary files /dev/null and b/firmware/Wideband f1_dual.zip differ diff --git a/firmware/auxout.cpp b/firmware/auxout.cpp index 527f1459..5bf78ca3 100644 --- a/firmware/auxout.cpp +++ b/firmware/auxout.cpp @@ -78,15 +78,19 @@ static const int8_t auxOutPwmChN[AFR_CHANNELS] = { #endif }; -void SetAuxDac(int channel, float voltage) +void SetAuxDac(size_t channel, float voltage) { + if (channel >= AFR_CHANNELS) { + return; + } + voltage = voltage / AUXOUT_GAIN; auto duty = voltage / VCC_VOLTS; duty = clampF(0, duty, 1); auxDac.SetDuty(auxOutPwmCh[channel], duty); // Ripple cancelation channel - if (auxOutPwmChN[channel >= 0]) { + if (auxOutPwmChN[channel] >= 0) { auxDac.SetDuty(auxOutPwmChN[channel], duty); } } @@ -110,8 +114,12 @@ static const uint8_t auxOutDacCh[] = { #endif }; -void SetAuxDac(int channel, float voltage) +void SetAuxDac(size_t channel, float voltage) { + if (channel >= AFR_CHANNELS) { + return; + } + voltage = voltage / AUXOUT_GAIN; auxDac.SetVoltage(auxOutDacCh[channel], voltage); @@ -158,6 +166,9 @@ void AuxOutThread(void*) { for (int ch = 0; ch < AFR_CHANNELS; ch++) { + if (cfg->auxOutputSource[ch] == AuxOutputMode::MsIoBox) + continue; + float input = AuxGetInputSignal(cfg->auxOutputSource[ch]); float voltage = interpolate2d(input, cfg->auxOutBins[ch], cfg->auxOutValues[ch]); @@ -192,4 +203,9 @@ void InitAuxDac() { } + +void SetAuxDac(size_t, float) +{ +} + #endif diff --git a/firmware/auxout.h b/firmware/auxout.h index c78ea7d8..a1d4002d 100644 --- a/firmware/auxout.h +++ b/firmware/auxout.h @@ -3,4 +3,4 @@ #include void InitAuxDac(); -void SetAuxDac(int channel, float voltage); +void SetAuxDac(size_t channel, float voltage); diff --git a/firmware/boards/f0_module/port.cpp b/firmware/boards/f0_module/port.cpp index 4262350e..7e4ba06f 100644 --- a/firmware/boards/f0_module/port.cpp +++ b/firmware/boards/f0_module/port.cpp @@ -53,6 +53,7 @@ AnalogResult AnalogSample() .NernstVoltage = AverageSamples(adcBuffer, 0) * (1.0 / NERNST_INPUT_GAIN), .PumpCurrentVoltage = AverageSamples(adcBuffer, 1), .HeaterSupplyVoltage = 0, + .NernstClamped = false, }, }, .VirtualGroundVoltageInt = AverageSamples(adcBuffer, 2), @@ -118,15 +119,15 @@ Configuration* GetConfiguration() // See https://github.com/mck1117/wideband/issues/11 to explain this madness switch (3 * sel1 + sel2) { - case 0: config.CanIndexOffset = 2; break; - case 1: config.CanIndexOffset = 0; break; - case 2: config.CanIndexOffset = 3; break; - case 3: config.CanIndexOffset = 4; break; + case 0: config.afr[0].RusEfiIdOffset = 2; break; + case 1: config.afr[0].RusEfiIdOffset = 0; break; + case 2: config.afr[0].RusEfiIdOffset = 3; break; + case 3: config.afr[0].RusEfiIdOffset = 4; break; case 4: /* both floating, do nothing */ break; - case 5: config.CanIndexOffset = 1; break; - case 6: config.CanIndexOffset = 5; break; - case 7: config.CanIndexOffset = 6; break; - case 8: config.CanIndexOffset = 7; break; + case 5: config.afr[0].RusEfiIdOffset = 1; break; + case 6: config.afr[0].RusEfiIdOffset = 5; break; + case 7: config.afr[0].RusEfiIdOffset = 6; break; + case 8: config.afr[0].RusEfiIdOffset = 7; break; default: break; } @@ -146,6 +147,11 @@ void SetConfiguration() ); } +void ResetConfiguration(uint16_t option) +{ + // NOP +} + SensorType GetSensorType() { return SensorType::LSU49; diff --git a/firmware/boards/f0_module/wideband_board_config.h b/firmware/boards/f0_module/wideband_board_config.h index 529dcf9c..b71fc989 100644 --- a/firmware/boards/f0_module/wideband_board_config.h +++ b/firmware/boards/f0_module/wideband_board_config.h @@ -17,3 +17,9 @@ // Nernst voltage & ESR sense // ******************************* #define VM_RESISTOR_VALUE (10) + +// ******************************* +// Hack: allow pump driving above target temperature +// minus this offset to avoid Vnerns voltage clamp near 0V +// ******************************* +#define START_PUMP_TEMP_OFFSET (200.0) diff --git a/firmware/boards/f1_common/f1_port.cpp b/firmware/boards/f1_common/f1_port.cpp index b29f29dd..a527952d 100644 --- a/firmware/boards/f1_common/f1_port.cpp +++ b/firmware/boards/f1_common/f1_port.cpp @@ -5,12 +5,11 @@ #include "hal.h" #include "hal_mfs.h" -#if USE_OPENBLT -/* communication with OpenBLT that is plain C, not to modify external file */ +/* communication with OpenBLT that is plain C, not to modify external file + * Same code used to store "DFU-requested" flag */ extern "C" { #include "openblt/shared_params.h" }; -#endif // Storage // TODO: runtime detection? @@ -53,24 +52,110 @@ static Configuration cfg; // Configuration defaults void Configuration::LoadDefaults() { - int i; + *this = {}; - CanIndexOffset = 0; + NoLongerUsed0 = 0; sensorType = BOARD_DEFAULT_SENSOR_TYPE; /* default auxout curve is 0..5V for AFR 8.5 to 18.0 * default auxout[n] input is AFR[n] */ - for (i = 0; i < 8; i++) { + for (size_t i = 0; i < 8; i++) { auxOutBins[0][i] = auxOutBins[1][i] = 8.5 + (18.0 - 8.5) / 7 * i; auxOutValues[0][i] = auxOutValues[1][i] = 0.0 + (5.0 - 0.0) / 7 * i; } auxOutputSource[0] = AuxOutputMode::Afr0; auxOutputSource[1] = AuxOutputMode::Afr1; + for (size_t i = 0; i < AFR_CHANNELS; i++) { + // enable RusEFI protocol + afr[i].RusEfiTx = true; + afr[i].RusEfiTxDiag = true; + afr[i].RusEfiIdOffset = 2 * i; + + // Disable AemNet + afr[i].AemNetTx = false; + afr[i].AemNetIdOffset = i; + } + + for (size_t i = 0; i < EGT_CHANNELS; i++) { + // disable RusEFI protocol - not implemented + egt[i].RusEfiTx = false; + egt[i].RusEfiTxDiag = false; + egt[i].RusEfiIdOffset = i; + + // Enable AemNet + egt[i].AemNetTx = true; + egt[i].AemNetIdOffset = i; + } + + iobox.idx = 0; + iobox.enable_rx = 0; + iobox.enable_tx = 0; + iobox.IDE = CAN_IDE_STD; + iobox.SID = 0x200; + /* Finaly */ Tag = ExpectedTag; } +void Configuration::LoadDefaults(uint16_t option) +{ + LoadDefaults(); + + // Override default with specific options + switch (option) { + case 0: + //nop + break; + case 1: + // AEM protocol AFR + EGT + for (size_t i = 0; i < EGT_CHANNELS; i++) { + egt[i].AemNetTx = true; + } + for (size_t i = 0; i < AFR_CHANNELS; i++) { + afr[i].RusEfiTx = false; + afr[i].RusEfiTxDiag = false; + afr[i].AemNetTx = true; + } + break; + case 2: + // AEM AFR only + for (size_t i = 0; i < EGT_CHANNELS; i++) { + egt[i].AemNetTx = false; + } + for (size_t i = 0; i < AFR_CHANNELS; i++) { + afr[i].RusEfiTx = false; + afr[i].RusEfiTxDiag = false; + afr[i].AemNetTx = true; + } + break; + case 16: + case 17: + case 18: + // DAC over can using MS IO Box protocol (rx only) + auxOutputSource[0] = auxOutputSource[1] = AuxOutputMode::MsIoBox; + for (size_t i = 0; i < EGT_CHANNELS; i++) { + egt[i].AemNetTx = false; + } + for (size_t i = 0; i < AFR_CHANNELS; i++) { + afr[i].RusEfiTx = false; + afr[i].RusEfiTxDiag = false; + afr[i].AemNetTx = false; + } + if (option - 16 < 3) { + iobox.idx = option - 16; + } else { + // custom CAN ID + iobox.idx = 3; + } + iobox.EID = 0x200 + 0x20 * (option - 16); + iobox.enable_rx = 1; + break; + default: + break; + } +} + int InitConfiguration() { size_t size = GetConfigurationSize(); @@ -88,7 +173,7 @@ int InitConfiguration() err = mfsReadRecord(&mfs1, MFS_CONFIGURATION_RECORD_ID, &size, GetConfigurationPtr()); if ((err != MFS_NO_ERROR) || (size != GetConfigurationSize() || !cfg.IsValid())) { /* load defaults */ - cfg.LoadDefaults(); + cfg.LoadDefaults(0); } return 0; @@ -104,6 +189,11 @@ void SetConfiguration() SaveConfiguration(); } +void ResetConfiguration(uint16_t option) +{ + cfg.LoadDefaults(option); +} + /* TS stuff */ int SaveConfiguration() { /* TODO: handle error */ @@ -140,7 +230,7 @@ void rebootNow() void rebootToOpenblt() { -#if USE_OPENBLT +#ifdef USE_OPENBLT /* safe to call on already inited shares area */ SharedParamsInit(); /* Store flag to stay in OpenBLT */ @@ -150,6 +240,40 @@ void rebootToOpenblt() #endif } +void rebootToDfu() +{ + /* safe to call on already inited shares area */ + SharedParamsInit(); + /* Store flag to jump to DFU at main FW init */ + SharedParamsWriteByIndex(0, 0x02); + + rebootNow(); +} + +// stm32f10x XL-density devices +//#define BOOTLOADER_FW_ADDRESS 0x1FFFE000 +// stm32f10x devices +#define BOOTLOADER_FW_ADDRESS 0x1FFFF000 + +void checkDfuAndJump() +{ + uint8_t val; + if (SharedParamsReadByIndex(0, &val) == true) { + if (val == 0x02) { + // reset flag + SharedParamsWriteByIndex(0, 0x00); + + // AN2606 says: 2 Kbytes, starting from address 0x1FFFF000 contain the bootloader firmware. + // Point the PC to the System Memory reset vector (+4) + void (*SysMemBootJump)(void) = (void (*)(void)) (*((uint32_t *) (BOOTLOADER_FW_ADDRESS + 4))); + // Pick stack address from vector table + __set_MSP(*(__IO uint32_t*) BOOTLOADER_FW_ADDRESS); + SysMemBootJump(); + while (1); + } + } +} + void ToggleESRDriver(SensorType sensor) { switch (sensor) { diff --git a/firmware/boards/f1_dual/port.cpp b/firmware/boards/f1_dual/port.cpp index 328e7b78..5edb9946 100644 --- a/firmware/boards/f1_dual/port.cpp +++ b/firmware/boards/f1_dual/port.cpp @@ -117,12 +117,16 @@ AnalogResult AnalogSample() .NernstVoltage = AverageSamples(adcBuffer, 3) * (1.0 / NERNST_INPUT_GAIN), .PumpCurrentVoltage = AverageSamples(adcBuffer, 2), .HeaterSupplyVoltage = l_heater_voltage, + /* TODO: */ + .NernstClamped = false, }, { /* right */ .NernstVoltage = AverageSamples(adcBuffer, 1) * (1.0 / NERNST_INPUT_GAIN), .PumpCurrentVoltage = AverageSamples(adcBuffer, 0), .HeaterSupplyVoltage = r_heater_voltage, + /* TODO: */ + .NernstClamped = false, }, }, /* Dual board has separate internal virtual ground = 3.3V / 2 diff --git a/firmware/boards/f1_dual/wideband_board_config.h b/firmware/boards/f1_dual/wideband_board_config.h index 107e854c..25bdb644 100644 --- a/firmware/boards/f1_dual/wideband_board_config.h +++ b/firmware/boards/f1_dual/wideband_board_config.h @@ -1,7 +1,7 @@ #pragma once // TS settings -#define TS_SIGNATURE "rusEFI 2023.05.10.wideband_dual" +#define TS_SIGNATURE "rusEFI 2025.02.04.wideband_dual" // This board implements two channels #define AFR_CHANNELS 2 diff --git a/firmware/boards/f1_dual_rev1/board.c b/firmware/boards/f1_dual_rev1/board.c index 1b451928..df80d0d5 100644 --- a/firmware/boards/f1_dual_rev1/board.c +++ b/firmware/boards/f1_dual_rev1/board.c @@ -17,6 +17,9 @@ #include "hal.h" #include "io_pins.h" +//#include "port.h" +extern void checkDfuAndJump(); + /** * @brief PAL setup. * @details Digital I/O ports static configuration as defined in @p board.h. @@ -41,6 +44,9 @@ const PALConfig pal_default_config = * any other initialization. */ void __early_init(void) { + /* Check if requested to jump to DFU */ + checkDfuAndJump(); + stm32_clock_init(); } diff --git a/firmware/boards/f1_dual_rev1/io/io_pins.h b/firmware/boards/f1_dual_rev1/io/io_pins.h index 88d98a54..5e6112e1 100644 --- a/firmware/boards/f1_dual_rev1/io/io_pins.h +++ b/firmware/boards/f1_dual_rev1/io/io_pins.h @@ -16,6 +16,11 @@ #define LL_UART_TX_PIN LL_GPIO_PIN_9 #define LL_UART_RX_PIN LL_GPIO_PIN_10 +// Communication - secondary (BT) UART +#define SEC_UART_GPIO_PORT GPIOC +#define SEC_LL_UART_TX_PIN LL_GPIO_PIN_10 +#define SEC_LL_UART_RX_PIN LL_GPIO_PIN_11 + // Communication - CAN1 #define CAN_GPIO_PORT GPIOA #define LL_CAN_TX_PIN LL_GPIO_PIN_12 diff --git a/firmware/boards/f1_dual_rev1/openblt/blt_conf.h b/firmware/boards/f1_dual_rev1/openblt/blt_conf.h index ec478dcc..650cef64 100644 --- a/firmware/boards/f1_dual_rev1/openblt/blt_conf.h +++ b/firmware/boards/f1_dual_rev1/openblt/blt_conf.h @@ -74,8 +74,15 @@ /** \brief Configure number of bytes in the host->target data packet. */ #define BOOT_COM_RS232_RX_MAX_DATA (64) /** \brief Select the desired UART peripheral as a zero based index. */ -#define BOOT_COM_RS232_CHANNEL_INDEX (0) - +//#define BOOT_COM_RS232_CHANNEL_INDEX (0) + +#define BOOT_COM_RS232_CHANNELS_N 2 +#define BOOT_COM_RS232_CHANNEL_INDEXES {0, 2} +#define BOOT_COM_RS232_CHANNEL_DEVS {(USART1), (USART3)} +// BOOT_COM_RS232_CHANNEL_INDEXES[0] +#define BOOT_COM_RS232_CHANNEL_DEFAULT_INDEX 0 +// BOOT_COM_RS232_CHANNEL_DEVS[0] +#define BOOT_COM_RS232_CHANNEL_DEFAULT_DEV USART1 /**************************************************************************************** * B A C K D O O R E N T R Y C O N F I G U R A T I O N diff --git a/firmware/boards/f1_dual_rev1/openblt/gdb.sh b/firmware/boards/f1_dual_rev1/openblt/gdb.sh new file mode 100755 index 00000000..d6f6f2b1 --- /dev/null +++ b/firmware/boards/f1_dual_rev1/openblt/gdb.sh @@ -0,0 +1,5 @@ +#!/bin/sh +#../../toolchain/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gdb $* +#../../toolchain/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb $* + +arm-none-eabi-gdb $* diff --git a/firmware/boards/f1_dual_rev1/openblt/main.c b/firmware/boards/f1_dual_rev1/openblt/main.c index 0c8d0879..5e9c1920 100644 --- a/firmware/boards/f1_dual_rev1/openblt/main.c +++ b/firmware/boards/f1_dual_rev1/openblt/main.c @@ -128,6 +128,7 @@ void HAL_MspInit(void) /* GPIO ports clock enable. */ LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA); LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOB); + LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOC); #if (BOOT_COM_RS232_ENABLE > 0) /* UART clock enable. */ @@ -142,6 +143,23 @@ void HAL_MspInit(void) GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING; GPIO_InitStruct.Pull = LL_GPIO_PULL_UP; LL_GPIO_Init(UART_GPIO_PORT, &GPIO_InitStruct); +#if (BOOT_COM_RS232_CHANNELS_N > 1) + /* USART3 at PC10, PC11 */ + /* UART clock enable. */ + LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART3); + /* UART TX and RX GPIO pin configuration. */ + GPIO_InitStruct.Pin = SEC_LL_UART_TX_PIN; + GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; + GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH; + GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; + LL_GPIO_Init(SEC_UART_GPIO_PORT, &GPIO_InitStruct); + GPIO_InitStruct.Pin = SEC_LL_UART_RX_PIN; + GPIO_InitStruct.Mode = LL_GPIO_MODE_FLOATING; + GPIO_InitStruct.Pull = LL_GPIO_PULL_UP; + LL_GPIO_Init(SEC_UART_GPIO_PORT, &GPIO_InitStruct); + /* Enable remap: USART3 to PC10, PC11 */ + AFIO->MAPR |= AFIO_MAPR_USART3_REMAP_0; +#endif #endif #if (BOOT_COM_CAN_ENABLE > 0) @@ -181,6 +199,7 @@ void HAL_MspDeInit(void) LL_RCC_DeInit(); /* Deinit used GPIOs. */ + LL_GPIO_DeInit(GPIOC); LL_GPIO_DeInit(GPIOB); LL_GPIO_DeInit(GPIOA); @@ -191,10 +210,14 @@ void HAL_MspDeInit(void) #if (BOOT_COM_RS232_ENABLE > 0) /* UART clock disable. */ +#if (BOOT_COM_RS232_CHANNELS_N > 1) + LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_USART3); +#endif LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_USART1); #endif /* GPIO ports clock disable. */ + LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_GPIOC); LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_GPIOB); LL_APB2_GRP1_DisableClock(LL_APB2_GRP1_PERIPH_GPIOA); diff --git a/firmware/boards/f1_dual_rev1/port.cpp b/firmware/boards/f1_dual_rev1/port.cpp index bd9dc1d8..0cc8d01c 100644 --- a/firmware/boards/f1_dual_rev1/port.cpp +++ b/firmware/boards/f1_dual_rev1/port.cpp @@ -129,13 +129,18 @@ AnalogResult AnalogSample() res.VirtualGroundVoltageInt = HALF_VCC; for (int i = 0; i < AFR_CHANNELS; i++) { + res.ch[i].NernstClamped = false; float NernstRaw = AverageSamples(adcBuffer, (i == 0) ? 3 : 1); - if ((NernstRaw > 0.01) && (NernstRaw < (3.3 - 0.01))) { + if ((NernstRaw > 0.01) && (NernstRaw < (VCC_VOLTS - 0.01))) { /* not clamped */ res.ch[i].NernstVoltage = (NernstRaw - NERNST_INPUT_OFFSET) * (1.0 / NERNST_INPUT_GAIN); } else { /* Clamped, use ungained input */ - res.ch[i].NernstVoltage = AverageSamples(adcBuffer, (i == 0) ? 9 : 8) - HALF_VCC; + NernstRaw = AverageSamples(adcBuffer, (i == 0) ? 9 : 8) - HALF_VCC; + if ((NernstRaw > 0.01) && (NernstRaw < (VCC_VOLTS - 0.01))) { + res.ch[i].NernstClamped = true; + } + res.ch[i].NernstVoltage = NernstRaw; } } /* left */ diff --git a/firmware/boards/f1_dual_rev1/wideband_board_config.h b/firmware/boards/f1_dual_rev1/wideband_board_config.h index b62b1aec..1516fc1b 100644 --- a/firmware/boards/f1_dual_rev1/wideband_board_config.h +++ b/firmware/boards/f1_dual_rev1/wideband_board_config.h @@ -1,7 +1,7 @@ #pragma once // TS settings -#define TS_SIGNATURE "rusEFI 2023.05.10.wideband_dual" +#define TS_SIGNATURE "rusEFI 2025.02.04.wideband_dual" // This board implements two channels #define AFR_CHANNELS 2 diff --git a/firmware/boards/f1_dual_rev1/wideband_layout.ld b/firmware/boards/f1_dual_rev1/wideband_layout.ld index bd5d7ed7..00896070 100644 --- a/firmware/boards/f1_dual_rev1/wideband_layout.ld +++ b/firmware/boards/f1_dual_rev1/wideband_layout.ld @@ -5,8 +5,9 @@ /* OpenBLT code */ _OpenBLT_Flash_Size = DEFINED(USE_BOOTLOADER) ? 8k : 0; -/* OpenBLT <-> main FW shared area */ -_OpenBLT_Shared_Params_Size = DEFINED(USE_BOOTLOADER) ? 16 : 0; +/* OpenBLT <-> main FW shared area same area used to pass some data between restarts + * Now always enabled */ +Shared_Params_Size = 16; MEMORY { @@ -21,8 +22,8 @@ MEMORY flash5 (rx) : org = 0x00000000, len = 0 flash6 (rx) : org = 0x00000000, len = 0 flash7 (rx) : org = 0x00000000, len = 0 - shared (wx) : org = 0x20000000, len = _OpenBLT_Shared_Params_Size - ram0 (wx) : org = 0x20000000 + _OpenBLT_Shared_Params_Size, len = 48k - _OpenBLT_Shared_Params_Size + shared (wx) : org = 0x20000000, len = Shared_Params_Size + ram0 (wx) : org = 0x20000000 + Shared_Params_Size, len = 48k - Shared_Params_Size ram1 (wx) : org = 0x00000000, len = 0 ram2 (wx) : org = 0x00000000, len = 0 ram3 (wx) : org = 0x00000000, len = 0 diff --git a/firmware/boards/f1_rev2/openblt/gdb.sh b/firmware/boards/f1_rev2/openblt/gdb.sh new file mode 100755 index 00000000..d6f6f2b1 --- /dev/null +++ b/firmware/boards/f1_rev2/openblt/gdb.sh @@ -0,0 +1,5 @@ +#!/bin/sh +#../../toolchain/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gdb $* +#../../toolchain/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb $* + +arm-none-eabi-gdb $* diff --git a/firmware/boards/f1_rev2/port.cpp b/firmware/boards/f1_rev2/port.cpp index b183d105..bcbaaf44 100644 --- a/firmware/boards/f1_rev2/port.cpp +++ b/firmware/boards/f1_rev2/port.cpp @@ -76,6 +76,8 @@ AnalogResult AnalogSample() * Assume WBO supply voltage == heater supply voltage */ .HeaterSupplyVoltage = AverageSamples(adcBuffer, 3) / BATTERY_INPUT_DIVIDER, /* .HeaterSupplyVoltage = AverageSamples(adcBuffer, 4) / HEATER_INPUT_DIVIDER, */ + /* TODO: */ + .NernstClamped = false, }, }, /* Rev 2 board has separate internal virtual ground = 3.3V / 2 diff --git a/firmware/boards/f1_rev2/wideband_board_config.h b/firmware/boards/f1_rev2/wideband_board_config.h index a7650a6b..984beb89 100644 --- a/firmware/boards/f1_rev2/wideband_board_config.h +++ b/firmware/boards/f1_rev2/wideband_board_config.h @@ -1,7 +1,7 @@ #pragma once // TS settings -#define TS_SIGNATURE "rusEFI 2023.05.10.wideband_f1" +#define TS_SIGNATURE "rusEFI 2025.02.04.wideband_f1" // Fundamental board constants #define VCC_VOLTS (3.3f) diff --git a/firmware/boards/f1_rev3/port.cpp b/firmware/boards/f1_rev3/port.cpp index 361f7af9..5957e4f6 100644 --- a/firmware/boards/f1_rev3/port.cpp +++ b/firmware/boards/f1_rev3/port.cpp @@ -73,6 +73,8 @@ AnalogResult AnalogSample() * Assume WBO supply voltage == heater supply voltage */ .HeaterSupplyVoltage = AverageSamples(adcBuffer, 3) / BATTERY_INPUT_DIVIDER, /* .HeaterSupplyVoltage = AverageSamples(adcBuffer, 4) / HEATER_INPUT_DIVIDER, */ + /* TODO: */ + .NernstClamped = false, }, }, /* Rev 2 board has separate internal virtual ground = 3.3V / 2 diff --git a/firmware/boards/f1_rev3/wideband_board_config.h b/firmware/boards/f1_rev3/wideband_board_config.h index a7650a6b..984beb89 100644 --- a/firmware/boards/f1_rev3/wideband_board_config.h +++ b/firmware/boards/f1_rev3/wideband_board_config.h @@ -1,7 +1,7 @@ #pragma once // TS settings -#define TS_SIGNATURE "rusEFI 2023.05.10.wideband_f1" +#define TS_SIGNATURE "rusEFI 2025.02.04.wideband_f1" // Fundamental board constants #define VCC_VOLTS (3.3f) diff --git a/firmware/boards/port.h b/firmware/boards/port.h index c622a33e..7ef747b4 100644 --- a/firmware/boards/port.h +++ b/firmware/boards/port.h @@ -13,6 +13,8 @@ struct AnalogChannelResult /* for dual version - this is voltage on Heater-, switches between zero and Vbatt with heater PWM, * used for both Vbatt measurement and Heater diagnostic */ float HeaterSupplyVoltage; + /* If measured voltage is too close to ground or Vref assume value is clamped */ + bool NernstClamped; }; struct AnalogResult @@ -23,6 +25,7 @@ struct AnalogResult #ifdef BOARD_HAS_VOLTAGE_SENSE float SupplyVoltage; #endif + /* TODO: add AUX analog inputs */ }; // Enable ADCs, configure pins, etc @@ -42,6 +45,7 @@ enum class AuxOutputMode : uint8_t { Lambda1 = 3, Egt0 = 4, Egt1 = 5, + MsIoBox = 6, }; class Configuration { @@ -57,17 +61,61 @@ class Configuration { return this->Tag == ExpectedTag; } void LoadDefaults(); + void LoadDefaults(uint16_t option); // Actual configuration data union { struct { - uint8_t CanIndexOffset = 0; + uint8_t NoLongerUsed0 = 0; // AUX0 and AUX1 curves float auxOutBins[2][8]; float auxOutValues[2][8]; AuxOutputMode auxOutputSource[2]; SensorType sensorType; + + // per AFR channel settings + struct { + bool RusEfiTx:1; + bool RusEfiTxDiag:1; + bool AemNetTx:1; + + uint8_t RusEfiIdOffset; + uint8_t AemNetIdOffset; + uint8_t pad[5]; + } afr[2]; + + // per EGT channel settings + struct { + bool RusEfiTx:1; + bool RusEfiTxDiag:1; + bool AemNetTx:1; + + uint8_t RusEfiIdOffset; + uint8_t AemNetIdOffset; + uint8_t pad[5]; + } egt[2]; + + // MS IO Box protocol settings + struct { + // MS IO Box index: 0, 1 or 2, else custom SID/EID is used. + uint8_t idx; + uint8_t enable_rx:1; + uint8_t enable_tx:1; + // Identifier type: CAN_IDE_STD or CAN_IDE_EXT + uint8_t IDE:1; + uint8_t pad[3]; + union { + struct { + // CAN_IDE_STD + uint32_t SID:11; + }; + struct { + // Extended identifier + uint32_t EID:29; + }; + }; + } iobox __attribute__((packed)); } __attribute__((packed)); // pad to 256 bytes including tag @@ -75,9 +123,12 @@ class Configuration { }; }; +static_assert(sizeof(Configuration) == 256); + int InitConfiguration(); Configuration* GetConfiguration(); void SetConfiguration(); +void ResetConfiguration(uint16_t option); /* TS stuff */ uint8_t *GetConfigurationPtr(); @@ -87,6 +138,9 @@ const char *getTsSignature(); void rebootNow(); void rebootToOpenblt(); +void rebootToDfu(); + +extern "C" void checkDfuAndJump(); // LSU4.2, LSU4.9 or LSU_ADV SensorType GetSensorType(); diff --git a/firmware/build.zip b/firmware/build.zip new file mode 100644 index 00000000..111a056e Binary files /dev/null and b/firmware/build.zip differ diff --git a/firmware/can.cpp b/firmware/can.cpp index 69617834..c5caa305 100644 --- a/firmware/can.cpp +++ b/firmware/can.cpp @@ -4,6 +4,8 @@ #include "fault.h" #include "can_helper.h" +#include "can_aemnet.h" +#include "can_iobox.h" #include "heater_control.h" #include "lambda_conversion.h" #include "sampling.h" @@ -18,6 +20,7 @@ static Configuration* configuration; static THD_WORKING_AREA(waCanTxThread, 256); void CanTxThread(void*) { + int cycle; chRegSetThreadName("CAN Tx"); // Current system time. @@ -25,10 +28,19 @@ void CanTxThread(void*) while(1) { + // AFR - 100 Hz for (int ch = 0; ch < AFR_CHANNELS; ch++) { SendCanForChannel(ch); } + // EGT - 20 Hz + if ((cycle % 5) == 0) { + for (int ch = 0; ch < EGT_CHANNELS; ch++) { + SendCanEgtForChannel(ch); + } + } + + cycle++; prev = chThdSleepUntilWindowed(prev, chTimeAddX(prev, TIME_MS2I(WBO_TX_PERIOD_MS))); } } @@ -71,6 +83,11 @@ void CanRxThread(void*) continue; } + if (frame.IDE == CAN_IDE_STD) + { + CanIoBoxRx(&frame); + } + // Ignore std frames, only listen to ext if (!CAN_EXT(frame)) { @@ -106,7 +123,7 @@ void CanRxThread(void*) else if ((frame.DLC == 0 || frame.DLC == 1) && CAN_ID(frame) == WB_BL_ENTER) { // If 0xFF (force update all) or our ID, reset to bootloader, otherwise ignore - if (frame.DLC == 0 || frame.data8[0] == 0xFF || frame.data8[0] == GetConfiguration()->CanIndexOffset) + if (frame.DLC == 0 || frame.data8[0] == 0xFF || frame.data8[0] == GetConfiguration()->afr[0].RusEfiIdOffset) { SendAck(); @@ -119,8 +136,14 @@ void CanRxThread(void*) // Check if it's an "index set" message else if (frame.DLC == 1 && CAN_ID(frame) == WB_MSG_SET_INDEX) { + int offset = frame.data8[0]; configuration = GetConfiguration(); - configuration->CanIndexOffset = frame.data8[0]; + for (int i = 0; i < AFR_CHANNELS; i++) { + configuration->afr[i].RusEfiIdOffset = offset + i * 2; + } + for (int i = 0; i < EGT_CHANNELS; i++) { + configuration->egt[i].RusEfiIdOffset = offset + i; + } SetConfiguration(); SendAck(); } @@ -148,7 +171,7 @@ void InitCan() void SendRusefiFormat(uint8_t ch) { - auto baseAddress = WB_DATA_BASE_ADDR + 2 * (ch + configuration->CanIndexOffset); + auto baseAddress = WB_DATA_BASE_ADDR + configuration->afr[ch].RusEfiIdOffset; const auto& sampler = GetSampler(ch); const auto& heater = GetHeaterController(ch); @@ -166,7 +189,7 @@ void SendRusefiFormat(uint8_t ch) pumpDuty > 0.1f && pumpDuty < 0.9f && lambda > 0.6f; - { + if (configuration->afr[ch].RusEfiTx) { CanTxTyped frame(baseAddress + 0); // The same header is imported by the ECU and checked against this data in the frame @@ -179,8 +202,8 @@ void SendRusefiFormat(uint8_t ch) frame.get().Valid = (heaterClosedLoop && lambdaValid) ? 0x01 : 0x00; } - { - CanTxTyped frame(baseAddress + 1); + if (configuration->afr[ch].RusEfiTxDiag) { + CanTxTyped frame(baseAddress + 1);; frame.get().Esr = sampler.GetSensorInternalResistance(); frame.get().NernstDc = nernstDc * 1000; @@ -194,4 +217,13 @@ void SendRusefiFormat(uint8_t ch) __attribute__((weak)) void SendCanForChannel(uint8_t ch) { SendRusefiFormat(ch); + SendAemNetUEGOFormat(ch); +} + +__attribute__((weak)) void SendCanEgtForChannel(uint8_t ch) +{ +#if (EGT_CHANNELS > 0) + // TODO: implement RusEFI protocol? + SendAemNetEGTFormat(ch); +#endif } diff --git a/firmware/can.h b/firmware/can.h index 9e966083..cf7d8069 100644 --- a/firmware/can.h +++ b/firmware/can.h @@ -24,6 +24,7 @@ float GetRemoteBatteryVoltage(); // implement this for your board if you want some non-standard behavior // default implementation simply calls SendRusefiFormat void SendCanForChannel(uint8_t ch); +void SendCanEgtForChannel(uint8_t ch); // Helpers to support both bxCAN and CANFD peripherals #ifdef STM32G4XX diff --git a/firmware/can_aemnet.cpp b/firmware/can_aemnet.cpp new file mode 100644 index 00000000..63531f03 --- /dev/null +++ b/firmware/can_aemnet.cpp @@ -0,0 +1,105 @@ +#include "can.h" +#include "hal.h" + +#include "can_helper.h" +#include "can_aemnet.h" + +#include "port.h" +#include "fault.h" +#include "heater_control.h" +#include "lambda_conversion.h" +#include "sampling.h" +#include "pump_dac.h" +#include "max3185x.h" + +#include "byteswap.h" + +// AEMNet protocol + +#define AEMNET_UEGO_TX_PERIOD_MS 10 +#define AEMNET_UEGO_BASE_ID 0x00000180 + +namespace aemnet +{ +// 29 bit ID, 500kbs, rate 100 hz, endian big, DLC 8 +// ID: 0x180 .. 0x18f +struct UEGOData +{ + // 0.0001 Lambda/bit, 0 to 6.5535 Lambda + beuint16_t Lambda; + // 0.001 %/bit, -32.768% to 32.768% + beuint16_t Oxygen; + // 0.1 V/bit, 0 to 25.5 Volts + uint8_t SystemVolts; + uint8_t reserved; + // [1] - Bosch LSU4.9 detected + // [5] - Free-Air cal in use + // [7] - Lambda data valid + uint8_t Flags; + // [6] - Sensor Fault + uint8_t Faults; +} __attribute__((packed)); + +static_assert(sizeof(UEGOData) == 8); + +#define AEMNET_EGT_TX_PERIOD 50 +#define AEMNET_EGT_BASE_ID 0x000A0305 + +// 29 bit ID, 500kbs, rate 20 hz, endian big, DLC 8 +// ID: 0x000A0305 +struct EgtData +{ + // 1 degC/bit, 0 to 65535 degC + beuint16_t TemperatureC; + uint8_t pad[6]; +} __attribute__((packed)); + +static_assert(sizeof(EgtData) == 8); + +} //namespace aemnet + +static int LambdaIsValid(int ch) +{ + const auto& sampler = GetSampler(ch); + const auto& heater = GetHeaterController(ch); + + float nernstDc = sampler.GetNernstDc(); + + return ((heater.IsRunningClosedLoop()) && + (nernstDc > (NERNST_TARGET - 0.1f)) && + (nernstDc < (NERNST_TARGET + 0.1f))); +} + +void SendAemNetUEGOFormat(uint8_t ch) +{ + Configuration* configuration = GetConfiguration(); + auto id = AEMNET_UEGO_BASE_ID + configuration->afr[ch].AemNetIdOffset; + + const auto& sampler = GetSampler(ch); + + if (configuration->afr[ch].AemNetTx) { + CanTxTyped frame(id, true); + + frame.get().Lambda = GetLambda(ch) * 10000; + frame.get().Oxygen = 0; // TODO: + frame.get().SystemVolts = sampler.GetInternalHeaterVoltage() * 10; + frame.get().Flags = + ((configuration->sensorType == SensorType::LSU49) ? 0x02 : 0x00) | + ((LambdaIsValid(ch)) ? 0x80 : 0x00); + frame.get().Faults = 0; //TODO: + } +} + +#if (EGT_CHANNELS > 0) +void SendAemNetEGTFormat(uint8_t ch) +{ + Configuration* configuration = GetConfiguration(); + auto id = AEMNET_EGT_BASE_ID + configuration->egt[ch].AemNetIdOffset; + + if (configuration->egt[ch].AemNetTx) { + CanTxTyped frame(id, true); + + frame.get().TemperatureC = getEgtDrivers()[ch].temperature; + } +} +#endif /* EGT_CHANNELS > 0 */ diff --git a/firmware/can_aemnet.h b/firmware/can_aemnet.h new file mode 100644 index 00000000..6b42b1d1 --- /dev/null +++ b/firmware/can_aemnet.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +void SendAemNetUEGOFormat(uint8_t ch); +void SendAemNetEGTFormat(uint8_t ch); diff --git a/firmware/can_helper.h b/firmware/can_helper.h index b80c9177..3607a62c 100644 --- a/firmware/can_helper.h +++ b/firmware/can_helper.h @@ -46,7 +46,8 @@ class CanTxTyped final : public CanTxMessage static_assert(sizeof(TData) <= sizeof(CANTxFrame::data8)); public: - explicit CanTxTyped(uint32_t eid) : CanTxMessage(eid) { } + explicit CanTxTyped(uint32_t eid) : CanTxMessage(eid, sizeof(TData)) { } + explicit CanTxTyped(uint32_t eid, bool isExtended) : CanTxMessage(eid, sizeof(TData), isExtended) { } /** * Access members of the templated type. diff --git a/firmware/can_iobox.cpp b/firmware/can_iobox.cpp new file mode 100644 index 00000000..7ef804ab --- /dev/null +++ b/firmware/can_iobox.cpp @@ -0,0 +1,265 @@ +#include "can.h" +#include "hal.h" + +#include "can_helper.h" +#include "can_iobox.h" + +#include "port.h" +#include "auxout.h" +#include "sampling.h" +#include "byteswap.h" + +#define CAN_IOBOX_BASE0 0x200 +#define CAN_IOBOX_BASE1 0x220 +#define CAN_IOBOX_BASE2 0x240 + +/* Packets from MS3 to device */ +#define CAN_IOBOX_PING 0x00 +#define CAN_IOBOX_CONFIG 0x01 +#define CAN_IOBOX_SET_PWM(n) (0x02 + ((n) & 0x03)) +#define CAN_IOBOX_LAST_IN 0x05 + +/* Packets from device to MS3 */ +#define CAN_IOBOX_WHOAMI 0x08 +#define CAN_IOBOX_ADC14 0x09 +#define CAN_IOBOX_ADC57 0x0A + +struct pwm_settings { + beuint16_t on; + beuint16_t off; +} __attribute__ ((packed)); + +/* Base + 0x00 */ +/* "Are you there?" packet with zero payload */ + +/* Base + 0x01 */ +struct iobox_cfg { + uint8_t pwm_mask; // 0 - On/Off, 1 - PWM + uint8_t pad0; + uint8_t tachin_mask; + uint8_t pad1; + uint8_t adc_broadcast_interval; // mS + uint8_t tach_broadcast_interval; // mS + uint8_t pad2[2]; +} __attribute__((packed)); + +/* Base + 0x02, 0x03, 0x04 */ +struct iobox_pwm { + pwm_settings ch[2]; +} __attribute__ ((packed)); + +static_assert(sizeof(iobox_pwm) == 8); + +/* Base + 0x05 */ +struct iobox_pwm_last { + pwm_settings ch[1]; + uint8_t out_state; +} __attribute__ ((packed)); + +static_assert(sizeof(iobox_pwm_last) == 5); + +/* Base + 0x08 */ +struct iobox_whoami { + uint8_t version; + uint8_t pad[3]; + beuint16_t pwm_period; // PWM clock periods in 0.01 uS + beuint16_t tachin_period; // Tach-in clock periods in 0.01 uS +} __attribute__((packed)); + +static_assert(sizeof(iobox_whoami) == 8); + +/* Base + 0x09 */ +struct iobox_adc14 { + beuint16_t adc[4]; +} __attribute__((packed)); + +static_assert(sizeof(iobox_adc14) == 8); + +/* Base + 0x0A */ +struct iobox_adc57 { + uint8_t inputs; + uint8_t pad; + beuint16_t adc[3]; +} __attribute__((packed)); + +static_assert(sizeof(iobox_adc57) == 8); + +static bool configured = false; + +static uint8_t pwm_mask = 0x00; +static uint8_t tachin_mask = 0x00; +static uint8_t adc_broadcast_interval = 20; +static uint8_t tach_broadcast_interval = 20; + +static uint32_t CanIoBoxBaseId() +{ + const auto cfg = GetConfiguration(); + + // three standart IDs + if (cfg->iobox.idx <= 2) { + return (CAN_IOBOX_BASE0 + cfg->iobox.idx * 0x20); + } + + return ((cfg->iobox.IDE == CAN_IDE_STD) ? cfg->iobox.SID : cfg->iobox.EID); +} + +static uint8_t CanIoBoxIde() +{ + const auto cfg = GetConfiguration(); + + return cfg->iobox.IDE; +} + +/* + * TODO: validate + * Scale value to 0 .. 1023 + * MegaSquirt IOBox has 0..5V ADC input range + */ +static uint16_t CanIoBoxGetAdc(size_t index) +{ + /* Total 7 ADC inputs, mapping: + * 1 - AUX left + * 2 - AUX right + * 3 - AUX out left voltage + * 4 - AUX out right voltage + * 5 - WBO supply voltage + * 6 - Left sensor heater supply + * 7 - Right sensor heater supply */ + switch (index) { + case 0 ... 4: + return 0; + break; + case 5: + /* TODO: */ + return 0; + case 6: + case 7: + { + const auto& sampler = GetSampler(index - 6); + + /* TODO: clamp */ + return sampler.GetInternalHeaterVoltage() / 25.5 * 1024; + } + break; + + default: + return 0.0; + } +} + +int CanIoBoxRx(const CANRxFrame *fr) +{ + const auto cfg = GetConfiguration(); + + if (!cfg->iobox.enable_rx) + return 0; + + if (CanIoBoxIde() != fr->IDE) + return 0; + + const uint32_t base = CanIoBoxBaseId(); + uint32_t id; + if (fr->IDE == CAN_IDE_STD) { + id = fr->SID; + } else { + id = fr->EID; + } + + if ((id < base) || + (id > base + CAN_IOBOX_LAST_IN)) + return 0; + + if ((id == base + CAN_IOBOX_PING) && (fr->DLC == 0)) + { + CanTxTyped frame(base + CAN_IOBOX_WHOAMI, false); + + frame.get().version = 1; + // PWM clock periods in 0.01 uS, equal to clock freq / 100 * 1000 + frame.get().pwm_period = 5000; + // Tach-in clock periods in 0.01 uS, equal to clock freq / 100 * 1000 + frame.get().tachin_period = 66; + + return 0; + } + else if ((id == base + CAN_IOBOX_CONFIG) && (fr->DLC == sizeof(iobox_cfg))) + { + const iobox_cfg *cfg = reinterpret_cast(fr->data8); + + //can0 201 [8] 00 00 00 00 14 14 00 00 + pwm_mask = cfg->pwm_mask; + tachin_mask = cfg->tachin_mask; + adc_broadcast_interval = cfg->adc_broadcast_interval; + tach_broadcast_interval = cfg->tach_broadcast_interval; + + configured = true; + + return 0; + } + else if ((id >= base + CAN_IOBOX_SET_PWM(0)) && + (id <= base + CAN_IOBOX_SET_PWM(2)) && + (fr->DLC == sizeof(iobox_pwm))) + { + const iobox_pwm *pwm = reinterpret_cast(fr->data8); + + /* Two first channels are mapped to analog outputs */ + if (fr->EID == base + CAN_IOBOX_SET_PWM(0)) { + for (size_t n = 0; n < 2; n++) { + // if allowed to control DAC output over CAN + if (cfg->auxOutputSource[n] != AuxOutputMode::MsIoBox) { + continue; + } + + uint32_t period = pwm->ch[n].off + pwm->ch[n].on; + if (period == 0) { + SetAuxDac(n, 0); + } else { + SetAuxDac(n, 5.0 * pwm->ch[n].on / period); + } + } + } + + /* TODO: PWM periods */ + return 0; + } + else if ((id == base + CAN_IOBOX_SET_PWM(3)) && + (fr->DLC == sizeof(iobox_pwm_last))) + { + const iobox_pwm_last *pwm = reinterpret_cast(fr->data8); + + /* TODO: PWM periods and outputs */ + return 0; + } + + /* Should not happen */ + return -1; +} + +int CanIoBoxTx(void) +{ + const auto cfg = GetConfiguration(); + + if (!cfg->iobox.enable_tx) + return 0; + + if (!configured) + return 0; + + const uint32_t base = CanIoBoxBaseId(); + + if (1) { + CanTxTyped frame(base + CAN_IOBOX_ADC14, false); + + for (size_t i = 0; i < 4; i++) { + frame.get().adc[i] = CanIoBoxGetAdc(i); + } + } + if (1) { + CanTxTyped frame(base + CAN_IOBOX_ADC57, false); + + for (size_t i = 0; i < 3; i++) { + frame.get().adc[i] = CanIoBoxGetAdc(4 + i); + } + } + + return 0; +} diff --git a/firmware/can_iobox.h b/firmware/can_iobox.h new file mode 100644 index 00000000..1f360a47 --- /dev/null +++ b/firmware/can_iobox.h @@ -0,0 +1,3 @@ +#pragma once + +int CanIoBoxRx(const CANRxFrame *fr); diff --git a/firmware/console/binary/tunerstudio.cpp b/firmware/console/binary/tunerstudio.cpp index dcfa1b4b..074f18be 100644 --- a/firmware/console/binary/tunerstudio.cpp +++ b/firmware/console/binary/tunerstudio.cpp @@ -204,19 +204,17 @@ static void handleBurnCommand(TsChannelBase* tsChannel, ts_response_format_e mod sendResponseCode(mode, tsChannel, TS_RESPONSE_BURN_OK); } -static void handleIoTestCommand(TsChannelBase* tsChannel, ts_response_format_e mode, uint16_t subsystem, uint16_t /* index */) { +static void handleIoTestCommand(TsChannelBase* tsChannel, ts_response_format_e mode, uint16_t subsystem, uint16_t index) { /* index is not used yet */ switch (subsystem) { -#if 0 /* DFU */ case 0xba: /* Send ok to make TS happy, wait until sent */ sendOkResponse(tsChannel, TS_CRC); chThdSleepMilliseconds(100); - jump_to_bootloader(); + rebootToDfu(); break; -#endif case 0xbb: /* Send ok to make TS happy, wait until sent */ @@ -225,7 +223,6 @@ static void handleIoTestCommand(TsChannelBase* tsChannel, ts_response_format_e m rebootNow(); break; -#if USE_OPENBLT case 0xbc: /* Send ok to make TS happy, wait until sent */ sendOkResponse(tsChannel, TS_CRC); @@ -233,7 +230,14 @@ static void handleIoTestCommand(TsChannelBase* tsChannel, ts_response_format_e m /* Jump to OpenBLT if present */ rebootToOpenblt(); break; -#endif + + case 0xbd: + ResetConfiguration(index); + SetConfiguration(); + /* Send ok to make TS happy, wait until sent */ + sendOkResponse(tsChannel, TS_CRC); + chThdSleepMilliseconds(100); + rebootNow(); default: tunerStudioError(tsChannel, "Unexpected IoTest command"); diff --git a/firmware/devel-branch b/firmware/devel-branch new file mode 100644 index 00000000..8420171d --- /dev/null +++ b/firmware/devel-branch @@ -0,0 +1 @@ +b464bfa8d3f55c8fcb511a82e7c3df0cfe06b559 diff --git a/firmware/dfu_flash.sh b/firmware/dfu_flash.sh new file mode 100755 index 00000000..41e1baf2 --- /dev/null +++ b/firmware/dfu_flash.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +usage() { + cat < HEATER_BATTERY_ON_VOLTAGE) + else if (heaterSupplyVoltage > HEATER_SUPPLY_ON_VOLTAGE) { - // measured voltage is high enough to auto-start heating, wait some time to stabilize - heaterAllowed = m_batteryStableTimer.hasElapsedSec(HEATER_BATTERY_STAB_TIME); + // measured voltage is high enougth to auto-start heating, wait some time to stabilize + heaterAllowed = m_heaterStableTimer.hasElapsedSec(HEATER_BATTERY_STAB_TIME); } } @@ -116,11 +119,15 @@ HeaterState HeaterControllerBase::GetNextState(HeaterState currentState, HeaterA case HeaterState::WarmupRamp: if (sensorTemp > closedLoopTemp) { + m_closedLoopStableTimer.reset(); return HeaterState::ClosedLoop; } else if (m_warmupTimer.hasElapsedSec(m_warmupTimeSec)) { SetFault(ch, Fault::SensorDidntHeat); + // retry after timeout + m_retryTime = HEATER_DIDNOTHEAT_RETRY_TIMEOUT; + m_retryTimer.reset(); return HeaterState::Stopped; } @@ -137,19 +144,37 @@ HeaterState HeaterControllerBase::GetNextState(HeaterState currentState, HeaterA m_underheatTimer.reset(); } - if (m_overheatTimer.hasElapsedSec(0.5f)) - { - SetFault(ch, Fault::SensorOverheat); - return HeaterState::Stopped; - } - else if (m_underheatTimer.hasElapsedSec(0.5f)) - { - SetFault(ch, Fault::SensorUnderheat); - return HeaterState::Stopped; + if (m_closedLoopStableTimer.hasElapsedSec(HEATER_CLOSED_LOOP_STAB_TIME)) { + if (m_overheatTimer.hasElapsedSec(0.5f)) + { + SetFault(ch, Fault::SensorOverheat); + // retry after timeout + m_retryTime = HEATER_OVERHEAT_RETRY_TIMEOUT; + m_retryTimer.reset(); + return HeaterState::Stopped; + } + else if (m_underheatTimer.hasElapsedSec(0.5f)) + { + SetFault(ch, Fault::SensorUnderheat); + // retry after timeout + m_retryTime = HEATER_UNDERHEAT_RETRY_TIMEOUT; + m_retryTimer.reset(); + return HeaterState::Stopped; + } + } else { + // give some time for stabilization... + // looks like heavy ramped Ipump affects sensorTemp measure + // and right after switch to closed loop sensorTemp drops below underhead threshold } + // reset fault + SetFault(ch, Fault::None); break; case HeaterState::Stopped: + if ((m_retryTime) && (m_retryTimer.hasElapsedSec(m_retryTime))) { + return HeaterState::Preheat; + } + break; case HeaterState::NoHeaterSupply: /* nop */ break; @@ -199,14 +224,14 @@ void HeaterControllerBase::Update(const ISampler& sampler, HeaterAllow heaterAll float sensorEsr = sampler.GetSensorInternalResistance(); float sensorTemperature = sampler.GetSensorTemperature(); - #ifdef BOARD_HAS_VOLTAGE_SENSE + #if defined(HEATER_INPUT_DIVIDER) + // if board has ability to measure heater supply localy - use it + float heaterSupplyVoltage = sampler.GetInternalHeaterVoltage(); + #elif defined(BOARD_HAS_VOLTAGE_SENSE) float heaterSupplyVoltage = GetSupplyVoltage(); #else // not BOARD_HAS_VOLTAGE_SENSE - // If we haven't heard from the ECU, use the internally sensed - // battery voltage instead of voltage over CAN. - float heaterSupplyVoltage = heaterAllowState == HeaterAllow::Unknown - ? sampler.GetInternalHeaterVoltage() - : GetRemoteBatteryVoltage(); + // this board rely on measured voltage from ECU + float heaterSupplyVoltage = GetRemoteBatteryVoltage(); #endif // Run the state machine diff --git a/firmware/heater_control.h b/firmware/heater_control.h index 3c9f09f7..f42ea686 100644 --- a/firmware/heater_control.h +++ b/firmware/heater_control.h @@ -65,9 +65,13 @@ class HeaterControllerBase : public IHeaterController const int m_preheatTimeSec; const int m_warmupTimeSec; - Timer m_batteryStableTimer; + int m_retryTime = 0; + + Timer m_heaterStableTimer; Timer m_preheatTimer; Timer m_warmupTimer; + Timer m_closedLoopStableTimer; + Timer m_retryTimer; // Stores the time since a non-over/underheat condition // If the timer reaches a threshold, an over/underheat has diff --git a/firmware/ini/wideband_dual.ini b/firmware/ini/wideband_dual.ini index c4a0bc38..f95bc9ff 100644 --- a/firmware/ini/wideband_dual.ini +++ b/firmware/ini/wideband_dual.ini @@ -12,12 +12,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2023.05.10.wideband_dual" + signature = "rusEFI 2025.02.04.wideband_dual" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmware version for title bar. - signature = "rusEFI 2023.05.10.wideband_dual" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2025.02.04.wideband_dual" ; signature is expected to be 7 or more characters. ; TS will try to use legacy temp units in some cases, showing "deg F" on a CLT gauge that's actually deg C useLegacyFTempUnits = false @@ -65,15 +65,45 @@ Aux0OutBins = array, F32, 5, [8], "", 1, 0, 0, 1 Aux1OutBins = array, F32, 37, [8], "", 1, 0, 0, 1500, 2 Aux0Out = array, F32, 69, [8], "V", 1, 0, 0, 5.0, 2 Aux1Out = array, F32, 101, [8], "V", 1, 0, 0, 5.0, 2 -Aux0InputSel = bits, U08, 133, [0:3], "AFR 0", "AFR 1", "Lambda 0", "Lambda 1", "EGT 0", "EGT 1" -Aux1InputSel = bits, U08, 134, [0:3], "AFR 0", "AFR 1", "Lambda 0", "Lambda 1", "EGT 0", "EGT 1" +Aux0InputSel = bits, U08, 133, [0:2], "AFR 0", "AFR 1", "Lambda 0", "Lambda 1", "EGT 0", "EGT 1", "CAN input", "INVALID" +Aux1InputSel = bits, U08, 134, [0:2], "AFR 0", "AFR 1", "Lambda 0", "Lambda 1", "EGT 0", "EGT 1", "CAN input", "INVALID" LsuSensorType = bits, U08, 135, [0:2], "LSU 4.9", "LSU 4.2", "LSU ADV", "INVALID", "INVALID", "INVALID", "INVALID", "INVALID" +RusEfiTx0 = bits, U08, 136, [0:0], "Disable", "Enable" +RusEfiTxDiag0 = bits, U08, 136, [1:1], "Disable", "Enable" +AemNetTx0 = bits, U08, 136, [2:2], "Disable", "Enable" +RusEfiIdOffset0= scalar, U08, 137, "", 1, 0, 0, 255, 0 +AemNetIdOffset0= scalar, U08, 138, "", 1, 0, 0, 255, 0 + +RusEfiTx1 = bits, U08, 144, [0:0], "Disable", "Enable" +RusEfiTxDiag1 = bits, U08, 144, [1:1], "Disable", "Enable" +AemNetTx1 = bits, U08, 144, [2:2], "Disable", "Enable" +RusEfiIdOffset1= scalar, U08, 145, "", 1, 0, 0, 255, 0 +AemNetIdOffset1= scalar, U08, 146, "", 1, 0, 0, 255, 0 + +AemNetEgtTx0 = bits, U08, 152, [2:2], "Disable", "Enable" +AemNetIdEgOff0 = scalar, U08, 154, "", 1, 0, 0, 255, 0 + +AemNetEgtTx1 = bits, U08, 160, [2:2], "Disable", "Enable" +AemNetIdEgOff1 = scalar, U08, 162, "", 1, 0, 0, 255, 0 + +IoBoxIndex = bits, U08, 168, [0:1], "IoBox0 (0x200)", "IoBox1 (0x220)", "IoBox2 (0x240)", "Custom" +IoBoxRx = bits, U08, 169, [0:0], "Disable", "Enable" +IoBoxTx = bits, U08, 169, [1:1], "Disable", "Enable" +IoBoxCanIde = bits, U08, 169, [2:2], "Standart", "Extended" +IoBoxCanId = scalar, U32, 172, "", 1, 0, 0, 1073741823, 0 + page = 2 ; this is a RAM only page with no burnable flash ; name = class, type, offset, [shape], units, scale, translate, min, max, digits highSpeedOffsets = array, U16, 0, [32], "", 1, 0, 0, 65535, 0, noMsqSave [SettingContextHelp] + RusEfiIdOffset0 = "Defines CAN ID offset for RusEFI AFR format packet channel 0 (left). Data packet ID = (0x190 + this offset), Diagnostic packed ID = (0x190 + 1 + this offset)." + RusEfiIdOffset1 = "Defines CAN ID offset for RusEFI AFR format packet channel 1 (right). Data packet ID = (0x190 + this offset), Diagnostic packed ID = (0x190 + 1 + this offset)." + AemNetIdOffset0 = "Defines CAN ID offset for AemNET USEGO format packet channel 0 (left). Packet ID = (0x180 + this offset)." + AemNetIdOffset1 = "Defines CAN ID offset for AemNET USEGO format packet channel 1 (right). Packet ID = (0x180 + this offset)." + AemNetIdEgOff0 = "Defines CAN ID offset for AemNET EGT format packet channel 0 (left). Packed ID = (0xA0305 + this offset)." + AemNetIdEgOff1 = "Defines CAN ID offset for AemNET EGT format packet channel 1 (right). Packed ID = (0xA0305 + this offset)." [Tuning] @@ -97,13 +127,15 @@ VBatt = scalar, F32, 0, "V", 1, 0 AFR0_lambda = scalar, F32, 32, "", 1, 0 AFR0_afr = scalar, F32, 32, "", 14.7, 0 AFR0_temp = scalar, U16, 36, "C", 0.1, 0 +AFR0_HeaterSupply = scalar, U16, 38, "V", 0.01, 0 AFR0_NernstDc = scalar, U16, 40, "V", 0.001, 0 AFR0_NernstAc = scalar, U16, 42, "V", 0.001, 0 AFR0_PumpITarget = scalar, F32, 44, "mA", 1, 0 AFR0_PumpIMeasure = scalar, F32, 48, "mA", 1, 0 AFR0_HeaterDuty = scalar, U16, 52, "%", 0.1, 0 AFR0_HeaterEffV = scalar, U16, 54, "V", 0.01, 0 -AFR0_esr = scalar, F32, 56, "ohms", 1, 0 +AFR0_esr = scalar, U16, 56, "ohms", 1, 0 +AFR0_Nernst = scalar, S16, 58, "V", 0.001, 0 AFR0_fault = scalar, U08, 60, "", 1, 0 AFR0_heater = scalar, U08, 61, "", 1, 0 @@ -111,13 +143,15 @@ AFR0_heater = scalar, U08, 61, "", 1, 0 AFR1_lambda = scalar, F32, 64, "", 1, 0 AFR1_afr = scalar, F32, 64, "", 14.7, 0 AFR1_temp = scalar, U16, 68, "C", 0.1, 0 +AFR1_HeaterSupply = scalar, U16, 70, "V", 0.01, 0 AFR1_NernstDc = scalar, U16, 72, "V", 0.001, 0 AFR1_NernstAc = scalar, U16, 74, "V", 0.001, 0 AFR1_PumpITarget = scalar, F32, 76, "mA", 1, 0 AFR1_PumpIMeasure = scalar, F32, 80, "mA", 1, 0 AFR1_HeaterDuty = scalar, U16, 84, "%", 0.1, 0 AFR1_HeaterEffV = scalar, U16, 86, "V", 0.01, 0 -AFR1_esr = scalar, F32, 88, "ohms", 1, 0 +AFR1_esr = scalar, U16, 88, "ohms", 1, 0 +AFR1_Nernst = scalar, S16, 90, "V", 0.001, 0 AFR1_fault = scalar, U08, 92, "", 1, 0 AFR1_heater = scalar, U08, 93, "", 1, 0 @@ -179,6 +213,8 @@ gaugeCategory = AFR channel 0 AFR0_LambdaGauge = AFR0_lambda, "0: lambda", "", 0.5, 1.3, 0.5, 0.6, 1.05, 1.2, 3, 3 AFR0_AfrGauge = AFR0_afr, "0: AFR", "", 6.5, 20.0, 9.0, 10.0, 16.0, 17.0, 2, 2 AFR0_TempGauge = AFR0_temp, "0: AFR t", "C", 500, 1050, 500, 650, 800, 950, 0, 0 +AFR0_HeaterSupply = AFR0_HeaterSupply,"0: Heater Supply", "V", 3.0, 24.0, 9.0, 11.0, 15.0, 16.0, 1, 1 +AFR0_NernstGauge = AFR0_Nernst, "0: nernst V", "V", -0.2, 1.8, -0.1, 0.0, 0.9, 0.95, 3, 3 AFR0_NernstDcGauge = AFR0_NernstDc, "0: nernst DC", "V", 0.0, 1.0, 0.0, 0.0, 0.9, 0.95, 3, 3 AFR0_NernstAcGauge = AFR0_NernstAc, "0: nernst AC", "V", 0.0, 1.0, 0.0, 0.0, 0.9, 0.95, 3, 3 AFR0_HeaterDutyGauge = AFR0_HeaterDuty, "0: Heater Duty", "%", 0.0, 100.0, 1.0, 3.0, 90, 95, 1, 1 @@ -193,6 +229,8 @@ gaugeCategory = AFR channel 1 AFR1_LambdaGauge = AFR1_lambda, "1: lambda", "", 0.5, 1.3, 0.5, 0.6, 1.05, 1.2, 3, 3 AFR1_AfrGauge = AFR1_afr, "1: AFR", "", 6.5, 20.0, 9.0, 10.0, 16.0, 17.0, 2, 2 AFR1_TempGauge = AFR1_temp, "1: AFR t", "C", 500, 1050, 500, 650, 800, 950, 0, 0 +AFR1_HeaterSupply = AFR1_HeaterSupply,"1: Heater Supply", "V", 3.0, 24.0, 9.0, 11.0, 15.0, 16.0, 1, 1 +AFR1_NernstGauge = AFR1_Nernst, "1: nernst V", "V", -0.2, 1.8, -0.1, 0.0, 0.9, 0.95, 3, 3 AFR1_NernstDcGauge = AFR1_NernstDc, "1: nernst DC", "V", 0.0, 1.0, 0.0, 0.0, 0.9, 0.95, 3, 3 AFR1_NernstAcGauge = AFR1_NernstAc, "1: nernst AC", "V", 0.0, 1.0, 0.0, 0.0, 0.9, 0.95, 3, 3 AFR1_HeaterDutyGauge = AFR1_HeaterDuty, "1: Heater Duty", "%", 0.0, 100.0, 1.0, 3.0, 90, 95, 1, 1 @@ -231,9 +269,9 @@ Aux1InputGauge = { (Aux1InputSel == 0) ? AFR0_AfrGauge : ((Aux1InputSel == 1) ? gauge2 = AFR0_AfrGauge gauge3 = AFR1_AfrGauge gauge4 = AFR1_TempGauge - gauge5 = VBattGauge - gauge6 = VBattGauge - gauge7 = EGT0_Gauge + gauge5 = EGT0_Gauge + gauge6 = AFR0_HeaterSupply + gauge7 = AFR1_HeaterSupply gauge8 = EGT1_Gauge indicator = { EGT0_state }, "EGT0 ok", { EGT0: bitStringValue(EgtStatesList, EGT0_state)}, green, black, red, black @@ -256,6 +294,8 @@ entry = VBatt, "Battery", float, "%.2f" entry = AFR0_lambda, "0: Lambda", float, "%.3f" entry = AFR0_afr, "0: AFR", float, "%.2f" entry = AFR0_temp, "0: Temp C", int, "%d" +entry = AFR0_HeaterSupply, "0: Heater Supply", float, "%.2f" +entry = AFR0_Nernst, "0: Nernst V", float, "%.3f" entry = AFR0_NernstDc, "0: Nernst DC", float, "%.3f" entry = AFR0_NernstAc, "0: Nernst AC", float, "%.3f" entry = AFR0_PumpITarget, "0: Ipump target", float, "%.2f" @@ -270,6 +310,8 @@ entry = AFR0_esr, "0: ESR", float, "%.1f" entry = AFR1_lambda, "1: Lambda", float, "%.3f" entry = AFR1_afr, "1: AFR", float, "%.2f" entry = AFR1_temp, "1: Temp C", int, "%d" +entry = AFR1_HeaterSupply, "1: Heater Supply", float, "%.2f" +entry = AFR1_Nernst, "1: Nernst V", float, "%.3f" entry = AFR1_NernstDc, "1: Nernst DC", float, "%.3f" entry = AFR1_NernstAc, "1: Nernst AC", float, "%.3f" entry = AFR1_PumpITarget, "1: Ipump target", float, "%.2f" @@ -297,7 +339,9 @@ entry = EGT1_commErrors, "EGT 1: comm errors", int, "%d" menuDialog = main menu = "&Settings" subMenu = sensor_settings, "Sensor settings" - subMenu = can_settings, "CAN settings" + subMenu = can_settings, "CAN AFR settings" + subMenu = can_egt_settings, "CAN EGT settings" + subMenu = can_iobox_settings, "CAN IO Box settings" menu = "Outputs" subMenu = auxOut0, "AUX analog output 0" @@ -305,6 +349,7 @@ menuDialog = main menu = "&Controller" subMenu = ecuTools, "ECU tools" + subMenu = ecuPresets, "Presets" [ControllerCommands] ; commandName = command1, command2, commandn... @@ -322,22 +367,74 @@ cmd_reset_controller = "Z\x00\xbb\x00\x00" cmd_dfu = "Z\x00\xba\x00\x00" ; restart to OpenBlt cmd_openblt = "Z\x00\xbc\x00\x00" +; reset settings to default +cmd_defaults = "Z\x00\xbd\x00\x00" +; reset settings to presets +cmd_defaultsAemNet = "Z\x00\xbd\x00\x01" +cmd_defaultsAemNetAfrOnly = "Z\x00\xbd\x00\x02" +cmd_defaultsAemNetIoBox0 = "Z\x00\xbd\x00\x10" +cmd_defaultsAemNetIoBox1 = "Z\x00\xbd\x00\x11" +cmd_defaultsAemNetIoBox2 = "Z\x00\xbd\x00\x12" [UserDefined] dialog = sensor_settings, "Sensor Settings" field = "Sensor Type", LsuSensorType -dialog = can_settings, "CAN Settings" - field = "CAN message ID offset", CanIndexOffset +dialog = afr0_can_settings, "AFR 0 (left) channel CAN Settings" + field = "RusEFI protocol:" + field = "Output AFR", RusEfiTx0 + field = "Output AFR diagnostic", RusEfiTxDiag0 + field = "CAN ID offset (base ID is 0x190)", RusEfiIdOffset0, { (RusEfiTx0 == 1) || (RusEfiTxDiag0 == 1)}, { 1 }, displayInHex + field = "AemNet protocol:" + field = "Output AFR", AemNetTx0 + field = "CAN ID offset (base ID is 0x180)", AemNetIdOffset0, { (AemNetTx0 == 1) }, { 1 }, displayInHex + +dialog = afr1_can_settings, "AFR 1 (right) channel CAN Settings" + field = "RusEFI protocol:" + field = "Output AFR", RusEfiTx1 + field = "Output AFR diagnostic", RusEfiTxDiag1 + field = "CAN ID offset (base ID is 0x190)", RusEfiIdOffset1, { (RusEfiTx1 == 1) || (RusEfiTxDiag1 == 1)}, { 1 }, displayInHex + field = "AemNet protocol:" + field = "Output AFR", AemNetTx1 + field = "CAN ID offset (base ID is 0x180)", AemNetIdOffset1, { (AemNetTx1 == 1) }, { 1 }, displayInHex + +dialog = egt0_can_settings, "EGT 0 (left) channel CAN Settings" + field = "RusEFI protocol:" + field = "not implemented yet" + field = "AemNet protocol:" + field = "Output EGT", AemNetEgtTx0 + field = "CAN ID offset (base ID is 0xA0305)", AemNetIdEgOff0, { (AemNetEgtTx0 == 1) }, { 1 }, displayInHex + +dialog = egt1_can_settings, "EGT 1 (right) channel CAN Settings" + field = "RusEFI protocol:" + field = "not implemented yet" + field = "AemNet protocol:" + field = "Output EGT", AemNetEgtTx1 + field = "CAN ID offset (base ID is 0xA0305)", AemNetIdEgOff1, { (AemNetEgtTx1 == 1) }, { 1 }, displayInHex + +dialog = can_settings, "CAN AFR Settings", border + panel = afr0_can_settings, West + panel = afr1_can_settings, East + +dialog = can_egt_settings, "CAN AFR Settings", border + panel = egt0_can_settings, West + panel = egt1_can_settings, East + +dialog = can_iobox_settings, "CAN IO Box Settings" + field = "IoBox index", IoBoxIndex + field = "Custom CAN ID", IoBoxCanId, { IoBoxIndex >= 3 } + field = "Custom CAN ID type", IoBoxCanIde, { IoBoxIndex >= 3 }, { 1 }, displayInHex + field = "Enable CAN Rx", IoBoxRx + field = "Enable CAN Tx", IoBoxTx dialog = auxOut0, "AUX analog out 0 Settings" field = "Signal", Aux0InputSel - panel = auxOut0Curve + panel = auxOut0Curve, Center, { Aux0InputSel <= 5} dialog = auxOut1, "AUX analog out 1 Settings" field = "Signal", Aux1InputSel - panel = auxOut1Curve + panel = auxOut1Curve, Center, { Aux1InputSel <= 5} dialog = ecuReset, "Reset" commandButton = "Reset ECU", cmd_reset_controller @@ -347,4 +444,17 @@ dialog = ecuReset, "Reset" dialog = ecuTools, "ECU tools and Commands", xAxis panel = ecuReset +dialog = ecuDefaults, "Pre-defined settings" + field = "ECU will restart after new settings appied" + commandButton = "Defaults", cmd_defaults, { 1 }, showMessageOnClick, "Please restart TunerStudio to update settings" + commandButton = "AEMNet AFR and EGT", cmd_defaultsAemNet, { 1 }, showMessageOnClick, "Please restart TunerStudio to update settings" + commandButton = "AEMNet AFR only", cmd_defaultsAemNetAfrOnly, { 1 }, showMessageOnClick, "Please restart TunerStudio to update settings" + field = "Special modes" + commandButton = "IO Box #0 (0x200) (DAC only)", cmd_defaultsAemNetIoBox0, { 1 }, showMessageOnClick, "Please restart TunerStudio to update settings" + commandButton = "IO Box #1 (0x220) (DAC only)", cmd_defaultsAemNetIoBox1, { 1 }, showMessageOnClick, "Please restart TunerStudio to update settings" + commandButton = "IO Box #2 (0x240) (DAC only)", cmd_defaultsAemNetIoBox2, { 1 }, showMessageOnClick, "Please restart TunerStudio to update settings" + +dialog = ecuPresets, "Presets", xAxis + panel = ecuDefaults + [Tools] diff --git a/firmware/ini/wideband_f1.ini b/firmware/ini/wideband_f1.ini index facef154..5be3cefe 100644 --- a/firmware/ini/wideband_f1.ini +++ b/firmware/ini/wideband_f1.ini @@ -12,12 +12,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2023.05.10.wideband_f1" + signature = "rusEFI 2025.02.04.wideband_f1" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmware version for title bar. - signature = "rusEFI 2023.05.10.wideband_f1" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2025.02.04.wideband_f1" ; signature is expected to be 7 or more characters. ; TS will try to use legacy temp units in some cases, showing "deg F" on a CLT gauge that's actually deg C useLegacyFTempUnits = false @@ -90,13 +90,15 @@ VBatt = scalar, F32, 0, "V", 1, 0 AFR0_lambda = scalar, F32, 32, "", 1, 0 AFR0_afr = scalar, F32, 32, "", 14.7, 0 AFR0_temp = scalar, U16, 36, "C", 0.1, 0 +AFR0_HeaterSupply = scalar, U16, 38, "V", 0.01, 0 AFR0_NernstDc = scalar, U16, 40, "V", 0.001, 0 AFR0_NernstAc = scalar, U16, 42, "V", 0.001, 0 AFR0_PumpITarget = scalar, F32, 44, "mA", 1, 0 AFR0_PumpIMeasure = scalar, F32, 48, "mA", 1, 0 AFR0_HeaterDuty = scalar, U16, 52, "%", 0.1, 0 AFR0_HeaterEffV = scalar, U16, 54, "V", 0.01, 0 -AFR0_esr = scalar, F32, 56, "ohms", 1, 0 +AFR0_esr = scalar, U16, 56, "ohms", 1, 0 +AFR0_Nernst = scalar, S16, 58, "V", 0.001, 0 AFR0_fault = scalar, U08, 60, "", 1, 0 AFR0_heater = scalar, U08, 61, "", 1, 0 @@ -122,6 +124,8 @@ gaugeCategory = AFR channel 0 AFR0_LambdaGauge = AFR0_lambda, "0: lambda", "", 0.5, 1.3, 0.5, 0.6, 1.05, 1.2, 3, 3 AFR0_AfrGauge = AFR0_afr, "0: AFR", "", 6.5, 20.0, 9.0, 10.0, 16.0, 17.0, 2, 2 AFR0_TempGauge = AFR0_temp, "0: AFR t", "C", 500, 1050, 500, 650, 800, 950, 0, 0 +AFR0_HeaterSupply = AFR0_HeaterSupply,"0: Heater Supply", "V", 3.0, 24.0, 9.0, 11.0, 15.0, 16.0, 1, 1 +AFR0_NernstGauge = AFR0_Nernst, "0: nernst V", "V", -0.2, 1.8, -0.1, 0.0, 0.9, 0.95, 3, 3 AFR0_NernstDcGauge = AFR0_NernstDc, "0: nernst DC", "V", 0.0, 1.0, 0.0, 0.0, 0.9, 0.95, 3, 3 AFR0_NernstAcGauge = AFR0_NernstAc, "0: nernst AC", "V", 0.0, 1.0, 0.0, 0.0, 0.9, 0.95, 3, 3 AFR0_HeaterDutyGauge = AFR0_HeaterDuty, "0: Heater Duty", "%", 0.0, 100.0, 1.0, 3.0, 90, 95, 1, 1 @@ -136,7 +140,7 @@ AFR0_EsrGauge = AFR0_esr, "0: ESR", "ohms", ; 1 2 3 4 ; 5 6 7 8 - gauge1 = VBattGauge + gauge1 = AFR0_HeaterSupply gauge2 = AFR0_AfrGauge gauge3 = AFR0_TempGauge gauge4 = AFR0_HeaterDutyGauge @@ -161,6 +165,8 @@ entry = VBatt, "Battery", float, "%.2f" entry = AFR0_lambda, "0: Lambda", float, "%.3f" entry = AFR0_afr, "0: AFR", float, "%.2f" entry = AFR0_temp, "0: Temp C", int, "%d" +entry = AFR0_HeaterSupply, "0: Heater Supply", float, "%.2f" +entry = AFR0_Nernst, "0: Nernst V", float, "%.3f" entry = AFR0_NernstDc, "0: Nernst DC", float, "%.3f" entry = AFR0_NernstAc, "0: Nernst AC", float, "%.3f" entry = AFR0_PumpITarget, "0: Ipump target", float, "%.2f" diff --git a/firmware/livedata.cpp b/firmware/livedata.cpp index d69a37b7..bd50fa44 100644 --- a/firmware/livedata.cpp +++ b/firmware/livedata.cpp @@ -16,6 +16,7 @@ static livedata_afr_s livedata_afr[AFR_CHANNELS]; void SamplingUpdateLiveData() { + float vbat = 0; for (int ch = 0; ch < AFR_CHANNELS; ch++) { volatile struct livedata_afr_s *data = &livedata_afr[ch]; @@ -23,9 +24,13 @@ void SamplingUpdateLiveData() const auto& sampler = GetSampler(ch); const auto& heater = GetHeaterController(ch); + float voltage = sampler.GetInternalHeaterVoltage(); + data->lambda = GetLambda(ch); data->temperature = sampler.GetSensorTemperature() * 10; + data->heaterSupplyVoltage = voltage * 100; data->nernstDc = sampler.GetNernstDc() * 1000; + data->nernstV = (int16_t)(sampler.GetNernstV() * 1000.0); data->nernstAc = sampler.GetNernstAc() * 1000; data->pumpCurrentTarget = GetPumpCurrent(ch); data->pumpCurrentMeasured = sampler.GetPumpNominalCurrent(); @@ -34,9 +39,12 @@ void SamplingUpdateLiveData() data->esr = sampler.GetSensorInternalResistance(); data->fault = (uint8_t)GetCurrentFault(ch); data->heaterState = (uint8_t)GetHeaterState(ch); + /* TODO: add GetPumpOutputDuty() */ + if (voltage > vbat) + vbat = voltage; } - livedata_common.vbatt = GetSampler(0).GetInternalHeaterVoltage(); + livedata_common.vbatt = vbat; } template<> diff --git a/firmware/livedata.h b/firmware/livedata.h index 3a03b074..e5712dc7 100644 --- a/firmware/livedata.h +++ b/firmware/livedata.h @@ -23,14 +23,15 @@ struct livedata_afr_s { // lambda also displayed by TS as AFR, same data with different scale factor float lambda; uint16_t temperature; - uint16_t padding; + uint16_t heaterSupplyVoltage; uint16_t nernstDc; uint16_t nernstAc; float pumpCurrentTarget; float pumpCurrentMeasured; uint16_t heaterDuty; uint16_t heaterEffectiveVoltage; - float esr; + uint16_t esr; + int16_t nernstV; uint8_t fault; // See wbo::Fault uint8_t heaterState; } __attribute__((packed)); diff --git a/firmware/openocd-jlink.sh b/firmware/openocd-jlink.sh new file mode 100755 index 00000000..b569f325 --- /dev/null +++ b/firmware/openocd-jlink.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +LD_PRELOAD=/usr/local/lib/libjaylink.so openocd -f interface/jlink.cfg -c 'transport select swd' -f target/stm32f1x.cfg + +#-c '$_TARGETNAME configure -rtos chibios' diff --git a/firmware/openocd-stlink.sh b/firmware/openocd-stlink.sh new file mode 100755 index 00000000..c0ceec0f --- /dev/null +++ b/firmware/openocd-stlink.sh @@ -0,0 +1,3 @@ +#!/bin/sh +openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c '$_TARGETNAME configure -rtos auto' + diff --git a/firmware/reset_to_dfu.sh b/firmware/reset_to_dfu.sh new file mode 100755 index 00000000..49e5ecfd --- /dev/null +++ b/firmware/reset_to_dfu.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +DEV=/dev/ttyUSB0 + +#115200 +stty -F $DEV speed 115200 cs8 -cstopb -parenb > /dev/null +#Send cmd_dfu +printf '%b' '\x00\x05\x5A\x00\xBA\x00\x00\x7C\x48\x5B\xB1' > $DEV diff --git a/firmware/sampling.cpp b/firmware/sampling.cpp index 415e5ca4..43ffad8c 100644 --- a/firmware/sampling.cpp +++ b/firmware/sampling.cpp @@ -29,6 +29,11 @@ float Sampler::GetNernstAc() const return nernstAc; } +float Sampler::GetNernstV() const +{ + return nernstV; +} + float Sampler::GetPumpNominalCurrent() const { // Gain is 10x, then a 61.9 ohm resistor @@ -77,6 +82,13 @@ float Sampler::GetSensorTemperature() const float Sampler::GetSensorInternalResistance() const { + if (nernstClamped) + { + // TODO: report disconnected error? + // Return some non-realistic value + return 10000; + } + // Sensor is the lowside of a divider, top side is GetESRSupplyR(), and 3.3v AC pk-pk is injected float totalEsr = GetESRSupplyR() / (VCC_VOLTS / GetNernstAc() - 1); @@ -94,6 +106,13 @@ void Sampler::ApplySample(AnalogChannelResult& result, float virtualGroundVoltag { float r_1 = result.NernstVoltage; + // If value is close to ADC limit... + if (result.NernstClamped) { + nernstClamped = 100; + } else if (nernstClamped) { + nernstClamped--; + } + // r2_opposite_phase estimates where the previous sample would be had we not been toggling // AKA the absolute value of the difference between r2_opposite_phase and r2 is the amplitude // of the AC component on the nernst voltage. We have to pull this trick so as to use the past 3 @@ -104,6 +123,7 @@ void Sampler::ApplySample(AnalogChannelResult& result, float virtualGroundVoltag // Compute AC (difference) and DC (average) components float nernstAcLocal = f_abs(r2_opposite_phase - r_2); nernstDc = (r2_opposite_phase + r_2) / 2; + nernstV = result.NernstVoltage; nernstAc = (1 - ESR_SENSE_ALPHA) * nernstAc + diff --git a/firmware/sampling.h b/firmware/sampling.h index fb352de3..e7562cf1 100644 --- a/firmware/sampling.h +++ b/firmware/sampling.h @@ -8,6 +8,7 @@ struct ISampler { virtual float GetNernstDc() const = 0; virtual float GetNernstAc() const = 0; + virtual float GetNernstV() const = 0; virtual float GetPumpNominalCurrent() const = 0; virtual float GetInternalHeaterVoltage() const = 0; virtual float GetSensorTemperature() const = 0; @@ -24,6 +25,7 @@ class Sampler : public ISampler float GetNernstDc() const override; float GetNernstAc() const override; + float GetNernstV() const override; float GetPumpNominalCurrent() const override; float GetInternalHeaterVoltage() const override; float GetSensorTemperature() const override; @@ -35,7 +37,9 @@ class Sampler : public ISampler float nernstAc = 0; float nernstDc = 0; + float nernstV = 0; float pumpCurrentSenseVoltage = 0; + int nernstClamped = 0; #ifdef BATTERY_INPUT_DIVIDER float internalHeaterVoltage = 0; diff --git a/firmware/test_can_dac.sh b/firmware/test_can_dac.sh new file mode 100755 index 00000000..bc74738f --- /dev/null +++ b/firmware/test_can_dac.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +INTERFACE=can0 +# base 0x200, base+2 = PWM1,2 periods packet +CANID=202 +#See http://www.msextra.com/doc/pdf/Microsquirt-IObox-1.pdf for protocol specification +# Note BIG-endian values + +while true; do + # zero + cansend $INTERFACE ${CANID}#0000.ffff.0000.ffff + sleep 1 + # half + cansend $INTERFACE ${CANID}#ff7f.ff7f.ff7f.ff7f + sleep 1 + #full + cansend $INTERFACE ${CANID}#ffff.0000.ffff.0000 + sleep 1 +done diff --git a/firmware/tmp.bin b/firmware/tmp.bin new file mode 100644 index 00000000..22fc2734 Binary files /dev/null and b/firmware/tmp.bin differ diff --git a/firmware/todo b/firmware/todo new file mode 100644 index 00000000..06dbf3bd --- /dev/null +++ b/firmware/todo @@ -0,0 +1,32 @@ +1b7a76d504d204ed32540f283bd6e512de5a326c (my/master, master) Nernst clamp detection: dummy for all not dual_rev1 boards + +67f543d0424fefcf93e9789beb973fc69f61e5a1 sampler: fix tests +~~~5148f67793b10c751dcca6c68c27b7d1e3b02e76 OpneBLT: switch to my repo +~~~c49c572035ac2d53d6bbb1bdd88fe8a7c5c706bc openbtl: f1_dual_rev1: enable OpenBLT on USART2 (Bluetooth) +bc8217dc428ceb741de5bb6fd0190fe7ac24f8ec sampling: fix reported Nerst resistance in case of clamped signal +51e58076427905472dd1083f66698be09572b99a sampling: do not try to calculate ESR if NerstAc is clamped 2 +142afee486c11df8f665c3ba51b209a6955f6123 build: fix hex file generation again +~40c996fbe1f6f8b0702b59638a371d12d9fa7f03 Revert "sampling: do not try to calculate ESR if NerstAc is clamped" +937b7fb7e431ef4256f6e6ac7a74b59e5b810b9a sampling: fix nernstClamped unitialized ++6441a390912ac46219ad4abf1b39b493f7f66505 TS: send Ok response on Reset/Reboot commands to make TS happy +e555b52253a70e3396601d693351f259173a8fec Add reset to DFU script +5e1395005ffa4d8e77cf6406ee884b198494e78a Fix reset to DFU command +ac04b50e20b0048beedf99b5ed3db87e9cd5dcb9 build: fix hex file generation +-f71f3864d95a98ce5a8bfda4f626c907ce1cafce Reduce heater supply stabilization delay from 3s to 0.5s +871116fec25cb58e83b7acf38c469dd39ba63a55 heater: shortcut if sensor is hot enougth +ed2e5b7a02a427aec149461e7de44249d690ec50 heater: always rely on localy measured heater voltage +11ace408744ea3048957710804a1398d43fead44 sampling, heater: fix Battery vs Heater naming mess ++ 79decc0c9a51ce523ffdabe292d586c9bb603f8d fix f1_dual_rev1 ++ 1026b7964242e2bb90d97b5ab91fe69cf64bf5bf f1_dual: fix Heater vs Battery naming mess ++ 59371ef765a0df384e0275a78db379d6771e190f f1_dual_rev1: fix Heater vs Battery naming mess +eb40db8bd794910fda10a498f00a4598884629ca Rename GetInternalBatteryVoltage() to GetInternalHeaterVoltage() +- 58d9f441b15ffab9b1b039513178a421db532d40 lambda: getPhy for LSU ADV +4f697a9c02b1f954806559845585caf4704cb9d3 livedata: show Nernst voltage +8e70800e5751cc69d6717645a0cc1bf112737a4d livedata: show per-channel heater supply voltage +b9a276fe64ecea0e758d3c70572844f75080dbff TODO +bb4325419684ac1b729c17e0de4f66ddfb9c42f7 f1_dual: select bigger Vbat voltage from two heater voltages +7c8e9b0271d138e4afe353272b6219c4c05ef91a heater_control: report SensorNoHeatSupply if no supply +b64c3d7d137557baa24176bc903e012c6f7cbe42 heater_control: retry heating attempt after timeout +dbc218f5f45abda58eaa310b3134e7e7a3cc0f96 sampling: do not try to calculate ESR if NerstAc is clamped +- 30c20cf5fe0c88289dd0260ddba9fa1bc87e077a heater_control: do not preheat on low voltages +1e3bf3305952ab833e11a542a53033a30d633834 heater: 5 sec stabilization time after switching to closed loop diff --git a/firmware/uart.cpp b/firmware/uart.cpp index 1bbc8bec..d549b465 100644 --- a/firmware/uart.cpp +++ b/firmware/uart.cpp @@ -27,7 +27,7 @@ SerialConfig cfg = { .cr3 = 0 }; -static char printBuffer[200]; +BaseSequentialStream *chp = (BaseSequentialStream *) &SD1; static THD_WORKING_AREA(waUartThread, 512); static void UartThread(void*) @@ -60,7 +60,7 @@ static void UartThread(void*) int heaterDuty = GetHeaterDuty(ch) * 100; int pumpDuty = GetPumpOutputDuty(ch) * 100; - size_t writeCount = chsnprintf(printBuffer, sizeof(printBuffer), + chprintf(chp, "[AFR%d]: %d.%03d DC: %4d mV AC: %4d mV ESR: %5d T: %4d C Ipump: %6d uA PumpDac: %3d Vheater: %5d heater: %s (%d)\tfault: %s\r\n", ch, lambdaIntPart, lambdaThousandths, @@ -73,16 +73,14 @@ static void UartThread(void*) heaterVoltageMv, describeHeaterState(GetHeaterState(ch)), heaterDuty, describeFault(GetCurrentFault(ch))); - chnWrite(&SD1, (const uint8_t *)printBuffer, writeCount); } #if (EGT_CHANNELS > 0) for (ch = 0; ch < EGT_CHANNELS; ch++) { - size_t writeCount = chsnprintf(printBuffer, sizeof(printBuffer), + chprintf(chp, "EGT[%d]: %d C (int %d C)\r\n", (int)getEgtDrivers()[ch].temperature, (int)getEgtDrivers()[ch].coldJunctionTemperature); - chnWrite(&SD1, (const uint8_t *)printBuffer, writeCount); } #endif /* EGT_CHANNELS > 0 */ diff --git a/firmware/util/byteswap.h b/firmware/util/byteswap.h index a053d0fa..4ba6db1e 100644 --- a/firmware/util/byteswap.h +++ b/firmware/util/byteswap.h @@ -1,7 +1,17 @@ #pragma once +#include +#include + // http://en.wikipedia.org/wiki/Endianness +#if defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + #define bigEndianHost true +#endif +#if defined(__BYTE_ORDER__)&&(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + #define bigEndianHost false +#endif + static inline uint16_t SWAP_UINT16(uint16_t x) { return ((x << 8) | (x >> 8)); @@ -11,4 +21,78 @@ static inline uint32_t SWAP_UINT32(uint32_t x) { return (((x >> 24) & 0x000000ff) | ((x << 8) & 0x00ff0000) | ((x >> 8) & 0x0000ff00) | ((x << 24) & 0xff000000)); -} \ No newline at end of file +} + +// TODO: use SWAP_UINT16() and SWAP_UINT32() instead of following +template +T swap(const T& arg, bool bigInMem) +{ + if (bigEndianHost == bigInMem) { + // no byte-swapping needed + return arg; + } else { + // swap bytes + T ret; + + char* dst = reinterpret_cast(&ret); + const char* src = reinterpret_cast(&arg + 1); + + for (size_t i = 0; i < sizeof(T); i++) { + *dst++ = *--src; + } + + return ret; + } +} + +template +class BigEndian +{ + public: + struct IncompleteType; + BigEndian() { } + BigEndian(const T& t) : rep(swap(t, true)) { } + operator T() const { return swap(rep, true); } +#if 0 + // TODO: + constexpr operator typename std::conditional_t() const { + return SWAP_UINT32(rep); + } + + constexpr BigEndian(std::conditional_t val) { + rep = SWAP_UINT32(val); + } + + constexpr operator typename std::conditional_t() const { + return SWAP_UINT16(rep); + } + + constexpr BigEndian(std::conditional_t val) { + rep = SWAP_UINT32(val); + } +#endif + private: + T rep; +} __attribute__((packed)); + +template +class LittleEndian +{ + public: + T rep; +// LittleEndian() { } + LittleEndian(const T& t) : rep(swap(t, false)) { } + operator T() const { return swap(rep, false); } +}; + +// Big endian storage types +using beint16_t = BigEndian; +using beint32_t = BigEndian; +using beuint16_t = BigEndian; +using beuint32_t = BigEndian; + +//static_assert(sizeof(buint8_t) == 1); +static_assert(sizeof(beint16_t) == 2); +static_assert(sizeof(beint32_t) == 4); +static_assert(sizeof(beuint16_t) == 2); +static_assert(sizeof(beuint32_t) == 4); diff --git a/firmware/wideband_config.h b/firmware/wideband_config.h index 5416233e..e23dba39 100644 --- a/firmware/wideband_config.h +++ b/firmware/wideband_config.h @@ -42,12 +42,18 @@ #define HEATER_PREHEAT_TIME 5 #define HEATER_WARMUP_TIMEOUT 60 +#define HEATER_CLOSED_LOOP_STAB_TIME 5 #define HEATER_BATTERY_STAB_TIME 0.5f -// minimal battery voltage to start heating without CAN command -#define HEATER_BATTERY_ON_VOLTAGE 9.5 -// mininal battery voltage to continue heating -#define HEATER_BATTETY_OFF_VOLTAGE 8.5 + +#define HEATER_DIDNOTHEAT_RETRY_TIMEOUT 30 +#define HEATER_OVERHEAT_RETRY_TIMEOUT 60 +#define HEATER_UNDERHEAT_RETRY_TIMEOUT 30 + +// minimal heater voltage to start heating without CAN command +#define HEATER_SUPPLY_ON_VOLTAGE 9.5 +// mininal heater voltage to continue heating +#define HEATER_SUPPLY_OFF_VOLTAGE 8.5 // ******************************* // Start driving the pump just before we're at target temperature diff --git a/test/tests/test_heater.cpp b/test/tests/test_heater.cpp index d3de9272..54de18e4 100644 --- a/test/tests/test_heater.cpp +++ b/test/tests/test_heater.cpp @@ -126,6 +126,13 @@ TEST(HeaterStateMachine, ClosedLoop) Timer::setMockTime(0); dut.Configure(780, 300); + // Check 5 sec stabilization timeout + EXPECT_EQ(HeaterState::ClosedLoop, dut.GetNextState(HeaterState::ClosedLoop, HeaterAllow::Allowed, 12, 780)); + Timer::advanceMockTime(1e6); + EXPECT_EQ(HeaterState::ClosedLoop, dut.GetNextState(HeaterState::ClosedLoop, HeaterAllow::Allowed, 12, 1000)); + Timer::advanceMockTime(1e6); + EXPECT_EQ(HeaterState::ClosedLoop, dut.GetNextState(HeaterState::ClosedLoop, HeaterAllow::Allowed, 12, 200)); + // Temperature is reasonable, stay in closed loop EXPECT_EQ(HeaterState::ClosedLoop, dut.GetNextState(HeaterState::ClosedLoop, HeaterAllow::Allowed, 12, 780)); Timer::advanceMockTime(10e6); diff --git a/test/tests/test_sampler.cpp b/test/tests/test_sampler.cpp index 42debf66..365f3fba 100644 --- a/test/tests/test_sampler.cpp +++ b/test/tests/test_sampler.cpp @@ -21,6 +21,7 @@ TEST(Sampler, TestDc) AnalogChannelResult data; data.NernstVoltage = 0.45f; data.PumpCurrentVoltage = 1.75f; + data.NernstClamped = false; constexpr float virtualGroundVoltage = 1.65f; for (size_t i = 0; i < 5000; i++) @@ -41,10 +42,12 @@ TEST(Sampler, TestAc) AnalogChannelResult dataLow; dataLow.NernstVoltage = 0.45f - 0.1f; dataLow.PumpCurrentVoltage = 1.75f; + dataLow.NernstClamped = false; AnalogChannelResult dataHigh; dataHigh.NernstVoltage = 0.45f + 0.1f; dataHigh.PumpCurrentVoltage = 1.75f; + dataHigh.NernstClamped = false; constexpr float virtualGroundVoltage = 1.65f;