diff --git a/config/boards/arro_board.h b/config/boards/arro_board.h
new file mode 100644
index 0000000000..52bd07b48a
--- /dev/null
+++ b/config/boards/arro_board.h
@@ -0,0 +1,283 @@
+/**
+ * @file arro_board.h
+ *
+ * This file contents a configuration of default ecu board. Pinout and other.
+ * TODO: most of the pins should get configurable
+ *
+ *
+ * @date Nov 14, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ * @author frig
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+#ifndef ARRO_BOARD_H_
+#define ARRO_BOARD_H_
+
+
+#define STM32_ICU_USE_TIM1 TRUE // wave input
+#define STM32_ICU_USE_TIM2 TRUE // primary position sensor
+#define STM32_ICU_USE_TIM3 TRUE // secondary position sensor
+#define STM32_ICU_USE_TIM4 FALSE
+#define STM32_ICU_USE_TIM5 FALSE
+#define STM32_ICU_USE_TIM8 FALSE
+#define STM32_ICU_USE_TIM9 TRUE // wave input
+
+// todo: switch to continues ADC conversion for slow ADC?
+#define EFI_INTERNAL_SLOW_ADC_PWM &PWMD8
+// todo: switch to continues ADC conversion for fast ADC?
+#define EFI_INTERNAL_FAST_ADC_PWM &PWMD4
+
+
+#define STM32_PWM_USE_TIM1 FALSE
+#define STM32_PWM_USE_TIM2 FALSE
+#define STM32_PWM_USE_TIM3 FALSE
+//
+#define STM32_PWM_USE_TIM4 TRUE // fast adc
+#define STM32_PWM_USE_TIM5 FALSE
+#define STM32_PWM_USE_TIM8 TRUE // slow adc
+#define STM32_PWM_USE_TIM9 FALSE
+
+#define STM32_SPI_USE_SPI1 FALSE
+#define STM32_SPI_USE_SPI2 FALSE // external ADC
+#define STM32_SPI_USE_SPI3 TRUE // potentiometer
+
+#define STM32_CAN_USE_CAN1 TRUE
+#define STM32_CAN_USE_CAN2 TRUE
+
+#define STM32_I2C_I2C1_RX_DMA_STREAM STM32_DMA_STREAM_ID(1, 5)
+#define STM32_I2C_I2C1_TX_DMA_STREAM STM32_DMA_STREAM_ID(1, 6)
+
+#define EFI_CAN_DEVICE CAND2
+#define EFI_CAN_RX_PORT GPIOB
+#define EFI_CAN_RX_PIN 12
+#define EFI_CAN_RX_AF 9
+#define EFI_CAN_TX_PORT GPIOB
+#define EFI_CAN_TX_PIN 6
+#define EFI_CAN_TX_AF 9
+
+//#define EFI_CAN_DEVICE CAND1
+//#define EFI_CAN_RX_PORT GPIOB
+//#define EFI_CAN_RX_PIN 8
+//#define EFI_CAN_RX_AF 9
+//#define EFI_CAN_TX_PORT GPIOB
+//#define EFI_CAN_TX_PIN 9
+//#define EFI_CAN_TX_AF 9
+
+/**
+ * This section is for bottom-left corner SPI
+ */
+//#define SPI_CS1_PORT GPIOE
+//#define SPI_CS1_PIN 13
+//#define SPI_CS2_PORT GPIOE
+//#define SPI_CS2_PIN 14
+//#define SPI_CS3_PORT GPIOE
+//#define SPI_CS3_PIN 15
+//#define SPI_CS4_PORT GPIOD
+//#define SPI_CS4_PIN 10
+//#define SPI_SD_MODULE_PORT GPIOD
+//#define SPI_SD_MODULE_PIN 11
+#define EFI_SPI2_SCK_PORT GPIOB
+#define EFI_SPI2_SCK_PIN 13
+#define EFI_SPI2_MISO_PORT GPIOB
+#define EFI_SPI2_MISO_PIN 14
+#define EFI_SPI2_MOSI_PORT GPIOB
+#define EFI_SPI2_MOSI_PIN 15
+#define EFI_SPI2_AF 5
+
+/**
+ * This section is for right-side center SPI
+ */
+#define SPI_CS1_PORT GPIOD
+#define SPI_CS1_PIN 7
+// this is pointing into the sky for now - conflict with I2C
+#define SPI_CS2_PORT GPIOH
+// this is pointing into the sky for now - conflict with I2C
+#define SPI_CS2_PIN 0
+#define SPI_CS3_PORT GPIOD
+#define SPI_CS3_PIN 5
+#define SPI_CS4_PORT GPIOD
+#define SPI_CS4_PIN 3
+#define SPI_SD_MODULE_PORT GPIOD
+#define SPI_SD_MODULE_PIN 4
+#define EFI_SPI3_SCK_PORT GPIOB
+#define EFI_SPI3_SCK_PIN 3
+#define EFI_SPI3_MISO_PORT GPIOB
+#define EFI_SPI3_MISO_PIN 4
+#define EFI_SPI3_MOSI_PORT GPIOB
+#define EFI_SPI3_MOSI_PIN 5
+#define EFI_SPI3_AF 6
+#define MMC_CARD_SPI SPID3
+
+#define EFI_I2C_SCL_PORT GPIOB
+#define EFI_I2C_SCL_PIN 6
+#define EFI_I2C_SDA_PORT GPIOB
+#define EFI_I2C_SDA_PIN 7
+#define EFI_I2C_AF 4
+
+#define EFI_ADC_SLOW_CHANNELS_COUNT 10
+
+#define EFI_USE_ADC_CHANNEL_IN0 TRUE
+#define EFI_USE_ADC_CHANNEL_IN1 TRUE
+#define EFI_USE_ADC_CHANNEL_IN2 TRUE
+#define EFI_USE_ADC_CHANNEL_IN3 TRUE
+#define EFI_USE_ADC_CHANNEL_IN4 TRUE
+
+#define EFI_USE_ADC_CHANNEL_IN6 TRUE
+#define EFI_USE_ADC_CHANNEL_IN7 TRUE
+
+#define EFI_USE_ADC_CHANNEL_IN11 TRUE
+#define EFI_USE_ADC_CHANNEL_IN12 TRUE
+#define EFI_USE_ADC_CHANNEL_IN13 TRUE
+#define EFI_USE_ADC_CHANNEL_IN14 FALSE
+#define EFI_USE_ADC_CHANNEL_IN15 FALSE
+
+/**
+ * Patched version of ChibiOS/RT support extra details in the system error messages
+ */
+#define EFI_CUSTOM_PANIC_METHOD TRUE
+
+/*
+ * 10 channel board is (from left to right):
+ * ADC 15 PC5 TPS
+ * ADC 14 PC4 MAP
+ * ADC 7 PA7 IAT
+ * ADC 6 PA6 CLT
+ * ADC 5 PA5 TIM2_CH1
+ * ADC 4 PA4
+ * ADC 3 PA3
+ * ADC 2 PA2
+ * ADC 1 PA1 vBatt
+ * ADC 0 PA0 MAF
+ */
+
+#define ADC_LOGIC_TPS_2 ADC_CHANNEL_IN0
+
+#define ADC_CHANNEL_VREF ADC_CHANNEL_IN14
+
+
+/**
+ * currently ChibiOS uses only first and second channels of each timer for input capture
+ *
+ * So, our options are:
+ *
+ * TIM2_CH1
+ * PA5
+ *
+ * TIM4_CH1
+ * PB6
+ * PD12
+ *
+ * TIM9_CH1
+ * PE5
+ */
+
+
+/**
+ * Primary shaft position input
+ * TODO: ? rename to PRIMARY_TRIGGER?
+ */
+
+#define PRIMARY_SHAFT_POSITION_INPUT_DRIVER ICUD3
+#define PRIMARY_SHAFT_POSITION_INPUT_PORT GPIOC
+#define PRIMARY_SHAFT_POSITION_INPUT_PIN 6
+#define PRIMARY_SHAFT_POSITION_INPUT_CHANNEL ICU_CHANNEL_1
+
+/**
+ * Secondary shaft position input
+ * TODO: ? rename to SECONDARY_TRIGGER? *
+ */
+#define SECONDARY_SHAFT_POSITION_INPUT_DRIVER ICUD2
+#define SECONDARY_SHAFT_POSITION_INPUT_PORT GPIOA
+#define SECONDARY_SHAFT_POSITION_INPUT_PIN 5
+#define SECONDARY_SHAFT_POSITION_INPUT_CHANNEL ICU_CHANNEL_1
+
+/* Logic analyzer */
+#define LOGIC_ANALYZER_1_DRIVER ICUD1
+#define LOGIC_ANALYZER_1_PORT GPIOA
+#define LOGIC_ANALYZER_1_PIN 8
+
+#define LOGIC_ANALYZER_2_DRIVER ICUD9
+#define LOGIC_ANALYZER_2_PORT GPIOE
+#define LOGIC_ANALYZER_2_PIN 7
+
+//#define ETB_CONTROL_LINE_1_PORT GPIOE
+//#define ETB_CONTROL_LINE_1_PIN 0
+//
+//#define ETB_CONTROL_LINE_2_PORT GPIOB
+//#define ETB_CONTROL_LINE_2_PIN 8
+
+//#define CONSOLE_PORT GPIOB
+//#define CONSOLE_TX_PIN 10
+//#define CONSOLE_RX_PIN 11
+
+/**
+ * Here we define the pinout for the human-readable protocol via UART, TunerStudio pinout is defined separately
+ */
+//#define EFI_CONSOLE_TX_PORT GPIOD
+//#define EFI_CONSOLE_TX_PIN 8
+//#define EFI_CONSOLE_RX_PORT GPIOD
+//#define EFI_CONSOLE_RX_PIN 9
+//#define EFI_CONSOLE_AF 7
+
+#define EFI_CONSOLE_UART_DEVICE (&SD3)
+
+#define EFI_CONSOLE_TX_PORT GPIOC
+#define EFI_CONSOLE_TX_PIN 10
+#define EFI_CONSOLE_RX_PORT GPIOC
+#define EFI_CONSOLE_RX_PIN 11
+#define EFI_CONSOLE_AF 7
+
+//#define TS_SERIAL_TX_PORT GPIOD
+//#define TS_SERIAL_TX_PIN 8
+//#define TS_SERIAL_RX_PORT GPIOD
+//#define TS_SERIAL_RX_PIN 9
+//#define TS_SERIAL_AF 7
+
+#define TS_SERIAL_TX_PORT GPIOC
+#define TS_SERIAL_TX_PIN 10
+#define TS_SERIAL_RX_PORT GPIOC
+#define TS_SERIAL_RX_PIN 11
+#define TS_SERIAL_AF 7
+
+#define LED_CRANKING_STATUS_PORT GPIOD
+#define LED_CRANKING_STATUS_PIN GPIOD_LED3
+
+#define LED_RUNNING_STATUS_PORT GPIOD
+#define LED_RUNNING_STATUS_PIN GPIOD_LED4
+
+#define LED_ERROR_PORT GPIOD
+#define LED_ERROR_PIN GPIOD_LED5
+
+#define LED_COMMUNICATION_PORT GPIOD
+#define LED_COMMUNICATION_PIN GPIOD_LED6
+
+#define EFI_SIGNAL_EXECUTOR_SLEEP FALSE
+#define EFI_SIGNAL_EXECUTOR_SINGLE_TIMER FALSE
+#define EFI_SIGNAL_EXECUTOR_ONE_TIMER TRUE
+#define EFI_SIGNAL_EXECUTOR_HW_TIMER FALSE
+
+//#define EFI_SIGNAL_EXECUTOR_SLEEP FALSE
+//#define EFI_SIGNAL_EXECUTOR_SINGLE_TIMER TRUE
+
+
+// USART1 -> check defined STM32_SERIAL_USE_USART1
+// For GPS we have USART1. We can start with PB7 USART1_RX and PB6 USART1_TX
+#define GPS_SERIAL_DEVICE &SD1
+#define GPS_SERIAL_SPEED 38400
+#define GPS_PORT GPIOB
+#define GPS_SERIAL_TX_PIN 6
+#define GPS_SERIAL_RX_PIN 7
+
+#endif /*ARRO_BOARD_H_*/
diff --git a/config/engines/snow_blower.c b/config/engines/snow_blower.c
new file mode 100644
index 0000000000..c09ccfc50c
--- /dev/null
+++ b/config/engines/snow_blower.c
@@ -0,0 +1,14 @@
+/**
+ * @file snow_blower.c
+ * @brief Default configuration of a single-cylinder engine
+ *
+ * @date Sep 9, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "main.h"
+
+#if EFI_ENGINE_SNOW_BLOWER
+
+
+#endif
diff --git a/console/tunerstudio/tunerstudio.c b/console/tunerstudio/tunerstudio.c
new file mode 100644
index 0000000000..6a09eb32fb
--- /dev/null
+++ b/console/tunerstudio/tunerstudio.c
@@ -0,0 +1,276 @@
+/**
+ * @file tunerstudio.c
+ * @brief Integration with EFI Analytics Tuner Studio software
+ *
+ * todo: merge this file with tunerstudio_algo.c?
+ *
+ * @date Aug 26, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ *
+ */
+
+#include "main.h"
+
+#include "engine_state.h"
+#include "tunerstudio.h"
+#include "pin_repository.h"
+
+#include "main_trigger_callback.h"
+#include "flash_main.h"
+#include "usbconsole.h"
+#include "map_averaging.h"
+
+#include "tunerstudio_algo.h"
+#include "tunerstudio_configuration.h"
+#include "malfunction_central.h"
+#include "wave_math.h"
+
+#if EFI_TUNER_STUDIO
+
+static Logging logger;
+
+extern engine_configuration_s *engineConfiguration;
+extern board_configuration_s *boardConfiguration;
+extern persistent_config_s configWorkingCopy;
+extern FlashState flashState;
+
+extern SerialUSBDriver SDU1;
+#define CONSOLE_DEVICE &SDU1
+
+static efitimems_t previousWriteReportMs = 0;
+
+#if EFI_TUNER_STUDIO_OVER_USB
+#define ts_serail_ready() is_usb_serial_ready()
+#else
+#define ts_serail_ready() TRUE
+static SerialConfig tsSerialConfig = { TS_SERIAL_SPEED, 0, USART_CR2_STOP1_BITS | USART_CR2_LINEN, 0 };
+#endif /* EFI_TUNER_STUDIO_OVER_USB */
+
+static WORKING_AREA(TS_WORKING_AREA, UTILITY_THREAD_STACK_SIZE);
+
+static int tsCounter = 0;
+static int writeCounter = 0;
+
+static short pageId;
+
+static TunerStudioWriteRequest writeRequest;
+
+extern TunerStudioOutputChannels tsOutputChannels;
+
+//char *constantsAsPtr = (char *) &configWorkingCopy;
+
+extern TunerStudioState tsState;
+
+static void printStats(void) {
+#if EFI_TUNER_STUDIO_OVER_USB
+#else
+ scheduleMsg(&logger, "TS RX on %s%d", portname(TS_SERIAL_RX_PORT), TS_SERIAL_RX_PIN);
+ scheduleMsg(&logger, "TS TX on %s%d", portname(TS_SERIAL_TX_PORT), TS_SERIAL_TX_PIN);
+#endif /* EFI_TUNER_STUDIO_OVER_USB */
+ scheduleMsg(&logger, "TunerStudio total/error counter=%d/%d", tsCounter, tsState.errorCounter);
+ scheduleMsg(&logger, "TunerStudio H counter=%d", tsState.queryCommandCounter);
+ scheduleMsg(&logger, "TunerStudio O counter=%d size=%d", tsState.outputChannelsCommandCounter,
+ sizeof(tsOutputChannels));
+ scheduleMsg(&logger, "TunerStudio C counter=%d", tsState.readPageCommandsCounter);
+ scheduleMsg(&logger, "TunerStudio B counter=%d", tsState.burnCommandCounter);
+ scheduleMsg(&logger, "TunerStudio W counter=%d", writeCounter);
+ scheduleMsg(&logger, "page 0 size=%d", getTunerStudioPageSize(0));
+ scheduleMsg(&logger, "page 1 size=%d", getTunerStudioPageSize(1));
+}
+
+void tunerStudioWriteData(const uint8_t * buffer, int size) {
+ chSequentialStreamWrite(TS_SERIAL_DEVICE, buffer, size);
+}
+
+void tunerStudioDebug(char *msg) {
+#if EFI_TUNER_STUDIO_VERBOSE
+ scheduleMsg(&logger, "%s", msg);
+ printStats();
+#endif
+}
+
+char *getWorkingPageAddr(int pageIndex) {
+ switch (pageIndex) {
+ case 0:
+ return (char*) &configWorkingCopy.engineConfiguration;
+ case 1:
+ return (char*) &configWorkingCopy.boardConfiguration;
+ }
+ return NULL;
+}
+
+int getTunerStudioPageSize(int pageIndex) {
+ switch (pageIndex) {
+ case 0:
+ return sizeof(configWorkingCopy.engineConfiguration);
+ case 1:
+ return sizeof(configWorkingCopy.boardConfiguration);
+ }
+ return 0;
+
+}
+
+/**
+ * 'Write' command receives a single value at a given offset
+ */
+void handleValueWriteCommand(void) {
+ writeCounter++;
+
+ //tunerStudioDebug("got W (Write)"); // we can get a lot of these
+
+ int recieved = chSequentialStreamRead(TS_SERIAL_DEVICE, (uint8_t *)&pageId, 2);
+ if (recieved != 2) {
+ tsState.errorCounter++;
+ return;
+ }
+#if EFI_TUNER_STUDIO_VERBOSE
+// scheduleMsg(&logger, "Page number %d\r\n", pageId); // we can get a lot of these
+#endif
+
+ int size = sizeof(TunerStudioWriteRequest);
+// scheduleMsg(&logger, "Reading %d\r\n", size);
+
+ recieved = chSequentialStreamRead(TS_SERIAL_DEVICE, (uint8_t *)&writeRequest, size);
+// scheduleMsg(&logger, "got %d", recieved);
+
+// unsigned char offset = writeBuffer[0];
+// unsigned char value = writeBuffer[1];
+//
+
+ efitimems_t nowMs = currentTimeMillis();
+ if (nowMs - previousWriteReportMs > 5) {
+ previousWriteReportMs = nowMs;
+// scheduleMsg(&logger, "page %d offset %d: value=%d", pageId, writeRequest.offset, writeRequest.value);
+ }
+
+ getWorkingPageAddr(pageId)[writeRequest.offset] = writeRequest.value;
+
+// scheduleMsg(&logger, "va=%d", configWorkingCopy.boardConfiguration.idleValvePin);
+}
+
+void handlePageReadCommand(void) {
+ tsState.readPageCommandsCounter++;
+ tunerStudioDebug("got C (Constants)");
+ int recieved = chSequentialStreamRead(TS_SERIAL_DEVICE, (uint8_t *)&pageId, 2);
+ if (recieved != 2) {
+ tsState.errorCounter++;
+ return;
+ }
+#if EFI_TUNER_STUDIO_VERBOSE
+ scheduleMsg(&logger, "Page number %d", pageId);
+#endif
+
+ tunerStudioWriteData((const uint8_t *) getWorkingPageAddr(pageId), getTunerStudioPageSize(pageId));
+}
+
+
+/**
+ * 'Burn' command is a command to commit the changes
+ */
+void handleBurnCommand(void) {
+ tsState.burnCommandCounter++;
+
+ tunerStudioDebug("got B (Burn)");
+
+ int recieved = chSequentialStreamRead(TS_SERIAL_DEVICE, (uint8_t *)&pageId, 2);
+ if (recieved != 2) {
+ tsState.errorCounter++;
+ return;
+ }
+#if EFI_TUNER_STUDIO_VERBOSE
+ scheduleMsg(&logger, "Page number %d\r\n", pageId);
+#endif
+
+ // todo: how about some multi-threading?
+ memcpy(&flashState.persistentConfiguration, &configWorkingCopy, sizeof(persistent_config_s));
+
+ scheduleMsg(&logger, "va1=%d", configWorkingCopy.boardConfiguration.idleValvePin);
+ scheduleMsg(&logger, "va2=%d", flashState.persistentConfiguration.boardConfiguration.idleValvePin);
+
+ writeToFlash();
+ incrementGlobalConfigurationVersion();
+}
+
+static msg_t tsThreadEntryPoint(void *arg) {
+ (void) arg;
+ chRegSetThreadName("tunerstudio thread");
+
+ int wasReady = FALSE;
+ while (true) {
+ int isReady = ts_serail_ready();
+ if (!isReady) {
+ chThdSleepMilliseconds(10);
+ wasReady = FALSE;
+ continue;
+ }
+ if (!wasReady) {
+ wasReady = TRUE;
+// scheduleSimpleMsg(&logger, "ts channel is now ready ", hTimeNow());
+ }
+
+ short command = (short) chSequentialStreamGet(TS_SERIAL_DEVICE);
+ int success = tunerStudioHandleCommand(command);
+ if (!success && command != 0)
+ print("got unexpected TunerStudio command %c:%d\r\n", command, command);
+
+ tsCounter++;
+ }
+#if defined __GNUC__
+ return 0;
+#endif
+}
+
+extern engine_configuration_s *engineConfiguration;
+
+void syncTunerStudioCopy(void) {
+ memcpy(&configWorkingCopy, &flashState.persistentConfiguration, sizeof(persistent_config_s));
+}
+
+void startTunerStudioConnectivity(void) {
+ initLogging(&logger, "tuner studio");
+#if EFI_TUNER_STUDIO_OVER_USB
+ print("TunerStudio over USB serial");
+ usb_serial_start();
+#else
+ print("TunerStudio over USART");
+ mySetPadMode("tunerstudio rx", TS_SERIAL_RX_PORT, TS_SERIAL_RX_PIN, PAL_MODE_ALTERNATE(TS_SERIAL_AF));
+ mySetPadMode("tunerstudio tx", TS_SERIAL_TX_PORT, TS_SERIAL_TX_PIN, PAL_MODE_ALTERNATE(TS_SERIAL_AF));
+
+ sdStart(TS_SERIAL_DEVICE, &tsSerialConfig);
+#endif
+
+ syncTunerStudioCopy();
+
+ addConsoleAction("tsinfo", printStats);
+
+ chThdCreateStatic(TS_WORKING_AREA, sizeof(TS_WORKING_AREA), NORMALPRIO, tsThreadEntryPoint, NULL);
+}
+
+void updateTunerStudioState() {
+ tsOutputChannels.rpm = getRpm();
+ tsOutputChannels.coolant_temperature = getCoolantTemperature();
+ tsOutputChannels.intake_air_temperature = getIntakeAirTemperature();
+ tsOutputChannels.throttle_positon = getTPS();
+ tsOutputChannels.mass_air_flow = getMaf();
+ tsOutputChannels.air_fuel_ratio = getAfr();
+ tsOutputChannels.v_batt = getVBatt();
+ tsOutputChannels.tpsADC = getTPS10bitAdc();
+ tsOutputChannels.atmospherePressure = getAtmosphericPressure();
+ tsOutputChannels.manifold_air_pressure = getMap();
+ tsOutputChannels.checkEngine = hasErrorCodes();
+}
+
+#endif /* EFI_TUNER_STUDIO */
diff --git a/controllers/algo/event_queue.cpp b/controllers/algo/event_queue.cpp
new file mode 100644
index 0000000000..6dfca9306c
--- /dev/null
+++ b/controllers/algo/event_queue.cpp
@@ -0,0 +1,89 @@
+/**
+ * @file event_queue.cpp
+ * This is a data structure which keeps track of all pending events
+ * Implemented as a linked list, which is fine since the number of
+ * pending events is pretty low
+ * todo: MAYBE migrate to a better data structure, but that's low priority
+ *
+ * this data structure is NOT thread safe
+ *
+ * @date Apr 17, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "event_queue.h"
+#include "efitime.h"
+#include "utlist.h"
+
+EventQueue::EventQueue() {
+ head = NULL;
+}
+
+void EventQueue::insertTask(scheduling_s *scheduling, uint64_t nowUs, int delayUs, schfunc_t callback, void *param) {
+ if (callback == NULL)
+ firmwareError("NULL callback");
+ uint64_t time = nowUs + delayUs;
+
+ scheduling->momentUs = time;
+#if EFI_SIGNAL_EXECUTOR_ONE_TIMER
+ scheduling->callback = callback;
+ scheduling->param = param;
+#endif
+
+ scheduling_s * elt;
+ LL_FOREACH(head, elt)
+ {
+ if (elt == scheduling) {
+ firmwareError("re-adding element");
+ return;
+ }
+ }
+
+ LL_PREPEND(head, scheduling);
+}
+
+void EventQueue::insertTask(scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param) {
+ insertTask(scheduling, getTimeNowUs(), delayUs, callback, param);
+}
+
+/**
+ * Get the timestamp of the soonest pending action
+ */
+uint64_t EventQueue::getNextEventTime(uint64_t nowUs) {
+ scheduling_s * elt;
+ // this is a large value which is expected to be larger than any real time
+ uint64_t result = EMPTY_QUEUE;
+
+ LL_FOREACH(head, elt)
+ {
+ if (elt->momentUs <= nowUs) {
+ // todo: I am not so sure about this branch
+ continue;
+ }
+ if (elt->momentUs < result)
+ result = elt->momentUs;
+ }
+ return result;
+}
+
+/**
+ * Invoke all pending actions prior to specified timestamp
+ */
+void EventQueue::executeAll(uint64_t now) {
+ scheduling_s * elt, *tmp;
+
+// here we need safe iteration because we are removing elements
+ LL_FOREACH_SAFE(head, elt, tmp)
+ {
+ if (elt->momentUs <= now) {
+ LL_DELETE(head, elt);
+#if EFI_SIGNAL_EXECUTOR_ONE_TIMER
+ elt->callback(elt->param);
+#endif /* EFI_SIGNAL_EXECUTOR_ONE_TIMER */
+ }
+ }
+}
+
+void EventQueue::clear(void) {
+ head = NULL;
+}
diff --git a/controllers/algo/event_queue.h b/controllers/algo/event_queue.h
new file mode 100644
index 0000000000..3f307c0e31
--- /dev/null
+++ b/controllers/algo/event_queue.h
@@ -0,0 +1,30 @@
+/**
+ * @file event_queue.h
+ *
+ * @date Apr 17, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#include "signal_executor.h"
+
+#ifndef EVENT_SCHEDULER_H_
+#define EVENT_SCHEDULER_H_
+
+#define EMPTY_QUEUE 0x0FFFFFFFFFFFFFFFLL
+
+class EventQueue {
+public:
+ EventQueue();
+
+ void insertTask(scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param);
+ void insertTask(scheduling_s *scheduling, uint64_t nowUs, int delayUs, schfunc_t callback, void *param);
+
+ void executeAll(uint64_t now);
+
+ uint64_t getNextEventTime(uint64_t nowUs);
+ void clear(void);
+private:
+ scheduling_s *head;
+};
+
+#endif /* EVENT_SCHEDULER_H_ */
diff --git a/controllers/algo/signal_executor.c b/controllers/algo/signal_executor.c
new file mode 100644
index 0000000000..0fe0845b04
--- /dev/null
+++ b/controllers/algo/signal_executor.c
@@ -0,0 +1,163 @@
+/**
+ * @file signal_executor.c
+ *
+ * todo: we should split this file into two:
+ * one for pure scheduling and another one for signal output which would
+ * use the scheduling
+ *
+ * @date Dec 4, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "main.h"
+#include "signal_executor.h"
+
+#if EFI_WAVE_CHART
+#include "rpm_calculator.h"
+#endif
+
+#if EFI_WAVE_ANALYZER
+
+/**
+ * Signal executors feed digital events right into WaveChart used by Sniffer tab of Dev Console
+ */
+#include "wave_analyzer.h"
+
+#endif /* EFI_WAVE_ANALYZER */
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ static Logging logger;
+#endif
+
+void initSignalExecutor(void) {
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ initLogging(&logger, "s exec");
+#endif
+ initSignalExecutorImpl();
+}
+
+void initOutputSignalBase(OutputSignal *signal) {
+ signal->status = IDLE;
+// signal->last_scheduling_time = 0;
+ signal->initialized = TRUE;
+}
+
+static void turnHigh(OutputSignal *signal) {
+#if EFI_DEFAILED_LOGGING
+// signal->hi_time = hTimeNow();
+#endif /* EFI_DEFAILED_LOGGING */
+ io_pin_e pin = signal->io_pin;
+ // turn the output level ACTIVE
+ // todo: this XOR should go inside the setOutputPinValue method
+ setOutputPinValue(pin, TRUE);
+ // sleep for the needed duration
+
+#if EFI_PROD_CODE || EFI_SIMULATOR
+ if(
+ pin == SPARKOUT_1_OUTPUT ||
+ pin == SPARKOUT_3_OUTPUT) {
+// time_t now = hTimeNow();
+// float an = getCrankshaftAngle(now);
+// scheduleMsg(&logger, "spark up%d %d", pin, now);
+// scheduleMsg(&logger, "spark angle %d %f", (int)an, an);
+ }
+#endif
+
+#if EFI_WAVE_CHART
+ addWaveChartEvent(signal->name, "up", "");
+#endif /* EFI_WAVE_ANALYZER */
+}
+
+static void turnLow(OutputSignal *signal) {
+ // turn off the output
+ // todo: this XOR should go inside the setOutputPinValue method
+ setOutputPinValue(signal->io_pin, FALSE);
+
+#if EFI_DEFAILED_LOGGING
+ systime_t after = hTimeNow();
+ debugInt(&signal->logging, "a_time", after - signal->hi_time);
+ scheduleLogging(&signal->logging);
+#endif /* EFI_DEFAILED_LOGGING */
+
+#if EFI_WAVE_CHART
+ addWaveChartEvent(signal->name, "down", "");
+#endif /* EFI_WAVE_ANALYZER */
+}
+
+/**
+ *
+ * @param delay the number of ticks before the output signal
+ * immediate output if delay is zero
+ * @param dwell the number of ticks of output duration
+ *
+ */
+
+int getRevolutionCounter(void);
+
+void scheduleOutput(OutputSignal *signal, float delayMs, float durationMs) {
+ if (durationMs < 0) {
+ firmwareError("duration cannot be negative: %d", durationMs);
+ return;
+ }
+
+ scheduleOutputBase(signal, delayMs, durationMs);
+
+ int index = getRevolutionCounter() % 2;
+ scheduling_s * sUp = &signal->signalTimerUp[index];
+ scheduling_s * sDown = &signal->signalTimerDown[index];
+
+ scheduleTask(sUp, MS2US(delayMs), (schfunc_t) &turnHigh, (void *) signal);
+ scheduleTask(sDown, MS2US(delayMs + durationMs), (schfunc_t) &turnLow, (void*)signal);
+
+// signal->last_scheduling_time = now;
+}
+
+void scheduleOutputBase(OutputSignal *signal, float delayMs, float durationMs) {
+ /**
+ * it's better to check for the exact 'TRUE' value since otherwise
+ * we would accept any memory garbage
+ */
+ chDbgCheck(signal->initialized == TRUE, "Signal not initialized");
+// signal->offset = offset;
+// signal->duration = duration;
+}
+
+
+char *getPinName(io_pin_e io_pin) {
+ switch (io_pin) {
+ case SPARKOUT_1_OUTPUT:
+ return "Spark 1";
+ case SPARKOUT_2_OUTPUT:
+ return "Spark 2";
+ case SPARKOUT_3_OUTPUT:
+ return "Spark 3";
+ case SPARKOUT_4_OUTPUT:
+ return "Spark 4";
+
+ case INJECTOR_1_OUTPUT:
+ return "Injector 1";
+ case INJECTOR_2_OUTPUT:
+ return "Injector 2";
+ case INJECTOR_3_OUTPUT:
+ return "Injector 3";
+ case INJECTOR_4_OUTPUT:
+ return "Injector 4";
+ case INJECTOR_5_OUTPUT:
+ return "Injector 5";
+ default:
+ return "No name";
+ }
+}
diff --git a/controllers/algo/signal_executor.h b/controllers/algo/signal_executor.h
new file mode 100644
index 0000000000..659848da90
--- /dev/null
+++ b/controllers/algo/signal_executor.h
@@ -0,0 +1,111 @@
+/**
+ * @file signal_executor.h
+ * @brief Asynchronous output signal header
+ *
+ * @date Feb 10, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SPARKOUT_H_
+#define SPARKOUT_H_
+
+#include "rusefi_enums.h"
+#include "global.h"
+#include "efifeatures.h"
+#include "io_pins.h"
+
+#if EFI_PROD_CODE
+#include "datalogging.h"
+#endif /* EFI_PROD_CODE */
+
+#if EFI_SIGNAL_EXECUTOR_SLEEP
+#include "signal_executor_sleep.h"
+#endif /* EFI_SIGNAL_EXECUTOR_SLEEP */
+
+#if EFI_SIGNAL_EXECUTOR_SINGLE_TIMER
+#include "signal_executor_single_timer.h"
+#endif /* EFI_SIGNAL_EXECUTOR_SINGLE_TIMER */
+
+typedef void (*schfunc_t)(void *);
+
+typedef struct scheduling_struct scheduling_s;
+struct scheduling_struct {
+ //int initialized;
+#if EFI_SIGNAL_EXECUTOR_SLEEP
+ VirtualTimer timer;
+#endif /* EFI_SIGNAL_EXECUTOR_SLEEP */
+#if EFI_SIGNAL_EXECUTOR_SINGLE_TIMER
+ volatile time_t moment;
+#endif /* EFI_SIGNAL_EXECUTOR_SINGLE_TIMER */
+
+ volatile uint64_t momentUs;
+#if EFI_SIGNAL_EXECUTOR_ONE_TIMER
+ schfunc_t callback;
+ void *param;
+#endif
+
+ scheduling_s *next;
+};
+
+typedef enum {
+ IDLE = 0, ACTIVE
+} executor_status_t;
+
+/**
+ * @brief Asynchronous output signal data structure
+ */
+typedef struct OutputSignal_struct OutputSignal;
+struct OutputSignal_struct {
+ /**
+ * name of this signal
+ */
+ char *name;
+ io_pin_e io_pin;
+#if 0 // depricated
+ // time in system ticks
+ volatile int offset;
+ // time in system ticks
+ volatile int duration;
+#endif
+ int initialized;
+
+// time_t last_scheduling_time;
+// time_t hi_time;
+
+ /**
+ * We are alternating instances so that events which extend into next revolution are not overriden while
+ * scheduling next revolution events
+ */
+ scheduling_s signalTimerUp[2];
+ scheduling_s signalTimerDown[2];
+
+ executor_status_t status;
+
+#if EFI_SIGNAL_EXECUTOR_HW_TIMER
+ // todo
+#endif
+
+ OutputSignal *next;
+};
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+void initOutputSignal(OutputSignal *signal, io_pin_e ioPin);
+void scheduleOutput(OutputSignal *signal, float delayMs, float durationMs);
+void initOutputSignalBase(OutputSignal *signal);
+void scheduleOutputBase(OutputSignal *signal, float delayMs, float durationMs);
+
+void initSignalExecutor(void);
+void initSignalExecutorImpl(void);
+void scheduleTask(scheduling_s *scheduling, int delayUs, schfunc_t callback, void *param);
+void scheduleByAngle(scheduling_s *timer, float angle, schfunc_t callback, void *param);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SPARKOUT_H_ */
diff --git a/controllers/algo/signal_executor_single_timer_algo.c b/controllers/algo/signal_executor_single_timer_algo.c
new file mode 100644
index 0000000000..73893445dd
--- /dev/null
+++ b/controllers/algo/signal_executor_single_timer_algo.c
@@ -0,0 +1,74 @@
+/**
+ * @file signal_executor_single_timer_algo.c
+ *
+ * @date Nov 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "signal_executor.h"
+#include "signal_executor_single_timer_algo.h"
+#include "main.h"
+#include "utlist.h"
+#include "io_pins.h"
+
+#if EFI_WAVE_ANALYZER
+#include "wave_analyzer.h"
+#include "wave_chart.h"
+extern WaveChart waveChart;
+#endif
+
+#if EFI_SIGNAL_EXECUTOR_SINGLE_TIMER
+/**
+ * @brief Output list
+ *
+ * List of all active output signals
+ * This is actually the head of the list.
+ * When the list is empty (initial state) the head of the list should be NULL.
+ * This is by design.
+ */
+OutputSignal *st_output_list = NULL;
+
+inline void registerSignal(OutputSignal *signal) {
+ LL_APPEND(st_output_list, signal);
+}
+
+void setOutputPinValue(io_pin_e pin, int value);
+
+/**
+ * @return time of next event within for this signal
+ * @todo Find better name.
+ */
+inline time_t toggleSignalIfNeeded(OutputSignal *out, time_t now) {
+// chDbgCheck(out!=NULL, "out is NULL");
+// chDbgCheck(out->io_pin < IO_PIN_COUNT, "pin assertion");
+ time_t last = out->last_scheduling_time;
+ //estimated = last + out->timing[out->status];
+ time_t estimated = last + GET_DURATION(out);
+ if (now >= estimated) {
+ out->status ^= 1; /* toggle status */
+ //setOutputPinValue(out->io_pin, out->status); /* Toggle output */
+ palWritePad(GPIOE, 5, out->status);
+#if EFI_WAVE_ANALYZER
+// addWaveChartEvent(out->name, out->status ? "up" : "down", "");
+#endif /* EFI_WAVE_ANALYZER */
+
+// out->last_scheduling_time = now; /* store last update */
+ estimated = now + GET_DURATION(out); /* update estimation */
+ }
+ return estimated - now;
+}
+#endif /* EFI_SIGNAL_EXECUTOR_SINGLE_TIMER */
diff --git a/efitime.h b/efitime.h
new file mode 100644
index 0000000000..d39a9aa34c
--- /dev/null
+++ b/efitime.h
@@ -0,0 +1,61 @@
+/**
+ * @file efitime.h
+ *
+ * By the way, there are 86400000 milliseconds in a day
+ *
+ * @date Apr 14, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef EFITIME_H_
+#define EFITIME_H_
+
+#include
+#include "efifeatures.h"
+
+/**
+ * integer time in milliseconds
+ * 32 bit 4B / 1000 = 4M seconds = 1111.11 hours = 46 days.
+ * Please restart your ECU every 46 days? :)
+ */
+typedef uint32_t efitimems_t;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+#define US_PER_SECOND 1000000
+
+#define MS2US(MS_TIME) ((MS_TIME) * 1000)
+
+#define US_TO_TI_TEMP 10
+
+// todo: implement a function to work with times considering counter overflow
+#define overflowDiff(now, time) ((now) - (time))
+
+/**
+ * 64-bit counter of microseconds (1/1 000 000 of a second) since MCU reset
+ *
+ * By using 64 bit, we can achive a very precise timestamp which does not overflow.
+ * The primary implementation counts the number of CPU cycles from MCU reset.
+ */
+uint64_t getTimeNowUs(void);
+
+uint64_t getHalTimer(void);
+
+/**
+ * @brief Returns the number of milliseconds since the board initialization.
+ */
+efitimems_t currentTimeMillis(void);
+
+/**
+ * @brief Current system time in seconds.
+ */
+int getTimeNowSeconds(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* EFITIME_H_ */
diff --git a/engine_math.cpp b/engine_math.cpp
new file mode 100644
index 0000000000..9e8ef9767e
--- /dev/null
+++ b/engine_math.cpp
@@ -0,0 +1,340 @@
+/**
+ * @file engine_math.cpp
+ * @brief
+ *
+ * @date Jul 13, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "engine_math.h"
+#include "main.h"
+#include "engine_configuration.h"
+#include "interpolation.h"
+#include "allsensors.h"
+#include "io_pins.h"
+#include "OutputSignalList.h"
+#include "trigger_decoder.h"
+
+/*
+ * default Volumetric Efficiency
+ */
+//float getDefaultVE(int rpm) {
+// if (rpm > 5000)
+// return interpolate(5000, 1.1, 8000, 1, rpm);
+// return interpolate(500, 0.5, 5000, 1.1, rpm);
+//}
+//#define K_AT_MIN_RPM_MIN_TPS 0.25
+//#define K_AT_MIN_RPM_MAX_TPS 0.25
+//#define K_AT_MAX_RPM_MIN_TPS 0.25
+//#define K_AT_MAX_RPM_MAX_TPS 0.9
+//
+//#define rpmMin 500
+//#define rpmMax 8000
+//
+//#define tpMin 0
+//#define tpMax 100
+//
+// http://rusefi.com/math/t_charge.html
+// /
+//float getTCharge(int rpm, int tps, float coolantTemp, float airTemp) {
+// float minRpmKcurrentTPS = interpolate(tpMin, K_AT_MIN_RPM_MIN_TPS, tpMax,
+// K_AT_MIN_RPM_MAX_TPS, tps);
+// float maxRpmKcurrentTPS = interpolate(tpMin, K_AT_MAX_RPM_MIN_TPS, tpMax,
+// K_AT_MAX_RPM_MAX_TPS, tps);
+//
+// float Tcharge_coff = interpolate(rpmMin, minRpmKcurrentTPS, rpmMax,
+// maxRpmKcurrentTPS, rpm);
+//
+// float Tcharge = coolantTemp * (1 - Tcharge_coff) + airTemp * Tcharge_coff;
+//
+// return Tcharge;
+//}
+#define MAX_STARTING_FUEL 15
+#define MIN_STARTING_FUEL 8
+
+/**
+ * @return time needed to rotate crankshaft by one degree, in milliseconds.
+ */
+float getOneDegreeTimeMs(int rpm) {
+ return 1000.0 * 60 / 360 / rpm;
+}
+
+/**
+ * @return number of milliseconds in one crankshaft revolution
+ */
+float getCrankshaftRevolutionTimeMs(int rpm) {
+ return 360 * getOneDegreeTimeMs(rpm);
+}
+
+/**
+ * @brief Shifts angle into the [0..720) range
+ * TODO: should be 'crankAngleRange' range?
+ */
+float fixAngle(float angle) {
+ // I guess this implementation would be faster than 'angle % 720'
+ while (angle < 0)
+ angle += 720;
+ while (angle > 720)
+ angle -= 720;
+ return angle;
+}
+
+/**
+ * @brief Returns engine load according to selected engine_load_mode
+ *
+ */
+float getEngineLoadT(engine_configuration_s *engineConfiguration) {
+ switch (engineConfiguration->engineLoadMode) {
+ case LM_MAF:
+ return getMaf();
+ case LM_MAP:
+ return getMap();
+ case LM_TPS:
+ return getTPS();
+ case LM_SPEED_DENSITY:
+ // TODO: real implementation
+ return getMap();
+ default:
+ firmwareError("Unexpected engine load parameter: %d", engineConfiguration->engineLoadMode);
+ return -1;
+ }
+}
+
+void setSingleCoilDwell(engine_configuration_s *engineConfiguration) {
+ for (int i = 0; i < DWELL_CURVE_SIZE; i++) {
+ engineConfiguration->sparkDwellBins[i] = 0;
+ engineConfiguration->sparkDwell[i] = -1;
+ }
+
+ engineConfiguration->sparkDwellBins[5] = 1;
+ engineConfiguration->sparkDwell[5] = 4;
+
+ engineConfiguration->sparkDwellBins[6] = 4500;
+ engineConfiguration->sparkDwell[6] = 4;
+
+ engineConfiguration->sparkDwellBins[7] = 12500;
+ engineConfiguration->sparkDwell[7] = 0;
+}
+
+int isCrankingRT(engine_configuration_s *engineConfiguration, int rpm) {
+ return rpm > 0 && rpm < engineConfiguration->crankingSettings.crankingRpm;
+}
+
+OutputSignalList ignitionSignals;
+OutputSignalList injectonSignals;
+
+void initializeIgnitionActions(float baseAngle, engine_configuration_s *engineConfiguration,
+ engine_configuration2_s *engineConfiguration2) {
+ chDbgCheck(engineConfiguration->cylindersCount > 0, "cylindersCount");
+ ignitionSignals.clear();
+
+ EventHandlerConfiguration *config = &engineConfiguration2->engineEventConfiguration;
+ resetEventList(&config->ignitionEvents);
+
+ switch (engineConfiguration->ignitionMode) {
+ case IM_ONE_COIL:
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ // todo: extract method
+ float angle = baseAngle + 720.0 * i / engineConfiguration->cylindersCount;
+
+ registerActuatorEventExt(engineConfiguration, &engineConfiguration2->triggerShape, &config->ignitionEvents,
+ ignitionSignals.add(SPARKOUT_1_OUTPUT), angle);
+ }
+ break;
+ case IM_WASTED_SPARK:
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ float angle = baseAngle + 720.0 * i / engineConfiguration->cylindersCount;
+
+ int wastedIndex = i % (engineConfiguration->cylindersCount / 2);
+
+ int id = (getCylinderId(engineConfiguration->firingOrder, wastedIndex) - 1);
+ io_pin_e ioPin = (io_pin_e) (SPARKOUT_1_OUTPUT + id);
+
+ registerActuatorEventExt(engineConfiguration, &engineConfiguration2->triggerShape, &config->ignitionEvents,
+ ignitionSignals.add(ioPin), angle);
+
+ }
+
+ break;
+ case IM_INDIVIDUAL_COILS:
+ for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
+ float angle = baseAngle + 720.0 * i / engineConfiguration->cylindersCount;
+
+ io_pin_e pin = (io_pin_e) ((int) SPARKOUT_1_OUTPUT + getCylinderId(engineConfiguration->firingOrder, i) - 1);
+ registerActuatorEventExt(engineConfiguration, &engineConfiguration2->triggerShape, &config->ignitionEvents,
+ ignitionSignals.add(pin), angle);
+ }
+ break;
+
+ default:
+ firmwareError("unsupported ignitionMode %d in initializeIgnitionActions()", engineConfiguration->ignitionMode);
+ }
+}
+
+void addFuelEvents(engine_configuration_s const *e, engine_configuration2_s *engineConfiguration2,
+ ActuatorEventList *list, injection_mode_e mode) {
+ resetEventList(list);
+
+ trigger_shape_s *s = &engineConfiguration2->triggerShape;
+
+ float baseAngle = e->globalTriggerAngleOffset + e->injectionOffset;
+
+ switch (mode) {
+ case IM_SEQUENTIAL:
+ for (int i = 0; i < e->cylindersCount; i++) {
+ io_pin_e pin = (io_pin_e) ((int) INJECTOR_1_OUTPUT + getCylinderId(e->firingOrder, i) - 1);
+ float angle = baseAngle + i * 720.0 / e->cylindersCount;
+ registerActuatorEventExt(e, s, list, injectonSignals.add(pin), angle);
+ }
+ break;
+ case IM_SIMULTANEOUS:
+ for (int i = 0; i < e->cylindersCount; i++) {
+ float angle = baseAngle + i * 720.0 / e->cylindersCount;
+
+ for (int j = 0; j < e->cylindersCount; j++) {
+ io_pin_e pin = (io_pin_e) ((int) INJECTOR_1_OUTPUT + j);
+ registerActuatorEventExt(e, s, list, injectonSignals.add(pin), angle);
+ }
+ }
+ break;
+ case IM_BATCH:
+ for (int i = 0; i < e->cylindersCount; i++) {
+ io_pin_e pin = (io_pin_e) ((int) INJECTOR_1_OUTPUT + (i % 2));
+ float angle = baseAngle + i * 720.0 / e->cylindersCount;
+ registerActuatorEventExt(e, s, list, injectonSignals.add(pin), angle);
+ }
+ break;
+ default:
+ firmwareError("Unexpected injection mode %d", mode);
+ }
+}
+
+/**
+ * @return Spark dwell time, in milliseconds.
+ */
+float getSparkDwellMsT(engine_configuration_s *engineConfiguration, int rpm) {
+ if (isCrankingR(rpm)) {
+ // technically this could be implemented via interpolate2d
+ float angle = engineConfiguration->crankingChargeAngle;
+ return getOneDegreeTimeMs(rpm) * angle;
+ }
+
+ if (rpm > engineConfiguration->rpmHardLimit) {
+ // technically this could be implemented via interpolate2d by setting everything above rpmHardLimit to zero
+ warning(OBD_PCM_Processor_Fault, "skipping spark due to rpm=%d", rpm);
+ return 0;
+ }
+
+ return interpolate2d(rpm, engineConfiguration->sparkDwellBins, engineConfiguration->sparkDwell, DWELL_CURVE_SIZE);
+}
+
+void registerActuatorEventExt(engine_configuration_s const *engineConfiguration, trigger_shape_s * s,
+ ActuatorEventList *list, OutputSignal *actuator, float angleOffset) {
+ chDbgCheck(s->size > 0, "uninitialized trigger_shape_s");
+
+ angleOffset = fixAngle(angleOffset + engineConfiguration->globalTriggerAngleOffset);
+
+ int triggerIndexOfZeroEvent = s->triggerShapeSynchPointIndex;
+
+ // todo: migrate to crankAngleRange?
+ float firstAngle = s->wave.switchTimes[triggerIndexOfZeroEvent] * 720;
+
+ // let's find the last trigger angle which is less or equal to the desired angle
+ int i;
+ for (i = 0; i < s->size - 1; i++) {
+ // todo: we need binary search here
+ float angle = fixAngle(s->wave.switchTimes[(triggerIndexOfZeroEvent + i + 1) % s->size] * 720 - firstAngle);
+ if (angle > angleOffset)
+ break;
+ }
+ // explicit check for zero to avoid issues where logical zero is not exactly zero due to float nature
+ float angle =
+ i == 0 ? 0 : fixAngle(s->wave.switchTimes[(triggerIndexOfZeroEvent + i) % s->size] * 720 - firstAngle);
+
+ chDbgCheck(angleOffset >= angle, "angle constraint violation in registerActuatorEventExt()");
+
+ registerActuatorEvent(list, i, actuator, angleOffset - angle);
+}
+
+//float getTriggerEventAngle(int triggerEventIndex) {
+// return 0;
+//}
+
+/**
+ * there is some BS related to isnan in MinGW, so let's have all the issues in one place
+ */
+int cisnan(float f) {
+ return *(((int*) (&f))) == 0x7FC00000;
+}
+
+static int order_1_THEN_3_THEN_4_THEN2[] = { 1, 3, 4, 2 };
+
+static int order_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4[] = { 1, 5, 3, 6, 2, 4 };
+
+/**
+ * @param index from zero to cylindersCount - 1
+ * @return cylinderId from one to cylindersCount
+ */
+int getCylinderId(firing_order_e firingOrder, int index) {
+
+ switch (firingOrder) {
+ case FO_ONE_CYLINDER:
+ return 1;
+ case FO_1_THEN_3_THEN_4_THEN2:
+ return order_1_THEN_3_THEN_4_THEN2[index];
+ case FO_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4:
+ return order_1_THEN_5_THEN_3_THEN_6_THEN_2_THEN_4[index];
+
+ default:
+ firmwareError("getCylinderId not supported for %d", firingOrder);
+ }
+ return -1;
+}
+
+void prepareOutputSignals(engine_configuration_s *engineConfiguration, engine_configuration2_s *engineConfiguration2) {
+
+ // todo: move this reset into decoder
+ engineConfiguration2->triggerShape.triggerShapeSynchPointIndex = findTriggerZeroEventIndex(
+ &engineConfiguration2->triggerShape, &engineConfiguration->triggerConfig);
+
+ injectonSignals.clear();
+ EventHandlerConfiguration *config = &engineConfiguration2->engineEventConfiguration;
+ addFuelEvents(engineConfiguration, engineConfiguration2, &config->crankingInjectionEvents,
+ engineConfiguration->crankingInjectionMode);
+ addFuelEvents(engineConfiguration, engineConfiguration2, &config->injectionEvents,
+ engineConfiguration->injectionMode);
+}
+
+void setTableBin(float array[], int size, float l, float r) {
+ for (int i = 0; i < size; i++)
+ array[i] = interpolate(0, l, size - 1, r, i);
+}
+
+void setFuelRpmBin(engine_configuration_s *engineConfiguration, float l, float r) {
+ setTableBin(engineConfiguration->fuelRpmBins, FUEL_RPM_COUNT, l, r);
+}
+
+void setFuelLoadBin(engine_configuration_s *engineConfiguration, float l, float r) {
+ setTableBin(engineConfiguration->fuelLoadBins, FUEL_LOAD_COUNT, l, r);
+}
+
+void setTimingRpmBin(engine_configuration_s *engineConfiguration, float l, float r) {
+ setTableBin(engineConfiguration->ignitionRpmBins, IGN_RPM_COUNT, l, r);
+}
+
+void setTimingLoadBin(engine_configuration_s *engineConfiguration, float l, float r) {
+ setTableBin(engineConfiguration->ignitionLoadBins, IGN_LOAD_COUNT, l, r);
+}
diff --git a/engine_math.h b/engine_math.h
new file mode 100644
index 0000000000..dfc886270a
--- /dev/null
+++ b/engine_math.h
@@ -0,0 +1,62 @@
+/**
+ * @file engine_math.h
+ *
+ * @date Jul 13, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef ENGINE_MATH_H_
+#define ENGINE_MATH_H_
+
+#include "engine_configuration.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+int cisnan(float f);
+
+//float getDefaultVE(int rpm);
+
+float getDefaultFuel(int rpm, float map);
+//float getTCharge(int rpm, int tps, float coolantTemp, float airTemp);
+
+float getOneDegreeTimeMs(int rpm);
+float getCrankshaftRevolutionTimeMs(int rpm);
+
+int isCrankingRT(engine_configuration_s *engineConfiguration, int rpm);
+#define isCrankingR(rpm) isCrankingRT(engineConfiguration, rpm)
+
+float fixAngle(float angle);
+float getTriggerEventAngle(int triggerEventIndex);
+
+float getEngineLoadT(engine_configuration_s *engineConfiguration);
+#define getEngineLoad() getEngineLoadT(engineConfiguration)
+
+void initializeIgnitionActions(float baseAngle, engine_configuration_s *engineConfiguration, engine_configuration2_s *engineConfiguration2);
+void addFuelEvents(engine_configuration_s const *e, engine_configuration2_s *engineConfiguration2, ActuatorEventList *list, injection_mode_e mode);
+
+float getSparkDwellMsT(engine_configuration_s *engineConfiguration, int rpm);
+#define getSparkDwellMs(rpm) getSparkDwellMsT(engineConfiguration, rpm)
+
+void registerActuatorEventExt(engine_configuration_s const *engineConfiguration, trigger_shape_s * s, ActuatorEventList *list, OutputSignal *actuator, float angleOffset);
+
+int getCylinderId(firing_order_e firingOrder, int index);
+void prepareOutputSignals(engine_configuration_s *engineConfiguration,
+ engine_configuration2_s *engineConfiguration2);
+
+void setTableBin(float array[], int size, float l, float r);
+void setFuelRpmBin(engine_configuration_s *engineConfiguration, float l, float r);
+void setFuelLoadBin(engine_configuration_s *engineConfiguration, float l, float r);
+void setTimingRpmBin(engine_configuration_s *engineConfiguration, float l, float r);
+void setTimingLoadBin(engine_configuration_s *engineConfiguration, float l, float r);
+
+void setSingleCoilDwell(engine_configuration_s *engineConfiguration);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* ENGINE_MATH_H_ */
diff --git a/firmware/controllers/algo/fuel_math.c b/firmware/controllers/algo/fuel_math.c
deleted file mode 100644
index 1ffd4f1711..0000000000
--- a/firmware/controllers/algo/fuel_math.c
+++ /dev/null
@@ -1,155 +0,0 @@
-/**
- * @file fuel_math.c
- * @brief Fuel amount calculation logic
- *
- * While engine running, fuel amount is an interpolated value from the fuel map by getRpm() and getEngineLoad()
- * On top of the value from the fuel map we also apply
- *
1) getInjectorLag() correction to account for fuel injector lag
- *
2) getCltCorrection() for warm-up
- *
3) getIatCorrection() to account for cold weather
- *
- * getCrankingFuel() depents only on getCoolantTemperature()
- *
- *
- * @date May 27, 2013
- * @author Andrey Belomutskiy, (c) 2012-2014
- *
- * This file is part of rusEfi - see http://rusefi.com
- *
- * rusEfi is free software; you can redistribute it and/or modify it under the terms of
- * the GNU General Public License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
- * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see .
- *
- */
-
-#include "main.h"
-#include "fuel_math.h"
-#include "interpolation.h"
-#include "engine_configuration.h"
-#include "allsensors.h"
-#include "engine_math.h"
-
-static float *fuel_ptrs[FUEL_LOAD_COUNT];
-static int initialized = FALSE;
-extern engine_configuration_s *engineConfiguration;
-
-/**
- * @brief Initialize fuel map data structure
- * @note this method has nothing to do with fuel map VALUES - it's job
- * is to prepare the fuel map data structure for 3d interpolation
- */
-void prepareFuelMap(void) {
- for (int k = 0; k < FUEL_LOAD_COUNT; k++)
- fuel_ptrs[k] = engineConfiguration->fuelTable[k];
- initialized = TRUE;
-}
-
-/**
- * @brief Engine warm-up fuel correction.
- */
-float getCltCorrection(float clt) {
- if (cisnan(clt))
- return 1; // this error should be already reported somewhere else, let's just handle it
- return interpolate2d(clt, engineConfiguration->cltFuelCorrBins, engineConfiguration->cltFuelCorr, CLT_CURVE_SIZE);
-}
-
-float getIatCorrection(float iat) {
- if (cisnan(iat))
- return 1; // this error should be already reported somewhere else, let's just handle it
- return interpolate2d(iat, engineConfiguration->iatFuelCorrBins, engineConfiguration->iatFuelCorr, IAT_CURVE_SIZE);
-}
-
-/**
- * @brief Injector lag correction
- * @param vBatt Battery voltage.
- * @return Time in ms for injection opening time based on current battery voltage
- */
-float getInjectorLag(float vBatt) {
- if (cisnan(vBatt)) {
- warning("vBatt=%f", vBatt);
- return 0;
- }
- float vBattCorrection = interpolate2d(vBatt, engineConfiguration->battInjectorLagCorrBins,
- engineConfiguration->battInjectorLagCorr, VBAT_INJECTOR_CURVE_SIZE);
- return engineConfiguration->injectorLag + vBattCorrection;
-}
-
-float getBaseFuel(int rpm, float engineLoad) {
- chDbgCheck(initialized, "fuel map initialized");
- return interpolate3d(engineLoad, engineConfiguration->fuelLoadBins, FUEL_LOAD_COUNT, rpm, engineConfiguration->fuelRpmBins,
- FUEL_RPM_COUNT, fuel_ptrs);
-}
-
-float getCrankingFuel(void) {
- return getStartingFuel(getCoolantTemperature());
-}
-
-int isCranking(void);
-
-/**
- * @returns Length of fuel injection, in milliseconds
- */
-float getFuelMs(int rpm) {
- if (isCranking()) {
- return getCrankingFuel();
- } else {
- float fuel = getRunningFuel(rpm, getEngineLoad());
- return fuel;
- }
-}
-
-float getRunningFuel(int rpm, float engineLoad) {
- float baseFuel = getBaseFuel(rpm, engineLoad);
-
- float iatCorrection = getIatCorrection(getIntakeAirTemperature());
- float cltCorrection = getCltCorrection(getCoolantTemperature());
- float injectorLag = getInjectorLag(getVBatt());
-
- return baseFuel * cltCorrection * iatCorrection + injectorLag;
-}
-
-float getStartingFuel(float coolantTemperature) {
- // these magic constants are in Celsius
- if (cisnan(coolantTemperature)
- || coolantTemperature
- < engineConfiguration->crankingSettings.coolantTempMinC)
- return engineConfiguration->crankingSettings.fuelAtMinTempMs;
- if (coolantTemperature
- > engineConfiguration->crankingSettings.coolantTempMaxC)
- return engineConfiguration->crankingSettings.fuelAtMaxTempMs;
- return interpolate(engineConfiguration->crankingSettings.coolantTempMinC,
- engineConfiguration->crankingSettings.fuelAtMinTempMs,
- engineConfiguration->crankingSettings.coolantTempMaxC,
- engineConfiguration->crankingSettings.fuelAtMaxTempMs,
- coolantTemperature);
-}
-
-/**
- * @return 0 for OM_DEFAULT and OM_OPENDRAIN
- */
-
-inline static int getElectricalValue0(pin_output_mode_e mode) {
- return mode == OM_INVERTED || mode == OM_OPENDRAIN_INVERTED;
-}
-
-/**
- * @return 1 for OM_DEFAULT and OM_OPENDRAIN
- */
-inline static int getElectricalValue1(pin_output_mode_e mode) {
- return mode == OM_DEFAULT || mode == OM_OPENDRAIN;
-}
-
-// todo: this method is here for unit test visibility. todo: move to a bette place!
-int getElectricalValue(int logicalValue, pin_output_mode_e mode) {
- chDbgCheck(mode <= OM_OPENDRAIN_INVERTED, "invalid pin_output_mode_e");
-
- return logicalValue ? getElectricalValue1(mode) : getElectricalValue0(mode);
-}
-
diff --git a/firmware/controllers/math/engine_math.c b/firmware/controllers/math/engine_math.c
deleted file mode 100644
index 87f838f699..0000000000
--- a/firmware/controllers/math/engine_math.c
+++ /dev/null
@@ -1,231 +0,0 @@
-/**
- * @file engine_math.c
- * @brief
- *
- * @date Jul 13, 2013
- * @author Andrey Belomutskiy, (c) 2012-2014
- *
- * This file is part of rusEfi - see http://rusefi.com
- *
- * rusEfi is free software; you can redistribute it and/or modify it under the terms of
- * the GNU General Public License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
- * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with this program.
- * If not, see .
- */
-
-//#include
-
-#include "engine_math.h"
-#include "main.h"
-#include "engine_configuration.h"
-#include "interpolation.h"
-#include "allsensors.h"
-#include "io_pins.h"
-
-/*
- * default Volumetric Efficiency
- */
-//float getDefaultVE(int rpm) {
-// if (rpm > 5000)
-// return interpolate(5000, 1.1, 8000, 1, rpm);
-// return interpolate(500, 0.5, 5000, 1.1, rpm);
-//}
-//#define K_AT_MIN_RPM_MIN_TPS 0.25
-//#define K_AT_MIN_RPM_MAX_TPS 0.25
-//#define K_AT_MAX_RPM_MIN_TPS 0.25
-//#define K_AT_MAX_RPM_MAX_TPS 0.9
-//
-//#define rpmMin 500
-//#define rpmMax 8000
-//
-//#define tpMin 0
-//#define tpMax 100
-//
-// http://rusefi.com/math/t_charge.html
-// /
-//float getTCharge(int rpm, int tps, float coolantTemp, float airTemp) {
-// float minRpmKcurrentTPS = interpolate(tpMin, K_AT_MIN_RPM_MIN_TPS, tpMax,
-// K_AT_MIN_RPM_MAX_TPS, tps);
-// float maxRpmKcurrentTPS = interpolate(tpMin, K_AT_MAX_RPM_MIN_TPS, tpMax,
-// K_AT_MAX_RPM_MAX_TPS, tps);
-//
-// float Tcharge_coff = interpolate(rpmMin, minRpmKcurrentTPS, rpmMax,
-// maxRpmKcurrentTPS, rpm);
-//
-// float Tcharge = coolantTemp * (1 - Tcharge_coff) + airTemp * Tcharge_coff;
-//
-// return Tcharge;
-//}
-#define MAX_STARTING_FUEL 15
-#define MIN_STARTING_FUEL 8
-
-/**
- * @return time needed to rotate crankshaft by one degree, in milliseconds.
- */
-float getOneDegreeTimeMs(int rpm) {
- return 1000.0 * 60 / 360 / rpm;
-}
-
-/**
- * @return time needed to rotate crankshaft by one degree, in systicks
- */
-float getOneDegreeTime(int rpm) {
- return getOneDegreeTimeMs(rpm) * TICKS_IN_MS;
-}
-
-/**
- * @return number of system it needed for one crankshaft revolution
- */
-float getCrankshaftRevolutionTime(int rpm) {
- return 360 * getOneDegreeTime(rpm);
-}
-
-/**
- * @brief Shifts angle into the [0..720) range
- * TODO: should be 'crankAngleRange' range?
- */
-float fixAngle(float angle) {
- // I guess this implementation would be faster than 'angle % 720'
- while (angle < 0)
- angle += 720;
- while (angle > 720)
- angle -= 720;
- return angle;
-}
-
-/**
- * @brief Returns engine load according to selected engine_load_mode
- *
- */
-float getEngineLoadT(engine_configuration_s *engineConfiguration) {
- switch (engineConfiguration->engineLoadMode) {
- case LM_MAF:
- return getMaf();
- case LM_MAP:
- return getMap();
- case LM_TPS:
- return getTPS();
- case LM_SPEED_DENSITY:
- // TODO: real implementation
- return getMap();
- default:
- fatal("Unexpected engine load parameter");
- return -1;
- }
-}
-
-int isCrankingRT(engine_configuration_s *engineConfiguration, int rpm) {
- return rpm > 0 && rpm < engineConfiguration->crankingSettings.crankingRpm;
-}
-
-void initializeIgnitionActions(engine_configuration_s *engineConfiguration, engine_configuration2_s *engineConfiguration2) {
- EventHandlerConfiguration *config = &engineConfiguration2->engineEventConfiguration;
- resetEventList(&config->ignitionEvents);
- chDbgCheck(engineConfiguration->cylindersCount > 0, "cylindersCount");
-
- int x = 13; //todo
-
- if (engineConfiguration->ignitionMode == IM_ONE_COIL) {
-
- for (int i = 0; i < engineConfiguration->cylindersCount; i++) {
- float angle = x + 720.0 * i / engineConfiguration->cylindersCount;
-
- registerActuatorEventExt(engineConfiguration,
- &engineConfiguration2->triggerShape,
- &config->ignitionEvents, addOutputSignal(SPARKOUT_1_OUTPUT), angle);
- }
-
- } else
- fatal("unfinished initializeIgnitionActions");
-
-}
-
-void addFuelEvents(engine_configuration_s *e, trigger_shape_s * s, EventHandlerConfiguration *config) {
- resetEventList(&config->injectionEvents);
-
- for(int i = 0;i < e->cylindersCount;i++) {
- io_pin_e pin = (io_pin_e)((int)INJECTOR_1_OUTPUT + getCylinderId(e->firingOrder, i) - 1);
- float angle = e->injectionOffset + i * 720.0 / e->cylindersCount;
- registerActuatorEventExt(e, s, &config->injectionEvents, addOutputSignal(pin), angle);
- }
-}
-
-
-/**
- * @return Spark dwell time, in milliseconds.
- */
-float getSparkDwellMsT(engine_configuration_s *engineConfiguration, int rpm) {
- if (isCrankingR(rpm)) {
- // technically this could be implemented via interpolate2d
- float angle = engineConfiguration->crankingChargeAngle;
- return getOneDegreeTimeMs(rpm) * angle;
- }
-
- if (rpm > engineConfiguration->rpmHardLimit) {
- // technically this could be implemented via interpolate2d by setting everything above rpmHardLimit to zero
- warning("skipping spark due to rpm=%d", rpm);
- return 0;
- }
-
- return interpolate2d(rpm, engineConfiguration->sparkDwellBins, engineConfiguration->sparkDwell, DWELL_CURVE_SIZE);
-}
-
-void registerActuatorEventExt(engine_configuration_s *engineConfiguration, trigger_shape_s * s, ActuatorEventList *list, OutputSignal *actuator, float angleOffset) {
- chDbgCheck(s->size > 0, "uninitialized trigger_shape_s");
-
- angleOffset = fixAngle(angleOffset + engineConfiguration->globalTriggerAngleOffset);
-
- // todo: migrate to crankAngleRange?
- float firstAngle = s->wave.switchTimes[0] * 720;
-
- // let's find the last trigger angle which is less or equal to the desired angle
- int i;
- for (i = 0; i < s->size - 1; i++) {
- // todo: we need binary search here
- float angle = s->wave.switchTimes[i + 1] * 720 - firstAngle;
- if (angle > angleOffset)
- break;
- }
- float angle = s->wave.switchTimes[i] * 720 - firstAngle;
-
- registerActuatorEvent(list, i, actuator, angleOffset - angle);
-}
-
-
-//float getTriggerEventAngle(int triggerEventIndex) {
-// return 0;
-//}
-
-/**
- * there is some BS related to isnan in MinGW, so let's have all the issues in one place
- */
-int cisnan(float f) {
- return *(((int*)(&f))) == 0x7FC00000;
-}
-
-static int order_1_THEN_3_THEN_4_THEN2[] = {1, 3, 4, 2};
-
-/**
- * @param index from zero to cylindersCount - 1
- * @return cylinderId from one to cylindersCount
- */
-int getCylinderId(firing_order_e firingOrder, int index) {
-
- switch(firingOrder) {
- case FO_ONE_CYLINDER:
- return 1;
- case FO_1_THEN_3_THEN_4_THEN2:
- return order_1_THEN_3_THEN_4_THEN2[index];
-
- default:
- firmwareError("getCylinderId not supported for %d", firingOrder);
- }
- return -1;
-}
-
diff --git a/firmware/hw_layer/lcd/lcd_2x16.c b/firmware/hw_layer/lcd/lcd_2x16.c
deleted file mode 100644
index 319511985b..0000000000
--- a/firmware/hw_layer/lcd/lcd_2x16.c
+++ /dev/null
@@ -1,212 +0,0 @@
-/**
- * @file lcd_2x16.c
- * @brief HD44780 character display driver
- *
- *
- * http://forum.chibios.org/phpbb/viewtopic.php?f=16&t=1584
- * @date 13.12.2013
- * @author shilow
- */
-
-#include "main.h"
-
-#include "lcd_2x16.h"
-#include "pin_repository.h"
-#include "string.h"
-
-#include "engine_configuration.h"
-
-extern engine_configuration_s *engineConfiguration;
-
-static Logging logger;
-
-enum {
- LCD_2X16_RESET = 0x30, LCD_2X16_4_BIT_BUS = 0x20,
-// LCD_2X16_8_BIT_BUS = 0x30,
-// LCD_2X16_LINE_ONE = 0x20,
-// LCD_2X16_LINES_TWO = 0x28,
-// LCD_2X16_FONT_5X8 = 0x20,
-// LCD_2X16_FONT_5X10 = 0x24,
-// LCD_2X16_DISPLAY_CLEAR = 0x01,
-// LCD_2X16_DISPLAY_HOME = 0x02,
-// LCD_2X16_DISPLAY_ON = 0x0C,
-// LCD_2X16_DISPLAY_RIGHT = 0x1C,
-// LCD_2X16_DISPLAY_LEFT = 0x18,
-// LCD_2X16_DISPLAY_SHIFT = 0x05,
-// LCD_2X16_CURSOR_ON = 0x0A,
-// LCD_2X16_CURSOR_BLINK = 0x09,
-// LCD_2X16_CURSOR_RIGHT = 0x14,
-// LCD_2X16_CURSOR_LEFT = 0x10,
-// LCD_2X16_SHIFT_RIGHT = 0x06,
-// LCD_2X16_SHIFT_LEFT = 0x04,
-// LCD_2X16_CGRAM_ADDR = 0x40,
- LCD_2X16_DDRAM_ADDR = 0x80,
-// LCD_2X16_BUSY_FLAG = 0x80,
-// LCD_2X16_COMMAND = 0x01,
-// LCD_2X16_DATA = 0x00,
-} lcd_2x16_command;
-
-// http://web.alfredstate.edu/weimandn/lcd/lcd_addressing/lcd_addressing_index.html
-static const int lineStart[] = { 0, 0x40, 0x14, 0x54 };
-
-static int BUSY_WAIT_DELAY = FALSE;
-static int currentRow = 0;
-
-static void lcdSleep(int period) {
- if (BUSY_WAIT_DELAY) {
- // this mode is useful for displaying messages to report OS fatal issues
-
- int ticks = 168000000 / 1000000 * period;
- int a = 0;
- for (int i = 0; i < ticks; i++)
- a += i;
- // the purpose of this code is to fool the compiler so that the loop is not optimized away
- chDbgCheck(a != 0, "true");
-
- } else {
- chThdSleepMicroseconds(period);
- }
-}
-
-static char txbuf[1];
-#define LCD_PORT_EXP_ADDR 0x20
-
-//-----------------------------------------------------------------------------
-static void lcd_HD44780_write(uint8_t data) {
- if (engineConfiguration->displayMode == DM_HD44780) {
- palWritePad(HD44780_PORT_DB7, HD44780_PIN_DB7, data & 0x80 ? 1 : 0);
- palWritePad(HD44780_PORT_DB6, HD44780_PIN_DB6, data & 0x40 ? 1 : 0);
- palWritePad(HD44780_PORT_DB5, HD44780_PIN_DB5, data & 0x20 ? 1 : 0);
- palWritePad(HD44780_PORT_DB4, HD44780_PIN_DB4, data & 0x10 ? 1 : 0);
-
- palSetPad(HD44780_PORT_E, HD44780_PIN_E); // En high
- lcdSleep(10); // enable pulse must be >450ns
- palClearPad(HD44780_PORT_E, HD44780_PIN_E); // En low
- lcdSleep(40); // commands need > 37us to settle
- } else {
-
- // LCD D4_pin -> P4
- // LCD D5_pin -> P5
- // LCD D6_pin -> P6
- // LCD D7_pin -> P7
- // LCD Pin RS -> P0
- // LCD Pin RW -> P1
- // LCD Pin E -> P2
-
- i2cAcquireBus(&I2CD1);
-
- txbuf[0] = 4;
- i2cMasterTransmit(&I2CD1, LCD_PORT_EXP_ADDR, txbuf, 1, NULL, 0);
- lcdSleep(10); // enable pulse must be >450ns
-
- txbuf[0] = 0;
- i2cMasterTransmit(&I2CD1, LCD_PORT_EXP_ADDR, txbuf, 1, NULL, 0);
-
- i2cReleaseBus(&I2CD1);
-
- }
-}
-
-//-----------------------------------------------------------------------------
-void lcd_2x16_write_command(uint8_t data) {
- palClearPad(HD44780_PORT_RS, HD44780_PIN_RS);
-
- lcd_HD44780_write(data);
- lcd_HD44780_write(data << 4);
-}
-
-//-----------------------------------------------------------------------------
-void lcd_2x16_write_data(uint8_t data) {
- palSetPad(HD44780_PORT_RS, HD44780_PIN_RS);
-
- lcd_HD44780_write(data);
- lcd_HD44780_write(data << 4);
-
- palClearPad(HD44780_PORT_RS, HD44780_PIN_RS);
-}
-
-//-----------------------------------------------------------------------------
-void lcd_HD44780_set_position(uint8_t row, uint8_t column) {
- chDbgCheck(row <= engineConfiguration->HD44780height, "invalid row");
- currentRow = row;
- lcd_2x16_write_command(LCD_2X16_DDRAM_ADDR + lineStart[row] + column);
-}
-
-void lcd_HD44780_print_char(char data) {
- if (data == '\n') {
- lcd_HD44780_set_position(++currentRow, 0);
- } else {
- lcd_2x16_write_data(data);
- }
-}
-
-void lcd_HD44780_print_string(char* string) {
- while (*string != 0x00)
- lcd_HD44780_print_char(*string++);
-}
-
-static void lcdInfo(void) {
- scheduleMsg(&logger, "HD44780 RS=%s%d E=%s%d", portname(HD44780_PORT_RS), HD44780_PIN_RS, portname(HD44780_PORT_E), HD44780_PIN_E);
- scheduleMsg(&logger, "HD44780 D4=%s%d D5=%s%d", portname(HD44780_PORT_DB4), HD44780_PIN_DB4, portname(HD44780_PORT_DB5), HD44780_PIN_DB5);
- scheduleMsg(&logger, "HD44780 D6=%s%d D7=%s%d", portname(HD44780_PORT_DB6), HD44780_PIN_DB6, portname(HD44780_PORT_DB7), HD44780_PIN_DB7);
-}
-
-void lcd_HD44780_init(void) {
- initLogging(&logger, "HD44780 driver");
-
- addConsoleAction("lcdinfo", lcdInfo);
-
-
- if (engineConfiguration->displayMode == DM_HD44780) {
- mySetPadMode("lcd RS", HD44780_PORT_RS, HD44780_PIN_RS, PAL_MODE_OUTPUT_PUSHPULL);
- mySetPadMode("lcd E", HD44780_PORT_E, HD44780_PIN_E, PAL_MODE_OUTPUT_PUSHPULL);
- mySetPadMode("lcd DB4", HD44780_PORT_DB4, HD44780_PIN_DB4, PAL_MODE_OUTPUT_PUSHPULL);
- mySetPadMode("lcd DB6", HD44780_PORT_DB5, HD44780_PIN_DB5, PAL_MODE_OUTPUT_PUSHPULL);
- mySetPadMode("lcd DB7", HD44780_PORT_DB6, HD44780_PIN_DB6, PAL_MODE_OUTPUT_PUSHPULL);
- mySetPadMode("lcd DB8", HD44780_PORT_DB7, HD44780_PIN_DB7, PAL_MODE_OUTPUT_PUSHPULL);
-
- palWritePad(HD44780_PORT_RS, HD44780_PIN_RS, 0);
- palWritePad(HD44780_PORT_E, HD44780_PIN_E, 0);
- palWritePad(HD44780_PORT_DB4, HD44780_PIN_DB4, 0);
- palWritePad(HD44780_PORT_DB5, HD44780_PIN_DB5, 0);
- palWritePad(HD44780_PORT_DB6, HD44780_PIN_DB6, 0);
- palWritePad(HD44780_PORT_DB7, HD44780_PIN_DB7, 0);
- }
-
- // LCD needs some time to wake up
- chThdSleepMilliseconds(50);
-
- lcd_HD44780_write(LCD_2X16_RESET);
- chThdSleepMilliseconds(1);
-
- lcd_HD44780_write(0x30);
-
- lcd_HD44780_write(LCD_2X16_4_BIT_BUS); // 4 bit, 2 line
- chThdSleepMicroseconds(40);
-
- lcd_HD44780_write(LCD_2X16_4_BIT_BUS); // 4 bit, 2 line
- lcd_HD44780_write(0x80);
- chThdSleepMicroseconds(40);
-
- lcd_HD44780_write(0x00); // display and cursor control
- lcd_HD44780_write(0xC0);
- chThdSleepMicroseconds(40);
-
- lcd_HD44780_write(0x00); // display clear
- lcd_HD44780_write(0x01);
- chThdSleepMilliseconds(2);
-
- lcd_HD44780_write(0x00); // entry mode set
- lcd_HD44780_write(0x60);
-
- lcd_HD44780_set_position(0, 0);
- lcd_HD44780_print_string("rusefi here\n");
- lcd_HD44780_print_string(__DATE__);
-}
-
-void lcdShowFatalMessage(char *message) {
- BUSY_WAIT_DELAY = TRUE;
- lcd_HD44780_set_position(0, 0);
- lcd_HD44780_print_string("fatal\n");
- lcd_HD44780_print_string(message);
-}
diff --git a/firmware/hw_layer/lcd/lcd_2x16.h b/firmware/hw_layer/lcd/lcd_2x16.h
deleted file mode 100644
index 6a49f621a2..0000000000
--- a/firmware/hw_layer/lcd/lcd_2x16.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * @file lcd_2x16.h
- * @date 13.12.2013
- */
-
-#ifndef LCD_2X16_H_
-#define LCD_2X16_H_
-
-extern void lcd_HD44780_init(void);
-extern void lcd_HD44780_set_position(uint8_t row, uint8_t column);
-extern void lcd_HD44780_print_char(char data);
-extern void lcd_HD44780_print_string(char *string);
-extern const uint8_t lcd_2x16_decode[];
-
-void lcdShowFatalMessage(char *message);
-
-#endif /* LCD_2X16_H_ */
diff --git a/wave_chart.c b/wave_chart.c
new file mode 100644
index 0000000000..8dc9a69efc
--- /dev/null
+++ b/wave_chart.c
@@ -0,0 +1,146 @@
+/**
+ * @file wave_chart.c
+ * @brief Dev console wave sniffer logic
+ *
+ * Here we have our own build-in logic analyzer. The data we aggregate here is sent to the
+ * java UI Dev Console so that it can be displayed nicely in the Sniffer tab.
+ *
+ * Both external events (see wave_analyzer.c) and internal (see signal executors) are supported
+ *
+ * @date Jun 23, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ *
+ * This file is part of rusEfi - see http://rusefi.com
+ *
+ * rusEfi is free software; you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ */
+
+#include "wave_chart.h"
+#include "main.h"
+
+#if EFI_WAVE_CHART
+
+#include "eficonsole.h"
+#include "status_loop.h"
+
+
+#define CHART_DELIMETER "!"
+
+/**
+ * This is the number of events in the digital chart which would be displayed
+ * on the 'digital sniffer' pane
+ */
+#if EFI_PROD_CODE
+static volatile int chartSize = 100;
+#else
+// need more events for automated test
+static volatile int chartSize = 200;
+#endif
+
+static int isChartActive = TRUE;
+//static int isChartActive = FALSE;
+
+//#define DEBUG_WAVE 1
+
+#if DEBUG_WAVE
+static Logging debugLogging;
+#endif
+
+static Logging logger;
+
+void resetWaveChart(WaveChart *chart) {
+#if DEBUG_WAVE
+ scheduleSimpleMsg(&debugLogging, "reset while at ", chart->counter);
+#endif
+ resetLogging(&chart->logging);
+ chart->counter = 0;
+ appendPrintf(&chart->logging, "wave_chart%s", DELIMETER);
+}
+
+static char LOGGING_BUFFER[5000] __attribute__((section(".ccm")));
+
+static void printStatus(void) {
+ scheduleIntValue(&logger, "chart", isChartActive);
+ scheduleIntValue(&logger, "chartsize", chartSize);
+}
+
+static void setChartActive(int value) {
+ isChartActive = value;
+ printStatus();
+}
+
+void setChartSize(int newSize) {
+ if (newSize < 5)
+ return;
+ chartSize = newSize;
+ printStatus();
+}
+
+void publishChartIfFull(WaveChart *chart) {
+ if (isWaveChartFull(chart)) {
+ publishChart(chart);
+ resetWaveChart(chart);
+ }
+}
+
+int isWaveChartFull(WaveChart *chart) {
+ return chart->counter >= chartSize;
+}
+
+void publishChart(WaveChart *chart) {
+ appendPrintf(&chart->logging, DELIMETER);
+#if DEBUG_WAVE
+ Logging *l = &chart->logging;
+ scheduleSimpleMsg(&debugLogging, "IT'S TIME", strlen(l->buffer));
+#endif
+ if (isChartActive && getFullLog())
+ scheduleLogging(&chart->logging);
+}
+
+/**
+ * @brief Register a change in sniffed signal
+ */
+void addWaveChartEvent3(WaveChart *chart, char *name, char * msg, char * msg2) {
+ chDbgCheck(chart->isInitialized, "chart not initialized");
+#if DEBUG_WAVE
+ scheduleSimpleMsg(&debugLogging, "current", chart->counter);
+#endif
+ if (isWaveChartFull(chart))
+ return;
+ lockOutputBuffer(); // we have multiple threads writing to the same output buffer
+ appendPrintf(&chart->logging, "%s%s%s%s", name, CHART_DELIMETER, msg, CHART_DELIMETER);
+ int time100 = getTimeNowUs() / 10;
+ appendPrintf(&chart->logging, "%d%s%s", time100, msg2, CHART_DELIMETER);
+ chart->counter++;
+ unlockOutputBuffer();
+}
+
+void initWaveChart(WaveChart *chart) {
+ initLogging(&logger, "wave info");
+
+ if (!isChartActive)
+ printMsg(&logger, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! chart disabled");
+
+ printStatus();
+
+ initLoggingExt(&chart->logging, "wave chart", LOGGING_BUFFER, sizeof(LOGGING_BUFFER));
+ chart->isInitialized = TRUE;
+#if DEBUG_WAVE
+ initLoggingExt(&debugLogging, "wave chart debug", &debugLogging.DEFAULT_BUFFER, sizeof(debugLogging.DEFAULT_BUFFER));
+#endif
+
+ resetWaveChart(chart);
+ addConsoleActionI("chartsize", setChartSize);
+ addConsoleActionI("chart", setChartActive);
+}
+
+#endif /* EFI_WAVE_CHART */