diff --git a/.github/workflows/pr-memory-deltas-generate.yml b/.github/workflows/pr-memory-deltas-generate.yml index 60ad1abc..c81345fb 100644 --- a/.github/workflows/pr-memory-deltas-generate.yml +++ b/.github/workflows/pr-memory-deltas-generate.yml @@ -28,6 +28,7 @@ jobs: - build.extra_flags=-DPLATFORMIO -DUSE_LIBDIVIDE -O3 -ffast-math -fshort-enums -funroll-loops -Wall -Wextra -std=c99 lib_deps: | - name: Time + - name: SimplyAtomic - fqbn: teensy:avr:teensy35 artifact-name-suffix: teensy-avr-teensy35 platform: | @@ -38,6 +39,7 @@ jobs: - build..extra_flags="-Wall" lib_deps: | - name: SDfat + - name: SimplyAtomic - fqbn: teensy:avr:teensy41 artifact-name-suffix: teensy-avr-teensy41 platform: | @@ -48,6 +50,7 @@ jobs: - build..extra_flags="-Wall" lib_deps: | - name: SDfat + - name: SimplyAtomic - fqbn: STMicroelectronics:stm32:GenF4 artifact-name-suffix: stm32-avr-stm32f4 platform: | @@ -61,6 +64,7 @@ jobs: - name: "STM32duino RTC" version: 1.2.0 - name: SdFat + - name: SimplyAtomic steps: - name: Checkout repository diff --git a/platformio.ini b/platformio.ini index 8e4ff1b0..ee377df1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,7 +16,7 @@ build_unflags = -Os build_flags = -DUSE_LIBDIVIDE -O3 -ffast-math -fshort-enums -funroll-loops -Wall -Wextra -std=c99 ; Note that fp64lib is only used by unit tests. It isn't referenced by the firmware & will be ; ignored. -lib_deps = EEPROM, Time, fp64lib +lib_deps = EEPROM, Time, fp64lib, simplyatomic ;test_build_project_src = true test_build_src = yes debug_tool = simavr @@ -41,7 +41,7 @@ board=ATmega2561 platform=https://github.com/platformio/platform-teensy.git board=teensy35 framework=arduino -lib_deps = EEPROM, FlexCAN_T4, Time +lib_deps = EEPROM, FlexCAN_T4, Time, SimplyAtomic test_build_src = yes test_ignore = test_table3d_native extra_scripts = post:post_extra_script.py @@ -51,7 +51,7 @@ extra_scripts = post:post_extra_script.py platform=https://github.com/platformio/platform-teensy.git board=teensy36 framework=arduino -lib_deps = EEPROM, FlexCAN_T4, Time +lib_deps = EEPROM, FlexCAN_T4, Time, SimplyAtomic test_build_src = yes test_ignore = test_table3d_native @@ -60,7 +60,7 @@ test_ignore = test_table3d_native platform=https://github.com/platformio/platform-teensy.git board=teensy41 framework=arduino -lib_deps = EEPROM, FlexCAN_T4, Time +lib_deps = EEPROM, FlexCAN_T4, Time, SimplyAtomic test_build_src = yes test_ignore = test_table3d_native @@ -72,7 +72,7 @@ framework = arduino ;board = genericSTM32F407VET6 board = black_f407ve ; RTC library fixed to 1.2.0, because in newer than that the RTC fails to keep up time. At least up to 1.3.7 version -lib_deps = stm32duino/STM32duino RTC @ 1.2.0, greiman/SdFat +lib_deps = stm32duino/STM32duino RTC @ 1.2.0, greiman/SdFat, SimplyAtomic board_build.core = stm32 build_flags = -DUSE_LIBDIVIDE -std=gnu++11 -UBOARD_MAX_IO_PINS -DENABLE_HWSERIAL2 -DENABLE_HWSERIAL3 -DUSBCON -DHAL_PCD_MODULE_ENABLED -DUSBD_USE_CDC -DHAL_CAN_MODULE_ENABLED -DSERIAL_TX_BUFFER_SIZE=128 -DSERIAL_RX_BUFFER_SIZE=128 upload_protocol = dfu @@ -84,7 +84,7 @@ monitor_speed = 115200 platform = ststm32 framework = arduino board = blackpill_f401cc -lib_deps = stm32duino/STM32duino RTC @ 1.2.0 +lib_deps = stm32duino/STM32duino RTC @ 1.2.0, SimplyAtomic board_build.core = stm32 build_flags = -DUSE_LIBDIVIDE -std=gnu++11 -UBOARD_MAX_IO_PINS -DUSBCON -DHAL_PCD_MODULE_ENABLED -DUSBD_USE_CDC -DHAL_DAC_MODULE_DISABLED -DHAL_ETH_MODULE_DISABLED -DHAL_SD_MODULE_DISABLED -DHAL_QSPI_MODULE_DISABLED upload_protocol = dfu @@ -96,7 +96,7 @@ monitor_speed = 115200 platform = ststm32 framework = arduino board = blackpill_f411ce -lib_deps = stm32duino/STM32duino RTC @ 1.2.0 +lib_deps = stm32duino/STM32duino RTC @ 1.2.0, SimplyAtomic board_build.core = stm32 build_flags = -DUSE_LIBDIVIDE -O3 -std=gnu++11 -UBOARD_MAX_IO_PINS upload_protocol = dfu @@ -108,7 +108,7 @@ monitor_speed = 115200 platform = ststm32 framework = arduino board = blackpill_f411ce -lib_deps = stm32duino/STM32duino RTC @ 1.2.0 +lib_deps = stm32duino/STM32duino RTC @ 1.2.0, SimplyAtomic board_build.core = stm32 build_flags = -DUSE_LIBDIVIDE -O3 -std=gnu++11 -UBOARD_MAX_IO_PINS -DUSBCON -DHAL_PCD_MODULE_ENABLED -DUSBD_USE_CDC upload_protocol = dfu @@ -120,7 +120,7 @@ platform = ststm32 framework = arduino ; framework-arduinoststm32 board = bluepill_f103c8_128k -lib_deps = EEPROM, stm32duino/STM32duino RTC @ 1.2.0 +lib_deps = EEPROM, stm32duino/STM32duino RTC @ 1.2.0, SimplyAtomic ;build_flags = -DUSE_LIBDIVIDE -fpermissive -std=gnu++11 -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,-Map,output.map build_flags = -DUSE_LIBDIVIDE -fpermissive -std=gnu++11 -Os -DCORE_STM32_OFFICIAL -UBOARD_MAX_IO_PINS diff --git a/speeduino/auxiliaries.h b/speeduino/auxiliaries.h index 93fc120c..be5cdfa8 100644 --- a/speeduino/auxiliaries.h +++ b/speeduino/auxiliaries.h @@ -3,9 +3,7 @@ #include BOARD_H //Note that this is not a real file, it is defined in globals.h. -#if defined(CORE_AVR) -#include -#endif +#include void initialiseAuxPWM(void); void boostControl(void); @@ -44,28 +42,22 @@ void wmiControl(void); #define FUEL_PUMP_ON() (digitalWrite(pinFuelPump, HIGH)) #define FUEL_PUMP_OFF() (digitalWrite(pinFuelPump, LOW)) -#define AIRCON_ON() { (((configPage15.airConCompPol==1)) ? AIRCON_PIN_LOW() : AIRCON_PIN_HIGH()); BIT_SET(currentStatus.airConStatus, BIT_AIRCON_COMPRESSOR); } -#define AIRCON_OFF() { (((configPage15.airConCompPol==1)) ? AIRCON_PIN_HIGH() : AIRCON_PIN_LOW()); BIT_CLEAR(currentStatus.airConStatus, BIT_AIRCON_COMPRESSOR); } -#define AIRCON_FAN_ON() { (((configPage15.airConFanPol==1)) ? AIRCON_FAN_PIN_LOW() : AIRCON_FAN_PIN_HIGH()); BIT_SET(currentStatus.airConStatus, BIT_AIRCON_FAN); } -#define AIRCON_FAN_OFF() { (((configPage15.airConFanPol==1)) ? AIRCON_FAN_PIN_HIGH() : AIRCON_FAN_PIN_LOW()); BIT_CLEAR(currentStatus.airConStatus, BIT_AIRCON_FAN); } - -#define FAN_ON() { ((configPage6.fanInv) ? FAN_PIN_LOW() : FAN_PIN_HIGH()); } -#define FAN_OFF() { ((configPage6.fanInv) ? FAN_PIN_HIGH() : FAN_PIN_LOW()); } #else -#define BOOST_PIN_LOW() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *boost_pin_port &= ~(boost_pin_mask); } -#define BOOST_PIN_HIGH() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *boost_pin_port |= (boost_pin_mask); } -#define VVT1_PIN_LOW() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *vvt1_pin_port &= ~(vvt1_pin_mask); } -#define VVT1_PIN_HIGH() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *vvt1_pin_port |= (vvt1_pin_mask); } -#define VVT2_PIN_LOW() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *vvt2_pin_port &= ~(vvt2_pin_mask); } -#define VVT2_PIN_HIGH() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *vvt2_pin_port |= (vvt2_pin_mask); } -#define N2O_STAGE1_PIN_LOW() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *n2o_stage1_pin_port &= ~(n2o_stage1_pin_mask); } -#define N2O_STAGE1_PIN_HIGH() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *n2o_stage1_pin_port |= (n2o_stage1_pin_mask); } -#define N2O_STAGE2_PIN_LOW() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *n2o_stage2_pin_port &= ~(n2o_stage2_pin_mask); } -#define N2O_STAGE2_PIN_HIGH() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *n2o_stage2_pin_port |= (n2o_stage2_pin_mask); } -#define FUEL_PUMP_ON() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *pump_pin_port |= (pump_pin_mask); } -#define FUEL_PUMP_OFF() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *pump_pin_port &= ~(pump_pin_mask); } -//Note the below macros cannot use ATOMIC_BLOCK(ATOMIC_RESTORESTATE) as they are called from within ternary operators. The ATOMIC_BLOCK wrapped is instead placed around the ternary call below +#define BOOST_PIN_LOW() ATOMIC() { *boost_pin_port &= ~(boost_pin_mask); } +#define BOOST_PIN_HIGH() ATOMIC() { *boost_pin_port |= (boost_pin_mask); } +#define VVT1_PIN_LOW() ATOMIC() { *vvt1_pin_port &= ~(vvt1_pin_mask); } +#define VVT1_PIN_HIGH() ATOMIC() { *vvt1_pin_port |= (vvt1_pin_mask); } +#define VVT2_PIN_LOW() ATOMIC() { *vvt2_pin_port &= ~(vvt2_pin_mask); } +#define VVT2_PIN_HIGH() ATOMIC() { *vvt2_pin_port |= (vvt2_pin_mask); } +#define N2O_STAGE1_PIN_LOW() ATOMIC() { *n2o_stage1_pin_port &= ~(n2o_stage1_pin_mask); } +#define N2O_STAGE1_PIN_HIGH() ATOMIC() { *n2o_stage1_pin_port |= (n2o_stage1_pin_mask); } +#define N2O_STAGE2_PIN_LOW() ATOMIC() { *n2o_stage2_pin_port &= ~(n2o_stage2_pin_mask); } +#define N2O_STAGE2_PIN_HIGH() ATOMIC() { *n2o_stage2_pin_port |= (n2o_stage2_pin_mask); } +#define FUEL_PUMP_ON() ATOMIC() { *pump_pin_port |= (pump_pin_mask); } +#define FUEL_PUMP_OFF() ATOMIC() { *pump_pin_port &= ~(pump_pin_mask); } + +//Note the below macros cannot use ATOMIC() as they are called from within ternary operators. The ATOMIC is instead placed around the ternary call below #define FAN_PIN_LOW() *fan_pin_port &= ~(fan_pin_mask) #define FAN_PIN_HIGH() *fan_pin_port |= (fan_pin_mask) #define AIRCON_PIN_LOW() *aircon_comp_pin_port &= ~(aircon_comp_pin_mask) @@ -73,17 +65,17 @@ void wmiControl(void); #define AIRCON_FAN_PIN_LOW() *aircon_fan_pin_port &= ~(aircon_fan_pin_mask) #define AIRCON_FAN_PIN_HIGH() *aircon_fan_pin_port |= (aircon_fan_pin_mask) -#define AIRCON_ON() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ((((configPage15.airConCompPol)==1)) ? AIRCON_PIN_LOW() : AIRCON_PIN_HIGH()); BIT_SET(currentStatus.airConStatus, BIT_AIRCON_COMPRESSOR); } -#define AIRCON_OFF() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ((((configPage15.airConCompPol)==1)) ? AIRCON_PIN_HIGH() : AIRCON_PIN_LOW()); BIT_CLEAR(currentStatus.airConStatus, BIT_AIRCON_COMPRESSOR); } -#define AIRCON_FAN_ON() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ((((configPage15.airConFanPol)==1)) ? AIRCON_FAN_PIN_LOW() : AIRCON_FAN_PIN_HIGH()); BIT_SET(currentStatus.airConStatus, BIT_AIRCON_FAN); } -#define AIRCON_FAN_OFF() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ((((configPage15.airConFanPol)==1)) ? AIRCON_FAN_PIN_HIGH() : AIRCON_FAN_PIN_LOW()); BIT_CLEAR(currentStatus.airConStatus, BIT_AIRCON_FAN); } - -#define FAN_ON() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ((configPage6.fanInv) ? FAN_PIN_LOW() : FAN_PIN_HIGH()); } -#define FAN_OFF() ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ((configPage6.fanInv) ? FAN_PIN_HIGH() : FAN_PIN_LOW()); } #endif -#define READ_N2O_ARM_PIN() ((*n2o_arming_pin_port & n2o_arming_pin_mask) ? true : false) +#define AIRCON_ON() ATOMIC() { ((((configPage15.airConCompPol)==1)) ? AIRCON_PIN_LOW() : AIRCON_PIN_HIGH()); BIT_SET(currentStatus.airConStatus, BIT_AIRCON_COMPRESSOR); } +#define AIRCON_OFF() ATOMIC() { ((((configPage15.airConCompPol)==1)) ? AIRCON_PIN_HIGH() : AIRCON_PIN_LOW()); BIT_CLEAR(currentStatus.airConStatus, BIT_AIRCON_COMPRESSOR); } +#define AIRCON_FAN_ON() ATOMIC() { ((((configPage15.airConFanPol)==1)) ? AIRCON_FAN_PIN_LOW() : AIRCON_FAN_PIN_HIGH()); BIT_SET(currentStatus.airConStatus, BIT_AIRCON_FAN); } +#define AIRCON_FAN_OFF() ATOMIC() { ((((configPage15.airConFanPol)==1)) ? AIRCON_FAN_PIN_HIGH() : AIRCON_FAN_PIN_LOW()); BIT_CLEAR(currentStatus.airConStatus, BIT_AIRCON_FAN); } +#define FAN_ON() ATOMIC() { ((configPage6.fanInv) ? FAN_PIN_LOW() : FAN_PIN_HIGH()); } +#define FAN_OFF() ATOMIC() { ((configPage6.fanInv) ? FAN_PIN_HIGH() : FAN_PIN_LOW()); } + +#define READ_N2O_ARM_PIN() ((*n2o_arming_pin_port & n2o_arming_pin_mask) ? true : false) #define VVT1_PIN_ON() VVT1_PIN_HIGH(); #define VVT1_PIN_OFF() VVT1_PIN_LOW(); diff --git a/speeduino/comms.cpp b/speeduino/comms.cpp index 9ece4276..7aaa14b4 100644 --- a/speeduino/comms.cpp +++ b/speeduino/comms.cpp @@ -14,7 +14,6 @@ A full copy of the license may be found in the projects root directory #include "utilities.h" #include "decoders.h" #include "TS_CommandButtonHandler.h" -#include "errors.h" #include "pages.h" #include "page_crc.h" #include "logger.h" diff --git a/speeduino/comms_legacy.cpp b/speeduino/comms_legacy.cpp index 871197aa..14cc7df9 100644 --- a/speeduino/comms_legacy.cpp +++ b/speeduino/comms_legacy.cpp @@ -15,7 +15,6 @@ A full copy of the license may be found in the projects root directory #include "utilities.h" #include "decoders.h" #include "TS_CommandButtonHandler.h" -#include "errors.h" #include "pages.h" #include "page_crc.h" #include "logger.h" diff --git a/speeduino/comms_secondary.cpp b/speeduino/comms_secondary.cpp index 4cfc15d5..61d60355 100644 --- a/speeduino/comms_secondary.cpp +++ b/speeduino/comms_secondary.cpp @@ -20,7 +20,6 @@ sendcancommand is called when a command is to be sent either to serial3 #include "comms_secondary.h" #include "comms_CAN.h" #include "maths.h" -#include "errors.h" #include "utilities.h" #include "comms_legacy.h" #include "logger.h" diff --git a/speeduino/corrections.cpp b/speeduino/corrections.cpp index c9b16745..f3048dc6 100644 --- a/speeduino/corrections.cpp +++ b/speeduino/corrections.cpp @@ -268,8 +268,8 @@ uint16_t correctionAccel(void) if(configPage2.aeMode == AE_MODE_MAP) { //Get the MAP rate change - MAP_change = (currentStatus.MAP - MAPlast); - currentStatus.mapDOT = (MICROS_PER_SEC / (MAP_time - MAPlast_time)) * MAP_change; //This is the % per second that the MAP has moved + MAP_change = getMAPDelta(); + currentStatus.mapDOT = (MICROS_PER_SEC / getMAPDeltaTime()) * MAP_change; //This is the % per second that the MAP has moved //currentStatus.mapDOT = 15 * MAP_change; //This is the kpa per second that the MAP has moved } else if(configPage2.aeMode == AE_MODE_TPS) diff --git a/speeduino/decoders.cpp b/speeduino/decoders.cpp index e8cbd418..5b4b4ad5 100644 --- a/speeduino/decoders.cpp +++ b/speeduino/decoders.cpp @@ -35,6 +35,7 @@ A full copy of the license may be found in the projects root directory * - To compare Speeduino Doxyfile to default config, do: `doxygen -g Doxyfile.default ; diff Doxyfile.default Doxyfile` */ #include +#include #include "globals.h" #include "decoders.h" #include "scheduledIO.h" @@ -42,6 +43,7 @@ A full copy of the license may be found in the projects root directory #include "crankMaths.h" #include "timers.h" #include "schedule_calcs.h" +#include "unit_testing.h" void nullTriggerHandler (void){return;} //initialisation function for triggerhandlers, does exactly nothing uint16_t nullGetRPM(void){return 0;} //initialisation function for getRpm, returns safe value of 0 @@ -66,12 +68,12 @@ volatile unsigned long curGap3; volatile unsigned long lastGap; volatile unsigned long targetGap; -unsigned long MAX_STALL_TIME = MICROS_PER_SEC/2U; //The maximum time (in uS) that the system will continue to function before the engine is considered stalled/stopped. This is unique to each decoder, depending on the number of teeth etc. 500000 (half a second) is used as the default value, most decoders will be much less. +TESTABLE_STATIC unsigned long MAX_STALL_TIME = MICROS_PER_SEC/2U; //The maximum time (in uS) that the system will continue to function before the engine is considered stalled/stopped. This is unique to each decoder, depending on the number of teeth etc. 500000 (half a second) is used as the default value, most decoders will be much less. volatile uint16_t toothCurrentCount = 0; //The current number of teeth (Once sync has been achieved, this can never actually be 0 -volatile byte toothSystemCount = 0; //Used for decoders such as Audi 135 where not every tooth is used for calculating crank angle. This variable stores the actual number of teeth, not the number being used to calculate crank angle +static volatile byte toothSystemCount = 0; //Used for decoders such as Audi 135 where not every tooth is used for calculating crank angle. This variable stores the actual number of teeth, not the number being used to calculate crank angle volatile unsigned long toothSystemLastToothTime = 0; //As below, but used for decoders where not every tooth count is used for calculation -volatile unsigned long toothLastToothTime = 0; //The time (micros()) that the last tooth was registered -volatile unsigned long toothLastSecToothTime = 0; //The time (micros()) that the last tooth was registered on the secondary input +TESTABLE_STATIC volatile unsigned long toothLastToothTime = 0; //The time (micros()) that the last tooth was registered +static volatile unsigned long toothLastSecToothTime = 0; //The time (micros()) that the last tooth was registered on the secondary input volatile unsigned long toothLastThirdToothTime = 0; //The time (micros()) that the last tooth was registered on the second cam input volatile unsigned long toothLastMinusOneToothTime = 0; //The time (micros()) that the tooth before the last tooth was registered volatile unsigned long toothLastMinusOneSecToothTime = 0; //The time (micros()) that the tooth before the last tooth was registered on secondary input @@ -84,7 +86,7 @@ volatile unsigned long toothOneMinusOneTime = 0; //The 2nd to last time (micros( volatile bool revolutionOne = 0; // For sequential operation, this tracks whether the current revolution is 1 or 2 (not 1) volatile bool revolutionLastOne = 0; // used to identify in the rover pattern which has a non unique primary trigger something unique - has the secondary tooth changed. -volatile unsigned int secondaryToothCount; //Used for identifying the current secondary (Usually cam) tooth for patterns with multiple secondary teeth +static volatile unsigned int secondaryToothCount; //Used for identifying the current secondary (Usually cam) tooth for patterns with multiple secondary teeth volatile unsigned int secondaryLastToothCount = 0; // used to identify in the rover pattern which has a non unique primary trigger something unique - has the secondary tooth changed. volatile unsigned long secondaryLastToothTime = 0; //The time (micros()) that the last tooth was registered (Cam input) volatile unsigned long secondaryLastToothTime1 = 0; //The time (micros()) that the last tooth was registered (Cam input) @@ -335,6 +337,23 @@ static inline bool IsCranking(const statuses &status) { return (status.RPM < status.crankRPM) && (status.startRevolutions == 0U); } +bool engineIsRunning(uint32_t curTime) { + // Check how long ago the last tooth was seen compared to now. + // If it was more than MAX_STALL_TIME then the engine is probably stopped. + // toothLastToothTime can be greater than curTime if a pulse occurs between getting the latest time and doing the comparison + ATOMIC() { + return (toothLastToothTime > curTime) || ((curTime - toothLastToothTime) < MAX_STALL_TIME); + } + return false; // Just here to avoid compiler warning. +} + +void resetDecoder(void) { + toothLastSecToothTime = 0; + toothLastToothTime = 0; + toothSystemCount = 0; + secondaryToothCount = 0; +} + #if defined(UNIT_TEST) bool SetRevolutionTime(uint32_t revTime) #else @@ -739,7 +758,7 @@ static inline void triggerRecordVVT1Angle (void) curAngle -= configPage4.triggerAngle; //Value at TDC if( configPage6.vvtMode == VVT_MODE_CLOSED_LOOP ) { curAngle -= configPage10.vvtCL0DutyAng; } - currentStatus.vvt1Angle = ANGLE_FILTER( (curAngle << 1), configPage4.ANGLEFILTER_VVT, currentStatus.vvt1Angle); + currentStatus.vvt1Angle = LOW_PASS_FILTER( (curAngle << 1), configPage4.ANGLEFILTER_VVT, currentStatus.vvt1Angle); } } @@ -770,7 +789,7 @@ void triggerThird_missingTooth(void) curAngle -= configPage4.triggerAngle; //Value at TDC if( configPage6.vvtMode == VVT_MODE_CLOSED_LOOP ) { curAngle -= configPage4.vvt2CL0DutyAng; } //currentStatus.vvt2Angle = int8_t (curAngle); //vvt1Angle is only int8, but +/-127 degrees is enough for VVT control - currentStatus.vvt2Angle = ANGLE_FILTER( (curAngle << 1), configPage4.ANGLEFILTER_VVT, currentStatus.vvt2Angle); + currentStatus.vvt2Angle = LOW_PASS_FILTER( (curAngle << 1), configPage4.ANGLEFILTER_VVT, currentStatus.vvt2Angle); toothLastThirdToothTime = curTime3; } //Trigger filter @@ -2655,7 +2674,7 @@ int getCamAngle_Miata9905(void) //lastVVTtime is the time between tooth #1 (10* BTDC) and the single cam tooth. //All cam angles in in BTDC, so the actual advance angle is 370 - timeToAngleDegPerMicroSec(lastVVTtime) - curAngle = 370 - timeToAngleDegPerMicroSec(lastVVTtime) - configPage10.vvtCL0DutyAng; - currentStatus.vvt1Angle = ANGLE_FILTER( (curAngle << 1), configPage4.ANGLEFILTER_VVT, currentStatus.vvt1Angle); + currentStatus.vvt1Angle = LOW_PASS_FILTER( (curAngle << 1), configPage4.ANGLEFILTER_VVT, currentStatus.vvt1Angle); return currentStatus.vvt1Angle; } @@ -4400,7 +4419,7 @@ void triggerSec_FordST170(void) while(curAngle > 360) { curAngle -= 360; } if( configPage6.vvtMode == VVT_MODE_CLOSED_LOOP ) { - curAngle = ANGLE_FILTER( (curAngle << 1), configPage4.ANGLEFILTER_VVT, curAngle); + curAngle = LOW_PASS_FILTER( (curAngle << 1), configPage4.ANGLEFILTER_VVT, curAngle); currentStatus.vvt1Angle = 360 - curAngle - configPage10.vvtCL0DutyAng; } } diff --git a/speeduino/decoders.h b/speeduino/decoders.h index ddb6dcfa..fa296faa 100644 --- a/speeduino/decoders.h +++ b/speeduino/decoders.h @@ -58,6 +58,17 @@ //220 bytes free extern volatile uint8_t decoderState; +/** + * @brief Is the engine running? + * + * This is based on whether or not the decoder has detected a tooth recently + * + * @param curTime The time in µS to use for the liveness check. Typically the result of a recent call to micros() + * @return true If the engine is turning + * @return false If the engine is not turning + */ +bool engineIsRunning(uint32_t curTime); + /* extern volatile bool validTrigger; //Is set true when the last trigger (Primary or secondary) was valid (ie passed filters) extern volatile bool triggerToothAngleIsCorrect; //Whether or not the triggerToothAngle variable is currently accurate. Some patterns have times when the triggerToothAngle variable cannot be accurately set. @@ -67,9 +78,6 @@ extern bool decoderHasSecondary; //Whether or not the pattern uses a secondary i extern bool decoderHasFixedCrankingTiming; */ -//This isn't to to filter out wrong pulses on triggers, but just to smooth out the cam angle reading for better closed loop VVT control. -#define ANGLE_FILTER(input, alpha, prior) (((long)(input) * (256 - (alpha)) + ((long)(prior) * (alpha)))) >> 8 - void loggerPrimaryISR(void); void loggerSecondaryISR(void); void loggerTertiaryISR(void); @@ -262,7 +270,12 @@ uint16_t getRPM_SuzukiK6A(void); int getCrankAngle_SuzukiK6A(void); void triggerSetEndTeeth_SuzukiK6A(void); - +/** + * @brief This function is called when the engine is stopped, or when the engine is started. It resets the decoder state and the tooth tracking variables + * + * @return void + */ +void resetDecoder(void); extern void (*triggerHandler)(void); //Pointer for the trigger function (Gets pointed to the relevant decoder) extern void (*triggerSecondaryHandler)(void); //Pointer for the secondary trigger function (Gets pointed to the relevant decoder) @@ -279,12 +292,8 @@ extern volatile unsigned long curGap2; extern volatile unsigned long lastGap; extern volatile unsigned long targetGap; -extern unsigned long MAX_STALL_TIME; //The maximum time (in uS) that the system will continue to function before the engine is considered stalled/stopped. This is unique to each decoder, depending on the number of teeth etc. 500000 (half a second) is used as the default value, most decoders will be much less. extern volatile uint16_t toothCurrentCount; //The current number of teeth (Once sync has been achieved, this can never actually be 0 -extern volatile byte toothSystemCount; //Used for decoders such as Audi 135 where not every tooth is used for calculating crank angle. This variable stores the actual number of teeth, not the number being used to calculate crank angle extern volatile unsigned long toothSystemLastToothTime; //As below, but used for decoders where not every tooth count is used for calculation -extern volatile unsigned long toothLastToothTime; //The time (micros()) that the last tooth was registered -extern volatile unsigned long toothLastSecToothTime; //The time (micros()) that the last tooth was registered on the secondary input extern volatile unsigned long toothLastThirdToothTime; //The time (micros()) that the last tooth was registered on the second cam input extern volatile unsigned long toothLastMinusOneToothTime; //The time (micros()) that the tooth before the last tooth was registered extern volatile unsigned long toothLastMinusOneSecToothTime; //The time (micros()) that the tooth before the last tooth was registered on secondary input @@ -294,7 +303,6 @@ extern volatile unsigned long toothOneTime; //The time (micros()) that tooth 1 l extern volatile unsigned long toothOneMinusOneTime; //The 2nd to last time (micros()) that tooth 1 last triggered extern volatile bool revolutionOne; // For sequential operation, this tracks whether the current revolution is 1 or 2 (not 1) -extern volatile unsigned int secondaryToothCount; //Used for identifying the current secondary (Usually cam) tooth for patterns with multiple secondary teeth extern volatile unsigned long secondaryLastToothTime; //The time (micros()) that the last tooth was registered (Cam input) extern volatile unsigned long secondaryLastToothTime1; //The time (micros()) that the last tooth was registered (Cam input) diff --git a/speeduino/errors.cpp b/speeduino/errors.cpp deleted file mode 100644 index b6cbcbf0..00000000 --- a/speeduino/errors.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* -Speeduino - Simple engine management for the Arduino Mega 2560 platform -Copyright (C) Josh Stewart -A full copy of the license may be found in the projects root directory -*/ - -/* - * Sets the next available error - * Returns the error number or 0 if there is no more room for errors - */ -#include "globals.h" -#include "errors.h" - -byte errorCount = 0; -byte errorCodes[4]; - -byte setError(byte errorID) -{ - if(errorCount < MAX_ERRORS) - { - errorCodes[errorCount] = errorID; - errorCount++; - if(errorCount == 1) { BIT_SET(currentStatus.status2, BIT_STATUS2_ERROR); } //Enable the error indicator - } - return errorCount; -} - -void clearError(byte errorID) -{ - byte clearedError = 255; - - if (errorID == errorCodes[0]) { clearedError = 0; } - else if(errorID == errorCodes[1]) { clearedError = 1; } - else if(errorID == errorCodes[2]) { clearedError = 2; } - else if(errorID == errorCodes[3]) { clearedError = 3; } - - if(clearedError < MAX_ERRORS) - { - errorCodes[clearedError] = ERR_NONE; - //Clear the required error and move any from above it 'down' in the error array - for (byte x=clearedError; x < (errorCount-1); x++) - { - errorCodes[x] = errorCodes[x+1]; - errorCodes[x+1] = ERR_NONE; - } - - errorCount--; - if(errorCount == 0) { BIT_CLEAR(currentStatus.status2, BIT_STATUS2_ERROR); } //Enable the error indicator - } -} - -byte getNextError(void) -{ - packedError currentError; - byte currentErrorNum = 0; - - if(errorCount > 0) - { - //We alternate through the errors once per second - currentErrorNum = currentStatus.secl % errorCount; //Which error number will be returned. This changes once per second. - - currentError.errorNum = currentErrorNum; - currentError.errorID = errorCodes[currentErrorNum]; - } - else - { - currentError.errorNum = 0; - currentError.errorID = 0; - } - - - return *(byte*)¤tError; //Ugly, but this forces the cast of the currentError struct to a byte. -} diff --git a/speeduino/errors.h b/speeduino/errors.h deleted file mode 100644 index 64d9cbd9..00000000 --- a/speeduino/errors.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef ERRORS_H -#define ERRORS_H - -/* - * Up to 64 different error codes may be defined (6 bits) - */ -#define ERR_NONE 0 //No error -#define ERR_UNKNOWN 1 //Unknown error -#define ERR_IAT_SHORT 2 //Inlet sensor shorted -#define ERR_IAT_GND 3 //Inlet sensor grounded -#define ERR_CLT_SHORT 4 //Coolant sensor shorted -#define ERR_CLT_GND 5 //Coolant Sensor grounded -#define ERR_O2_SHORT 6 //O2 sensor shorted -#define ERR_O2_GND 7 //O2 sensor grounded -#define ERR_TPS_SHORT 8 //TPS shorted (Is potentially valid) -#define ERR_TPS_GND 9 //TPS grounded (Is potentially valid) -#define ERR_BAT_HIGH 10 //Battery voltage is too high -#define ERR_BAT_LOW 11 //Battery voltage is too low -#define ERR_MAP_HIGH 12 //MAP output is too high -#define ERR_MAP_LOW 13 //MAP output is too low - -#define ERR_DEFAULT_IAT_SHORT 80 //Note that the default is 40C. 80 is used due to the -40 offset -#define ERR_DEFAULT_IAT_GND 80 //Note that the default is 40C. 80 is used due to the -40 offset -#define ERR_DEFAULT_CKT_SHORT 80 //Note that the default is 40C. 80 is used due to the -40 offset -#define ERR_DEFAULT_CLT_GND 80 //Note that the default is 40C. 80 is used due to the -40 offset -#define ERR_DEFAULT_O2_SHORT 147 //14.7 -#define ERR_DEFAULT_O2_GND 147 //14.7 -#define ERR_DEFAULT_TPS_SHORT 50 //50% -#define ERR_DEFAULT_TPS_GND 50 //50% -#define ERR_DEFAULT_BAT_HIGH 130 //13v -#define ERR_DEFAULT_BAT_LOW 130 //13v -#define ERR_DEFAULT_MAP_HIGH 240 -#define ERR_DEFAULT_MAP_LOW 80 - - -#define MAX_ERRORS 4 //The number of errors the system can hold simultaneously. Should be a power of 2 - -/* - * This struct is a single byte in length and is sent to TS - * The first 2 bits are used to define the current error (0-3) - * The remaining 6 bits are used to give the error number - */ -struct packedError -{ - byte errorNum : 2; - byte errorID : 6; -}; - -byte getNextError(void); -byte setError(byte errorID); -void clearError(byte errorID); - -extern byte errorCount; - -#endif diff --git a/speeduino/globals.cpp b/speeduino/globals.cpp index 1df07e77..0d192a32 100644 --- a/speeduino/globals.cpp +++ b/speeduino/globals.cpp @@ -107,7 +107,6 @@ volatile PORT_TYPE *triggerThird_pin_port; volatile PINMASK_TYPE triggerThird_pin_mask; //These are variables used across multiple files -bool initialisationComplete = false; ///< Tracks whether the setup() function has run completely (true = has run) byte fpPrimeTime = 0; ///< The time (in seconds, based on @ref statuses.secl) that the fuel pump started priming uint8_t softLimitTime = 0; //The time (in 0.1 seconds, based on seclx10) that the soft limiter started volatile uint16_t mainLoopCount; //Main loop counter (incremented at each main loop rev., used for maintaining currentStatus.loopsPerSecond) @@ -119,8 +118,6 @@ bool clutchTrigger; bool previousClutchTrigger; volatile uint32_t toothHistory[TOOTH_LOG_SIZE]; ///< Tooth trigger history - delta time (in uS) from last tooth (Indexed by @ref toothHistoryIndex) volatile uint8_t compositeLogHistory[TOOTH_LOG_SIZE]; -volatile bool fpPrimed = false; ///< Tracks whether or not the fuel pump priming has been completed yet -volatile bool injPrimed = false; ///< Tracks whether or not the injectors priming has been completed yet volatile unsigned int toothHistoryIndex = 0; ///< Current index to @ref toothHistory array unsigned long currentLoopTime; /**< The time (in uS) that the current mainloop started */ volatile uint16_t ignitionCount; /**< The count of ignition events that have taken place since the engine started */ diff --git a/speeduino/globals.h b/speeduino/globals.h index 2badb260..ecb737e2 100644 --- a/speeduino/globals.h +++ b/speeduino/globals.h @@ -25,9 +25,9 @@ #ifndef GLOBALS_H #define GLOBALS_H #include +#include #include "table2d.h" #include "table3d.h" -#include #include "src/FastCRC/FastCRC.h" #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) @@ -205,9 +205,6 @@ #define BIT_AIRCON_FAN 6 //Indicates whether the A/C fan is running #define BIT_AIRCON_UNUSED8 7 -#define VALID_MAP_MAX 1022 //The largest ADC value that is valid for the MAP sensor -#define VALID_MAP_MIN 2 //The smallest ADC value that is valid for the MAP sensor - #ifndef UNIT_TEST #define TOOTH_LOG_SIZE 127U #else @@ -304,9 +301,9 @@ static_assert(TOOTH_LOG_SIZE 120) long longRPM; ///< RPM as long int (gets assigned to / maintained in statuses.RPM as well) - uint16_t mapADC; - int baroADC; + uint16_t baroADC; long MAP; ///< Manifold absolute pressure. Has to be a long for PID calcs (Boost control) int16_t EMAP; ///< EMAP ... (See @ref config6.useEMAP for EMAP enablement) - uint16_t EMAPADC; - byte baro; ///< Barometric pressure is simply the initial MAP reading, taken before the engine is running. Alternatively, can be taken from an external sensor - byte TPS; /**< The current TPS reading (0% - 100%). Is the tpsADC value after the calibration is applied */ - byte tpsADC; /**< byte (valued: 0-255) representation of the TPS. Downsampled from the original 10-bit (0-1023) reading, but before any calibration is applied */ + uint8_t baro; ///< Barometric pressure is simply the initial MAP reading, taken before the engine is running. Alternatively, can be taken from an external sensor + uint8_t TPS; /**< The current TPS reading (0% - 100%). Is the tpsADC value after the calibration is applied */ + uint8_t tpsADC; /**< byte (valued: 0-255) representation of the TPS. Downsampled from the original 10-bit (0-1023) reading, but before any calibration is applied */ int16_t tpsDOT; /**< TPS delta over time. Measures the % per second that the TPS is changing. Note that is signed value, because TPSdot can be also negative */ byte TPSlast; /**< The previous TPS reading */ int16_t mapDOT; /**< MAP delta over time. Measures the kpa per second that the MAP is changing. Note that is signed value, because MAPdot can be also negative */ @@ -604,15 +599,14 @@ struct statuses { byte VE; /**< The current VE value being used in the fuel calculation. Can be the same as VE1 or VE2, or a calculated value of both. */ byte VE1; /**< The VE value from fuel table 1 */ byte VE2; /**< The VE value from fuel table 2, if in use (and required conditions are met) */ - byte O2; /**< Primary O2 sensor reading */ - byte O2_2; /**< Secondary O2 sensor reading */ + uint8_t O2; /**< Primary O2 sensor reading */ + uint8_t O2_2; /**< Secondary O2 sensor reading */ int coolant; /**< Coolant temperature reading */ - int cltADC; + uint16_t cltADC; int IAT; /**< Inlet air temperature reading */ - int iatADC; - int batADC; - int O2ADC; - int O2_2ADC; + uint16_t iatADC; + uint16_t O2ADC; + uint16_t O2_2ADC; uint16_t dwell; ///< dwell (coil primary winding/circuit on) time (in ms * 10 ? See @ref correctionsDwell) volatile uint16_t actualDwell; ///< actual dwell time if new ignition mode is used (in uS) byte dwellCorrection; /**< The amount of correction being applied to the dwell time (in unit ...). */ @@ -704,10 +698,28 @@ struct statuses { byte airConStatus; }; -static inline bool HasAnySync(const statuses &status) { +/** + * @brief Non-atomic version of HasAnySync. **Should only be called in an ATOMIC() block*** + * + */ +static inline bool HasAnySyncUnsafe(const statuses &status) { return status.hasSync || BIT_CHECK(status.status3, BIT_STATUS3_HALFSYNC); } +static inline bool HasAnySync(const statuses &status) { + ATOMIC() { + return HasAnySyncUnsafe(status); + } + return false; // Just here to avoid compiler warning. +} + +enum MAPSamplingMethod { + MAPSamplingInstantaneous = 0, + MAPSamplingCycleAverage = 1, + MAPSamplingCycleMinimum = 2, + MAPSamplingIgnitionEventAverage= 3, +}; + /** Page 2 of the config - mostly variables that are required for fuel. * These are "non-live" EFI setting, engine and "system" variables that remain fixed once sent * (and stored to e.g. EEPROM) from configuration/tuning SW (from outside by USBserial/bluetooth). @@ -754,7 +766,7 @@ struct config2 { uint16_t injAng[4]; //config1 in ini - byte mapSample : 2; ///< MAP sampling method (0=Instantaneous, 1=Cycle Average, 2=Cycle Minimum, 4=Ign. event average, See sensors.ino) + MAPSamplingMethod mapSample : 2; ///< MAP sampling method (0=Instantaneous, 1=Cycle Average, 2=Cycle Minimum, 4=Ign. event average, See sensors.ino) byte strokes : 1; ///< Engine cycle type: four-stroke (0) / two-stroke (1) byte injType : 1; ///< Injector type 0=Port (INJ_TYPE_PORT), 1=Throttle Body / TBI (INJ_TYPE_TBODY) byte nCylinders : 4; ///< Number of cylinders diff --git a/speeduino/idle.h b/speeduino/idle.h index d850c872..4e9a9d3d 100644 --- a/speeduino/idle.h +++ b/speeduino/idle.h @@ -5,14 +5,14 @@ #include "table2d.h" #include BOARD_H //Note that this is not a real file, it is defined in globals.h. -#define IAC_ALGORITHM_NONE 0 -#define IAC_ALGORITHM_ONOFF 1 -#define IAC_ALGORITHM_PWM_OL 2 -#define IAC_ALGORITHM_PWM_CL 3 -#define IAC_ALGORITHM_STEP_OL 4 -#define IAC_ALGORITHM_STEP_CL 5 -#define IAC_ALGORITHM_PWM_OLCL 6 //Openloop plus closedloop IAC control -#define IAC_ALGORITHM_STEP_OLCL 7 //Openloop plus closedloop IAC control +#define IAC_ALGORITHM_NONE 0U +#define IAC_ALGORITHM_ONOFF 1U +#define IAC_ALGORITHM_PWM_OL 2U +#define IAC_ALGORITHM_PWM_CL 3U +#define IAC_ALGORITHM_STEP_OL 4U +#define IAC_ALGORITHM_STEP_CL 5U +#define IAC_ALGORITHM_PWM_OLCL 6U //Openloop plus closedloop IAC control +#define IAC_ALGORITHM_STEP_OLCL 7U //Openloop plus closedloop IAC control #define IDLE_PIN_LOW() *idle_pin_port &= ~(idle_pin_mask) #define IDLE_PIN_HIGH() *idle_pin_port |= (idle_pin_mask) diff --git a/speeduino/init.cpp b/speeduino/init.cpp index 61db680d..a2326df5 100644 --- a/speeduino/init.cpp +++ b/speeduino/init.cpp @@ -236,6 +236,7 @@ void initialiseAll(void) initialiseCorrections(); BIT_CLEAR(currentStatus.engineProtectStatus, PROTECT_IO_ERROR); //Clear the I/O error bit. The bit will be set in initialiseADC() if there is problem in there. initialiseADC(); + initialiseMAPBaro(); initialiseProgrammableIO(); //Check whether the flex sensor is enabled and if so, attach an interrupt for it @@ -309,11 +310,7 @@ void initialiseAll(void) fixedCrankingOverride = 0; timer5_overflow_count = 0; toothHistoryIndex = 0; - toothLastToothTime = 0; - - //Lookup the current MAP reading for barometric pressure - instanteneousMAPReading(); - readBaro(); + resetDecoder(); noInterrupts(); initialiseTriggers(); diff --git a/speeduino/logger.cpp b/speeduino/logger.cpp index acf5644d..912f405d 100644 --- a/speeduino/logger.cpp +++ b/speeduino/logger.cpp @@ -1,6 +1,5 @@ #include "globals.h" #include "logger.h" -#include "errors.h" #include "decoders.h" #include "init.h" #include "maths.h" @@ -120,7 +119,7 @@ byte getTSLogEntry(uint16_t byteNum) case 73: statusValue = highByte(currentStatus.canin[15]); break; case 74: statusValue = currentStatus.tpsADC; break; - case 75: statusValue = getNextError(); break; + case 75: statusValue = 0U /*getNextError()*/; break; case 76: statusValue = lowByte(currentStatus.PW1); break; //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. case 77: statusValue = highByte(currentStatus.PW1); break; //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. @@ -258,7 +257,7 @@ int16_t getReadableLogEntry(uint16_t logIndex) case 50: statusValue = currentStatus.canin[15]; break; case 51: statusValue = currentStatus.tpsADC; break; - case 52: statusValue = getNextError(); break; + case 52: statusValue = 0U /*getNextError()*/; break; case 53: statusValue = currentStatus.PW1; break; //Pulsewidth 1 multiplied by 10 in ms. Have to convert from uS to mS. case 54: statusValue = currentStatus.PW2; break; //Pulsewidth 2 multiplied by 10 in ms. Have to convert from uS to mS. @@ -425,7 +424,7 @@ uint8_t getLegacySecondarySerialLogEntry(uint16_t byteNum) case 72: statusValue = highByte(currentStatus.canin[15]); break; case 73: statusValue = currentStatus.tpsADC; break; - case 74: statusValue = getNextError(); break; // errorNum (0:1), currentError(2:7) + case 74: statusValue = 0U /*getNextError()*/; break; // errorNum (0:1), currentError(2:7) case 75: statusValue = currentStatus.launchCorrection; break; case 76: statusValue = lowByte(currentStatus.PW2); break; //Pulsewidth 2 multiplied by 10 in ms. Have to convert from uS to mS. diff --git a/speeduino/maths.h b/speeduino/maths.h index 112bf5fd..3ba87332 100644 --- a/speeduino/maths.h +++ b/speeduino/maths.h @@ -14,7 +14,7 @@ #include "src/libdivide/constant_fast_div.h" #endif -extern uint8_t random1to100(void); +uint8_t random1to100(void); /** * @defgroup group-rounded-div Rounding integer division @@ -215,12 +215,6 @@ static inline int16_t nudge(int16_t min, int16_t max, int16_t value, int16_t nud return value; } -//This is a dedicated function that specifically handles the case of mapping 0-1023 values into a 0 to X range -//This is a common case because it means converting from a standard 10-bit analog input to a byte or 10-bit analog into 0-511 (Eg the temperature readings) -#define fastMap1023toX(x, out_max) ( rshift<10>((uint32_t)(x) * (out_max)) ) -//This is a new version that allows for out_min -#define fastMap10Bit(x, out_min, out_max) ( rshift<10>( (uint32_t)(x) * ((out_max)-(out_min)) ) + (out_min)) - #if defined(CORE_AVR) || defined(ARDUINO_ARCH_AVR) static inline bool udiv_is16bit_result(uint32_t dividend, uint16_t divisor) { @@ -300,4 +294,55 @@ static inline uint16_t udiv_32_16_closest(uint32_t dividend, uint16_t divisor) #endif } +/** + * @brief clamps a given value between the minimum and maximum thresholds. + * + * Uses operator< to compare the values. + * + * @tparam T Any type that supports operator< + * @param v The value to clamp + * @param lo The minimum threshold + * @param hi The maximum threshold + * @return if v compares less than lo, returns lo; otherwise if hi compares less than v, returns hi; otherwise returns v. + */ +template +constexpr const T& clamp(const T& v, const T& lo, const T& hi){ + return v +static inline T LOW_PASS_FILTER_8BIT(T input, uint8_t alpha, T prior) { + // Intermediate steps are for MISRA compliance + // Equivalent to: (input * (256 - alpha) + (prior * alpha)) >> 8 + static constexpr T ALPHA_MAX = (T)256; + T inv_alpha = ALPHA_MAX - (T)alpha; + TPrime prior_alpha = (prior * (TPrime)alpha); + TPrime preshift = (input * (TPrime)inv_alpha) + prior_alpha; + return (T)(preshift / ALPHA_MAX); // Division should resolve to a shift & avoids a MISRA violation +} + +/// @endcond + +/** + * @brief Simple low pass IIR filter 16-bit values + * + * This is effectively implementing the smooth filter from playground.arduino.cc/Main/Smooth + * But removes the use of floats and uses 8 bits of fixed precision. + * + * @param input incoming unfiltered value + * @param alpha filter factor. 0=off, 255=full smoothing (0.00 to 0.99 in float, 0-99%) + * @param prior previous *filtered* value. + * @return uint16_t The filtered input + */ +static inline uint16_t LOW_PASS_FILTER(uint16_t input, uint8_t alpha, uint16_t prior) { + return LOW_PASS_FILTER_8BIT(input, alpha, prior); +} + +/** @brief Simple low pass IIR filter for S16 values */ +static inline int16_t LOW_PASS_FILTER(int16_t input, uint8_t alpha, int16_t prior) { + return LOW_PASS_FILTER_8BIT(input, alpha, prior); +} + #endif \ No newline at end of file diff --git a/speeduino/scheduler.h b/speeduino/scheduler.h index d83b41cf..5c2f2d98 100644 --- a/speeduino/scheduler.h +++ b/speeduino/scheduler.h @@ -47,7 +47,7 @@ See page 136 of the processors datasheet: http://www.atmel.com/Images/doc2549.pd #define IGNITION_REFRESH_THRESHOLD 30 //Time in uS that the refresh functions will check to ensure there is enough time before changing the end compare #define DWELL_AVERAGE_ALPHA 30 -#define DWELL_AVERAGE(input) (((long)input * (256 - DWELL_AVERAGE_ALPHA) + ((long)currentStatus.actualDwell * DWELL_AVERAGE_ALPHA))) >> 8 +#define DWELL_AVERAGE(input) LOW_PASS_FILTER((input), DWELL_AVERAGE_ALPHA, currentStatus.actualDwell) //#define DWELL_AVERAGE(input) (currentStatus.dwell) //Can be use to disable the above for testing void initialiseSchedulers(void); diff --git a/speeduino/sensors.cpp b/speeduino/sensors.cpp index d5335679..941d94cf 100644 --- a/speeduino/sensors.cpp +++ b/speeduino/sensors.cpp @@ -6,6 +6,7 @@ A full copy of the license may be found in the projects root directory /** @file * Read sensors with appropriate timing / scheduling. */ +#include #include "sensors.h" #include "crankMaths.h" #include "globals.h" @@ -13,38 +14,74 @@ A full copy of the license may be found in the projects root directory #include "storage.h" #include "comms.h" #include "idle.h" -#include "errors.h" #include "corrections.h" #include "pages.h" #include "decoders.h" #include "auxiliaries.h" #include "utilities.h" -#include BOARD_H +#include "unit_testing.h" + +/** + * @brief A specialist function to map a value in the range [0, 1023] (I.e. 10-bit) to a different range. + * + * Mostly used for analog input voltage level to real world value conversions. + * + * @details + * analogRead returns a number in the range [0, 1023], representing the pin input + * voltage from min to max (typically 0V - 5V) + * We need to convert that value to the real world value the sensor is reading (pressure, temperature etc.) + * If: + * * rangeMin is the real world value when the sensor is reading 0V + * * rangeMax is the real world measurement when the sensor is reading 5V + * * There is a linear relationship between voltage output and the real world value. + * + * then this function will return the real world measurement (kPa, °C etc) + * + * @param value Value to map (should be in range [0, 1023]) + * @param rangeMin Minimum of the output range + * @param rangeMax Maximum of the output range + * @return int16_t + */ +TESTABLE_INLINE_STATIC int16_t fastMap10Bit(uint16_t value, int16_t rangeMin, int16_t rangeMax) { + uint16_t range = rangeMax-rangeMin; // Must be positive (assuming rangeMax>=rangeMin) + uint16_t fromStartOfRange = (uint16_t)rshift<10>((uint32_t)value * range); + return rangeMin + (int16_t)fromStartOfRange; +} + +// +static inline uint16_t readAnalogPin(uint8_t pin) { + // Why do we read twice? Who knows..... + analogRead(pin); + // According to the docs, analogRead result should be in range 0-1023 + // Clip the result to zero minimum to prevent rollover just in case + int tmp = analogRead(pin); + // max is a macro on some platforms - DO NOT place the call to analogRead as an inline parameter: + // (you might end up calling it twice) + return max(0, tmp); +} -uint32_t MAPcurRev; //Tracks which revolution we're sampling on -unsigned int MAPcount; //Number of samples taken in the current MAP cycle -unsigned long MAPrunningValue; //Used for tracking either the total of all MAP readings in this cycle (Event average) or the lowest value detected in this cycle (event minimum) -unsigned long EMAPrunningValue; //As above but for EMAP bool auxIsEnabled; -uint16_t MAPlast; /**< The previous MAP reading */ -unsigned long MAP_time; //The time the MAP sample was taken -unsigned long MAPlast_time; //The time the previous MAP sample was taken -volatile unsigned long vssTimes[VSS_SAMPLES] = {0}; -volatile byte vssIndex; -volatile byte flexCounter = 0; -volatile unsigned long flexStartTime; -volatile unsigned long flexPulseWidth; +static volatile uint32_t vssTimes[VSS_SAMPLES] = {0}; +static volatile uint8_t vssIndex = 0U; -//These variables are used for tracking the number of running sensors values that appear to be errors. Once a threshold is reached, the sensor reading will go to default value and assume the sensor is faulty -byte mapErrorCount = 0; -//byte iatErrorCount = 0; Not used -//byte cltErrorCount = 0; Not used - -static inline void validateMAP(void); +volatile uint8_t flexCounter = 0U; +static volatile uint32_t flexStartTime = 0UL; +volatile uint32_t flexPulseWidth = 0U; #if defined(ANALOG_ISR) static volatile uint16_t AnChannel[16]; +static inline uint16_t readAnalogSensor(uint8_t pin) { + return AnChannel[pin-A0]; +} +static inline uint16_t readMAPSensor(uint8_t pin) { +#if defined(ANALOG_ISR_MAP) + return AnChannel[pin-A0]; +#else + return readAnalogPin(pin); +#endif +} +#define ADMUX_DEFAULT_CONFIG 0x40 //AVCC reference, ADC0 input, right adjusted, ADC enabled ISR(ADC_vect) { @@ -54,17 +91,17 @@ ISR(ADC_vect) byte result_high = ADCH; #if defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) - if (nChannel == 7) { ADMUX = 0x40; } + if (nChannel == 7U) { ADMUX = 0x40; } #elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) if( BIT_CHECK(ADCSRB, MUX5) ) { nChannel += 8; } //8 to 15 - if(nChannel == 15) + if(nChannel == 15U) { ADMUX = ADMUX_DEFAULT_CONFIG; //channel 0 ADCSRB = 0x00; //clear MUX5 bit BIT_CLEAR(ADCSRA,ADIE); //Disable interrupt as we're at the end of a full ADC cycle. This will be re-enabled in the main loop } - else if (nChannel == 7) //channel 7 + else if (nChannel == 7U) //channel 7 { ADMUX = ADMUX_DEFAULT_CONFIG; ADCSRB = 0x08; //Set MUX5 bit @@ -73,9 +110,16 @@ ISR(ADC_vect) else { ADMUX++; } //ADMUX always appears to be one ahead of the actual channel value that is in ADCL/ADCH. Subtract 1 from it to get the correct channel number - if(nChannel == 0) { nChannel = 16;} + if(nChannel == 0U) { nChannel = 16;} AnChannel[nChannel-1] = (result_high << 8) | result_low; } +#else +static inline uint16_t readAnalogSensor(uint8_t pin) { + return readAnalogPin(pin); +} +static inline uint16_t readMAPSensor(uint8_t pin) { + return readAnalogPin(pin); +} #endif /** Init all ADC conversions by setting resolutions, etc. @@ -120,26 +164,23 @@ void initialiseADC(void) #elif defined(ARDUINO_ARCH_STM32) //STM32GENERIC core and ST STM32duino core, change analog read to 12 bit analogReadResolution(10); //use 10bits for analog reading on STM32 boards #endif - MAPcurRev = 0; - MAPcount = 0; - MAPrunningValue = 0; //The following checks the aux inputs and initialises pins if required auxIsEnabled = false; - for (byte AuxinChan = 0; AuxinChan <16 ; AuxinChan++) + for (uint8_t AuxinChan = 0U; AuxinChan <16U ; AuxinChan++) { currentStatus.current_caninchannel = AuxinChan; - if (((configPage9.caninput_sel[currentStatus.current_caninchannel]&12) == 4) - && ((configPage9.enable_secondarySerial == 1) || ((configPage9.enable_intcan == 1) && (configPage9.intcan_available == 1)))) + if (((configPage9.caninput_sel[currentStatus.current_caninchannel]&12U) == 4U) + && ((configPage9.enable_secondarySerial == 1U) || ((configPage9.enable_intcan == 1U) && (configPage9.intcan_available == 1U)))) { //if current input channel is enabled as external input in caninput_selxb(bits 2:3) and secondary serial or internal canbus is enabled(and is mcu supported) //currentStatus.canin[14] = 22; Dev test use only! auxIsEnabled = true; } - else if ((((configPage9.enable_secondarySerial == 1) || ((configPage9.enable_intcan == 1) && (configPage9.intcan_available == 1))) && (configPage9.caninput_sel[currentStatus.current_caninchannel]&12) == 8) - || (((configPage9.enable_secondarySerial == 0) && ( (configPage9.enable_intcan == 1) && (configPage9.intcan_available == 0) )) && (configPage9.caninput_sel[currentStatus.current_caninchannel]&3) == 2) - || (((configPage9.enable_secondarySerial == 0) && (configPage9.enable_intcan == 0)) && ((configPage9.caninput_sel[currentStatus.current_caninchannel]&3) == 2))) + else if ((((configPage9.enable_secondarySerial == 1U) || ((configPage9.enable_intcan == 1U) && (configPage9.intcan_available == 1U))) && (configPage9.caninput_sel[currentStatus.current_caninchannel]&12U) == 8U) + || (((configPage9.enable_secondarySerial == 0U) && ( (configPage9.enable_intcan == 1U) && (configPage9.intcan_available == 0U) )) && (configPage9.caninput_sel[currentStatus.current_caninchannel]&3U) == 2U) + || (((configPage9.enable_secondarySerial == 0U) && (configPage9.enable_intcan == 0U)) && ((configPage9.caninput_sel[currentStatus.current_caninchannel]&3U) == 2U))) { //if current input channel is enabled as analog local pin check caninput_selxb(bits 2:3) with &12 and caninput_selxa(bits 0:1) with &3 - byte pinNumber = pinTranslateAnalog(configPage9.Auxinpina[currentStatus.current_caninchannel]&63); + uint8_t pinNumber = pinTranslateAnalog(configPage9.Auxinpina[currentStatus.current_caninchannel]&63U); if( pinIsUsed(pinNumber) ) { //Do nothing here as the pin is already in use. @@ -153,11 +194,11 @@ void initialiseADC(void) auxIsEnabled = true; } } - else if ((((configPage9.enable_secondarySerial == 1) || ((configPage9.enable_intcan == 1) && (configPage9.intcan_available == 1))) && (configPage9.caninput_sel[currentStatus.current_caninchannel]&12) == 12) - || (((configPage9.enable_secondarySerial == 0) && ( (configPage9.enable_intcan == 1) && (configPage9.intcan_available == 0) )) && (configPage9.caninput_sel[currentStatus.current_caninchannel]&3) == 3) - || (((configPage9.enable_secondarySerial == 0) && (configPage9.enable_intcan == 0)) && ((configPage9.caninput_sel[currentStatus.current_caninchannel]&3) == 3))) + else if ((((configPage9.enable_secondarySerial == 1U) || ((configPage9.enable_intcan == 1U) && (configPage9.intcan_available == 1U))) && (configPage9.caninput_sel[currentStatus.current_caninchannel]&12U) == 12U) + || (((configPage9.enable_secondarySerial == 0U) && ( (configPage9.enable_intcan == 1U) && (configPage9.intcan_available == 0U) )) && (configPage9.caninput_sel[currentStatus.current_caninchannel]&3U) == 3U) + || (((configPage9.enable_secondarySerial == 0U) && (configPage9.enable_intcan == 0U)) && ((configPage9.caninput_sel[currentStatus.current_caninchannel]&3U) == 3U))) { //if current input channel is enabled as digital local pin check caninput_selxb(bits 2:3) with &12 and caninput_selxa(bits 0:1) with &3 - byte pinNumber = (configPage9.Auxinpinb[currentStatus.current_caninchannel]&63) + 1; + uint8_t pinNumber = (configPage9.Auxinpinb[currentStatus.current_caninchannel]&63U) + 1U; if( pinIsUsed(pinNumber) ) { //Do nothing here as the pin is already in use. @@ -170,7 +211,9 @@ void initialiseADC(void) //currentStatus.canin[14] = 44; Dev test use only! auxIsEnabled = true; } - + } + else { + // Do nothing. Keep MISRA checker happy } } //For loop iterating through aux in lines @@ -178,311 +221,344 @@ void initialiseADC(void) //Sanity checks to ensure none of the filter values are set above 240 (Which would include the 255 value which is the default on a new arduino) //If an invalid value is detected, it's reset to the default the value and burned to EEPROM. //Each sensor has it's own default value - if(configPage4.ADCFILTER_TPS > 240) { configPage4.ADCFILTER_TPS = ADCFILTER_TPS_DEFAULT; writeConfig(ignSetPage); } - if(configPage4.ADCFILTER_CLT > 240) { configPage4.ADCFILTER_CLT = ADCFILTER_CLT_DEFAULT; writeConfig(ignSetPage); } - if(configPage4.ADCFILTER_IAT > 240) { configPage4.ADCFILTER_IAT = ADCFILTER_IAT_DEFAULT; writeConfig(ignSetPage); } - if(configPage4.ADCFILTER_O2 > 240) { configPage4.ADCFILTER_O2 = ADCFILTER_O2_DEFAULT; writeConfig(ignSetPage); } - if(configPage4.ADCFILTER_BAT > 240) { configPage4.ADCFILTER_BAT = ADCFILTER_BAT_DEFAULT; writeConfig(ignSetPage); } - if(configPage4.ADCFILTER_MAP > 240) { configPage4.ADCFILTER_MAP = ADCFILTER_MAP_DEFAULT; writeConfig(ignSetPage); } - if(configPage4.ADCFILTER_BARO > 240) { configPage4.ADCFILTER_BARO = ADCFILTER_BARO_DEFAULT; writeConfig(ignSetPage); } - if(configPage4.FILTER_FLEX > 240) { configPage4.FILTER_FLEX = FILTER_FLEX_DEFAULT; writeConfig(ignSetPage); } + if(configPage4.ADCFILTER_TPS > 240U) { configPage4.ADCFILTER_TPS = ADCFILTER_TPS_DEFAULT; writeConfig(ignSetPage); } + if(configPage4.ADCFILTER_CLT > 240U) { configPage4.ADCFILTER_CLT = ADCFILTER_CLT_DEFAULT; writeConfig(ignSetPage); } + if(configPage4.ADCFILTER_IAT > 240U) { configPage4.ADCFILTER_IAT = ADCFILTER_IAT_DEFAULT; writeConfig(ignSetPage); } + if(configPage4.ADCFILTER_O2 > 240U) { configPage4.ADCFILTER_O2 = ADCFILTER_O2_DEFAULT; writeConfig(ignSetPage); } + if(configPage4.ADCFILTER_BAT > 240U) { configPage4.ADCFILTER_BAT = ADCFILTER_BAT_DEFAULT; writeConfig(ignSetPage); } + if(configPage4.ADCFILTER_MAP > 240U) { configPage4.ADCFILTER_MAP = ADCFILTER_MAP_DEFAULT; writeConfig(ignSetPage); } + if(configPage4.ADCFILTER_BARO > 240U) { configPage4.ADCFILTER_BARO = ADCFILTER_BARO_DEFAULT; writeConfig(ignSetPage); } + if(configPage4.FILTER_FLEX > 240U) { configPage4.FILTER_FLEX = FILTER_FLEX_DEFAULT; writeConfig(ignSetPage); } flexStartTime = micros(); vssIndex = 0; } -static inline void validateMAP(void) + +// ========================================== MAP ========================================== + +static constexpr uint16_t VALID_MAP_MAX=1022U; //The largest ADC value that is valid for the MAP sensor +static constexpr uint16_t VALID_MAP_MIN=2U; //The smallest ADC value that is valid for the MAP sensor + +#include "sensors_map_structs.h" + +TESTABLE_INLINE_STATIC bool instanteneousMAPReading(void) { - //Error checks - if(currentStatus.MAP < VALID_MAP_MIN) + // All we need to do it signal that the new readings should be used as-is + return true; +} + +static inline bool cycleAverageMAPReadingAccumulate(map_cycle_average_t &cycle_average, const map_adc_readings_t &sensorReadings) { + ++cycle_average.sampleCount; + cycle_average.mapAdcRunningTotal += sensorReadings.mapADC; + cycle_average.emapAdcRunningTotal += sensorReadings.emapADC; + + // We are *not* ready to derive new maps readings yet + return false; +} + +static inline void reset(const statuses ¤t, map_cycle_average_t &cycle_average, const map_adc_readings_t &sensorReadings) { + cycle_average.sampleCount = 0U; + cycle_average.mapAdcRunningTotal = 0U; + cycle_average.emapAdcRunningTotal = 0U; + cycle_average.cycleStartIndex = (uint8_t)current.startRevolutions; + (void)cycleAverageMAPReadingAccumulate(cycle_average, sensorReadings); +} + +static inline bool cycleAverageEndCycle(const statuses ¤t, map_cycle_average_t &cycle_average, map_adc_readings_t &sensorReadings) { + if( (cycle_average.mapAdcRunningTotal != 0UL) && (cycle_average.sampleCount != 0U) ) { - currentStatus.MAP = ERR_DEFAULT_MAP_LOW; - mapErrorCount += 1; - setError(ERR_MAP_LOW); - } - else if(currentStatus.MAP > VALID_MAP_MAX) - { - currentStatus.MAP = ERR_DEFAULT_MAP_HIGH; - mapErrorCount += 1; - setError(ERR_MAP_HIGH); - } - else - { - if(errorCount > 0) + // Record this since we're about to overwrite it.... + map_adc_readings_t rawReadings = sensorReadings; + + sensorReadings.mapADC = udiv_32_16(cycle_average.mapAdcRunningTotal, cycle_average.sampleCount); + + //If EMAP is enabled, the process is identical to the above + if(sensorReadings.emapADC!=UINT16_MAX) { - clearError(ERR_MAP_HIGH); - clearError(ERR_MAP_LOW); + sensorReadings.emapADC = udiv_32_16(cycle_average.emapAdcRunningTotal, cycle_average.sampleCount); //Note that the MAP count can be reused here as it will always be the same count. } - mapErrorCount = 0; + reset(current, cycle_average, rawReadings); + // We can now derive new map values + return true; } -} - -void instanteneousMAPReading(void) -{ - //Update the calculation times and last value. These are used by the MAP based Accel enrich - MAPlast = currentStatus.MAP; - MAPlast_time = MAP_time; - MAP_time = micros(); - - unsigned int tempReading; - //Instantaneous MAP readings - #if defined(ANALOG_ISR_MAP) - tempReading = AnChannel[pinMAP-A0]; - #else - tempReading = analogRead(pinMAP); - tempReading = analogRead(pinMAP); - #endif - //Error checking - if( (tempReading >= VALID_MAP_MAX) || (tempReading <= VALID_MAP_MIN) ) { mapErrorCount += 1; } - else { mapErrorCount = 0; } - - //During startup a call is made here to get the baro reading. In this case, we can't apply the ADC filter - if(currentStatus.initialisationComplete == true) { currentStatus.mapADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_MAP, currentStatus.mapADC); } //Very weak filter - else { currentStatus.mapADC = tempReading; } //Baro reading (No filter) - - currentStatus.MAP = fastMap10Bit(currentStatus.mapADC, configPage2.mapMin, configPage2.mapMax); //Get the current MAP value - if(currentStatus.MAP < 0) { currentStatus.MAP = 0; } //Sanity check - //Repeat for EMAP if it's enabled - if(configPage6.useEMAP == true) - { - #if defined(ANALOG_ISR_MAP) - tempReading = AnChannel[pinEMAP-A0]; - #else - tempReading = analogRead(pinEMAP); - tempReading = analogRead(pinEMAP); - #endif - - //Error check - if( (tempReading < VALID_MAP_MAX) && (tempReading > VALID_MAP_MIN) ) - { - currentStatus.EMAPADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_MAP, currentStatus.EMAPADC); - } - else { mapErrorCount += 1; } - currentStatus.EMAP = fastMap10Bit(currentStatus.EMAPADC, configPage2.EMAPMin, configPage2.EMAPMax); - if(currentStatus.EMAP < 0) { currentStatus.EMAP = 0; } //Sanity check - } - + reset(current, cycle_average, sensorReadings); + return instanteneousMAPReading(); } -void readMAP(void) -{ - unsigned int tempReading; - //MAP Sampling system - switch(configPage2.mapSample) +static inline bool isCycleCurrent(const statuses ¤t, uint32_t cycleStartIndex) { + ATOMIC() { + return (cycleStartIndex == (uint8_t)current.startRevolutions) || ((cycleStartIndex+1U) == (uint8_t)current.startRevolutions); + } + return false; // Just here to avoid compiler warning. +} + +static inline bool isCycleCurrent(const statuses ¤t, const map_cycle_average_t &cycle_avg) { + return isCycleCurrent(current, cycle_avg.cycleStartIndex); +} + +TESTABLE_INLINE_STATIC bool canUseCycleAverage(const statuses ¤t, const config2 &page2) { + ATOMIC() { + return (current.RPMdiv100 > page2.mapSwitchPoint) && HasAnySyncUnsafe(current) && (current.startRevolutions > 1U); + } + return false; // Just here to avoid compiler warning. +} + +TESTABLE_INLINE_STATIC bool cycleAverageMAPReading(const statuses ¤t, const config2 &page2, map_cycle_average_t &cycle_average, map_adc_readings_t &sensorReadings) { + if ( canUseCycleAverage(current, page2) ) { - case 0: - //Instantaneous MAP readings - instanteneousMAPReading(); + //2 revolutions are looked at for 4 stroke. 2 stroke not currently catered for. + if( isCycleCurrent(current, cycle_average) ) { + return cycleAverageMAPReadingAccumulate(cycle_average, sensorReadings); + } + // Reaching here means that the last cycle has completed and the MAP value should be calculated + return cycleAverageEndCycle(current, cycle_average, sensorReadings); + } + + //If the engine isn't running and RPM below switch point, fall back to instantaneous reads + reset(current, cycle_average, sensorReadings); + return instanteneousMAPReading(); +} + +static inline bool cycleMinimumAccumulate(map_cycle_min_t &cycle_min, const map_adc_readings_t &sensorReadings) { + //Check whether the current reading is lower than the running minimum + cycle_min.mapMinimum = min(sensorReadings.mapADC, cycle_min.mapMinimum); + + // We are *not* ready to derive new maps readings yet + return false; +} + +static inline void reset(const statuses ¤t, map_cycle_min_t &cycle_min, const map_adc_readings_t &sensorReadings) { + cycle_min.cycleStartIndex = (uint8_t)current.startRevolutions; //Reset the current rev count + cycle_min.mapMinimum = sensorReadings.mapADC; //Reset the latest value so the next reading will always be lower +} + +static inline bool cycleMinimumEndCycle(const statuses ¤t, map_cycle_min_t &cycle_min, map_adc_readings_t &sensorReadings) { + // Record this since we're about to overwrite it.... + map_adc_readings_t rawReadings = sensorReadings; + + sensorReadings.mapADC = cycle_min.mapMinimum; + + reset(current, cycle_min, rawReadings); + + // We can now derive new map values + return true; +} + +static inline bool isCycleCurrent(const statuses ¤t, const map_cycle_min_t &cycle_min) { + return isCycleCurrent(current, cycle_min.cycleStartIndex); +} + +TESTABLE_INLINE_STATIC bool cycleMinimumMAPReading(const statuses ¤t, const config2 &page2, map_cycle_min_t &cycle_min, map_adc_readings_t &sensorReadings) { + if (current.RPMdiv100 > page2.mapSwitchPoint) { + //2 revolutions are looked at for 4 stroke. 2 stroke not currently catered for. + if ( isCycleCurrent(current, cycle_min) ) { + return cycleMinimumAccumulate(cycle_min, sensorReadings); + } + //Reaching here means that the last cycle has completed and the MAP value should be calculated + return cycleMinimumEndCycle(current, cycle_min, sensorReadings); + } + + //If the engine isn't running and RPM below switch point, fall back to instantaneous reads + reset(current, cycle_min, sensorReadings); + return instanteneousMAPReading(); +} + +static inline bool eventAverageAccumulate(map_event_average_t &eventAverage, const map_adc_readings_t &sensorReadings) { + eventAverage.mapAdcRunningTotal += sensorReadings.mapADC; //Add the current reading onto the total + ++eventAverage.sampleCount; + + // We are *not* ready to derive new maps readings yet + return false; +} + +static inline bool isIgnitionEventValid(const map_event_average_t &eventAverage) { + ATOMIC() { + return (eventAverage.eventStartIndex < (uint8_t)ignitionCount); + } + return false; // Just here to avoid compiler warning. +} + +static inline void reset(map_event_average_t &eventAverage, const map_adc_readings_t &sensorReadings) { + // Reset for next cycle. + eventAverage.mapAdcRunningTotal = 0U; + eventAverage.sampleCount = 0U; + eventAverage.eventStartIndex = (uint8_t)ignitionCount; + (void)eventAverageAccumulate(eventAverage, sensorReadings); +} + +static inline bool eventAverageEndEvent(map_event_average_t &eventAverage, map_adc_readings_t &sensorReadings) { + //Sanity check + if( (eventAverage.mapAdcRunningTotal != 0U) && (eventAverage.sampleCount != 0U) && isIgnitionEventValid(eventAverage) ) + { + // Record this since we're about to overwrite it.... + map_adc_readings_t rawReadings = sensorReadings; + sensorReadings.mapADC = udiv_32_16(eventAverage.mapAdcRunningTotal, eventAverage.sampleCount); + reset(eventAverage, rawReadings); + // We can now derive new map values + return true; + } + + reset(eventAverage, sensorReadings); + return instanteneousMAPReading(); +} + +static inline bool isIgnitionEventCurrent(const map_event_average_t &eventAverage) { + ATOMIC() { + return (eventAverage.eventStartIndex == (uint8_t)ignitionCount); + } + return false; // Just here to avoid compiler warning. +} + + +TESTABLE_INLINE_STATIC bool canUseEventAverage(const statuses ¤t, const config2 &page2) { + ATOMIC() { + return (current.RPMdiv100 > page2.mapSwitchPoint) && HasAnySyncUnsafe(current) && (current.startRevolutions > 1U) && (!current.engineProtectStatus); + } + return false; // Just here to avoid compiler warning. +} + +TESTABLE_INLINE_STATIC bool eventAverageMAPReading(const statuses ¤t, const config2 &page2, map_event_average_t &eventAverage, map_adc_readings_t &sensorReadings) { + //Average of an ignition event + if ( canUseEventAverage(current, page2) ) //If the engine isn't running, fall back to instantaneous reads + { + if( isIgnitionEventCurrent(eventAverage) ) { //Watch for a change in the ignition counter to determine whether we're still on the same event + return eventAverageAccumulate(eventAverage, sensorReadings); + } + + //Reaching here means that the next ignition event has occurred and the MAP value should be calculated + return eventAverageEndEvent(eventAverage, sensorReadings); + } + + reset(eventAverage, sensorReadings); + return instanteneousMAPReading(); +} + +static inline bool processMapReadings(const statuses ¤t, const config2 &page2, map_algorithm_t &state) { + //MAP Sampling system + switch(page2.mapSample) + { + case MAPSamplingCycleAverage: + return cycleAverageMAPReading(current, page2, state.cycle_average, state.sensorReadings); break; - case 1: - //Average of a cycle - - if ( (currentStatus.RPMdiv100 > configPage2.mapSwitchPoint) && ((currentStatus.hasSync == true) || BIT_CHECK(currentStatus.status3, BIT_STATUS3_HALFSYNC)) && (currentStatus.startRevolutions > 1) ) //If the engine isn't running and RPM below switch point, fall back to instantaneous reads - { - if( (MAPcurRev == currentStatus.startRevolutions) || ( (MAPcurRev+1) == currentStatus.startRevolutions) ) //2 revolutions are looked at for 4 stroke. 2 stroke not currently catered for. - { - #if defined(ANALOG_ISR_MAP) - tempReading = AnChannel[pinMAP-A0]; - #else - tempReading = analogRead(pinMAP); - tempReading = analogRead(pinMAP); - #endif - - //Error check - if( (tempReading < VALID_MAP_MAX) && (tempReading > VALID_MAP_MIN) ) - { - currentStatus.mapADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_MAP, currentStatus.mapADC); - MAPrunningValue += currentStatus.mapADC; //Add the current reading onto the total - MAPcount++; - } - else { mapErrorCount += 1; } - - //Repeat for EMAP if it's enabled - if(configPage6.useEMAP == true) - { - #if defined(ANALOG_ISR_MAP) - tempReading = AnChannel[pinEMAP-A0]; - #else - tempReading = analogRead(pinEMAP); - tempReading = analogRead(pinEMAP); - #endif - - //Error check - if( (tempReading < VALID_MAP_MAX) && (tempReading > VALID_MAP_MIN) ) - { - currentStatus.EMAPADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_MAP, currentStatus.EMAPADC); - EMAPrunningValue += currentStatus.EMAPADC; //Add the current reading onto the total - } - else { mapErrorCount += 1; } - } - } - else - { - //Reaching here means that the last cycle has completed and the MAP value should be calculated - //Sanity check - if( (MAPrunningValue != 0) && (MAPcount != 0) ) - { - //Update the calculation times and last value. These are used by the MAP based Accel enrich - MAPlast = currentStatus.MAP; - MAPlast_time = MAP_time; - MAP_time = micros(); - - currentStatus.mapADC = udiv_32_16(MAPrunningValue, MAPcount); - currentStatus.MAP = fastMap10Bit(currentStatus.mapADC, configPage2.mapMin, configPage2.mapMax); //Get the current MAP value - validateMAP(); - - //If EMAP is enabled, the process is identical to the above - if(configPage6.useEMAP == true) - { - currentStatus.EMAPADC = udiv_32_16(EMAPrunningValue, MAPcount); //Note that the MAP count can be reused here as it will always be the same count. - currentStatus.EMAP = fastMap10Bit(currentStatus.EMAPADC, configPage2.EMAPMin, configPage2.EMAPMax); - if(currentStatus.EMAP < 0) { currentStatus.EMAP = 0; } //Sanity check - } - } - else { instanteneousMAPReading(); } - - MAPcurRev = currentStatus.startRevolutions; //Reset the current rev count - MAPrunningValue = 0; - EMAPrunningValue = 0; //Can reset this even if EMAP not used - MAPcount = 0; - } - } - else - { - instanteneousMAPReading(); - MAPrunningValue = currentStatus.mapADC; //Keep updating the MAPrunningValue to give it head start when switching to cycle average. - if(configPage6.useEMAP == true) - { - EMAPrunningValue = currentStatus.EMAPADC; - } - MAPcount = 1; - } + case MAPSamplingCycleMinimum: + return cycleMinimumMAPReading(current, page2, state.cycle_min, state.sensorReadings); break; - case 2: - //Minimum reading in a cycle - if (currentStatus.RPMdiv100 > configPage2.mapSwitchPoint) //If the engine isn't running and RPM below switch point, fall back to instantaneous reads - { - if( (MAPcurRev == currentStatus.startRevolutions) || ((MAPcurRev+1) == currentStatus.startRevolutions) ) //2 revolutions are looked at for 4 stroke. 2 stroke not currently catered for. - { - #if defined(ANALOG_ISR_MAP) - tempReading = AnChannel[pinMAP-A0]; - #else - tempReading = analogRead(pinMAP); - tempReading = analogRead(pinMAP); - #endif - //Error check - if( (tempReading < VALID_MAP_MAX) && (tempReading > VALID_MAP_MIN) ) - { - if( (unsigned long)tempReading < MAPrunningValue ) { MAPrunningValue = (unsigned long)tempReading; } //Check whether the current reading is lower than the running minimum - } - else { mapErrorCount += 1; } - } - else - { - //Reaching here means that the last cycle has completed and the MAP value should be calculated - - //Update the calculation times and last value. These are used by the MAP based Accel enrich - MAPlast = currentStatus.MAP; - MAPlast_time = MAP_time; - MAP_time = micros(); - - currentStatus.mapADC = MAPrunningValue; - currentStatus.MAP = fastMap10Bit(currentStatus.mapADC, configPage2.mapMin, configPage2.mapMax); //Get the current MAP value - MAPcurRev = currentStatus.startRevolutions; //Reset the current rev count - MAPrunningValue = 1023; //Reset the latest value so the next reading will always be lower - - validateMAP(); - } - } - else - { - instanteneousMAPReading(); - MAPrunningValue = currentStatus.mapADC; //Keep updating the MAPrunningValue to give it head start when switching to cycle minimum. - } - break; - - case 3: - //Average of an ignition event - if ( (currentStatus.RPMdiv100 > configPage2.mapSwitchPoint) && ((currentStatus.hasSync == true) || BIT_CHECK(currentStatus.status3, BIT_STATUS3_HALFSYNC)) && (currentStatus.startRevolutions > 1) && (! currentStatus.engineProtectStatus) ) //If the engine isn't running, fall back to instantaneous reads - { - if( (MAPcurRev == ignitionCount) ) //Watch for a change in the ignition counter to determine whether we're still on the same event - { - #if defined(ANALOG_ISR_MAP) - tempReading = AnChannel[pinMAP-A0]; - #else - tempReading = analogRead(pinMAP); - tempReading = analogRead(pinMAP); - #endif - - //Error check - if( (tempReading < VALID_MAP_MAX) && (tempReading > VALID_MAP_MIN) ) - { - currentStatus.mapADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_MAP, currentStatus.mapADC); - MAPrunningValue += currentStatus.mapADC; //Add the current reading onto the total - MAPcount++; - } - else { mapErrorCount += 1; } - } - else - { - //Reaching here means that the next ignition event has occurred and the MAP value should be calculated - //Sanity check - if( (MAPrunningValue != 0) && (MAPcount != 0) && (MAPcurRev < ignitionCount) ) - { - //Update the calculation times and last value. These are used by the MAP based Accel enrich - MAPlast = currentStatus.MAP; - MAPlast_time = MAP_time; - MAP_time = micros(); - - currentStatus.mapADC = udiv_32_16(MAPrunningValue, MAPcount); - currentStatus.MAP = fastMap10Bit(currentStatus.mapADC, configPage2.mapMin, configPage2.mapMax); //Get the current MAP value - validateMAP(); - } - else { instanteneousMAPReading(); } - - MAPcurRev = ignitionCount; //Reset the current event count - MAPrunningValue = 0; - MAPcount = 0; - } - } - else - { - instanteneousMAPReading(); - MAPrunningValue = currentStatus.mapADC; //Keep updating the MAPrunningValue to give it head start when switching to ignition event average. - MAPcount = 1; - } + case MAPSamplingIgnitionEventAverage: + return eventAverageMAPReading(current, page2, state.event_average, state.sensorReadings); break; + case MAPSamplingInstantaneous: default: - //Instantaneous MAP readings (Just in case) - instanteneousMAPReading(); + return instanteneousMAPReading(); break; } } +static inline bool isValidMapSensorReading(uint16_t reading) { + return (reading < VALID_MAP_MAX) && (reading > VALID_MAP_MIN); +} + +TESTABLE_INLINE_STATIC uint16_t validateFilterMapSensorReading(uint16_t reading, uint8_t alpha, uint16_t prior) { + //Error check + if (isValidMapSensorReading(reading)) { + return LOW_PASS_FILTER(reading, alpha, prior); + } + return prior; +} + +static inline uint16_t readFilteredMapADC(uint8_t pin, uint8_t alpha, uint16_t prior) { + return validateFilterMapSensorReading(readMAPSensor(pin), alpha, prior); +} + +static inline map_adc_readings_t readMapSensors(const map_adc_readings_t &previousReadings, const config4 &page4, bool useEMAP) { + return { + readFilteredMapADC(pinMAP, page4.ADCFILTER_MAP, previousReadings.mapADC), + (uint16_t)(useEMAP ? readFilteredMapADC(pinEMAP, page4.ADCFILTER_MAP, previousReadings.emapADC) : UINT16_MAX) + }; +} + +static inline void resetMAPLast(map_last_read_t &lastRead, uint16_t oldMAPValue) { + //Update the calculation times and last value. These are used by the MAP based Accel enrich + uint32_t currTime = micros(); + lastRead.lastMAPValue = oldMAPValue; + // lastRead.lastReadingTime = lastRead.currentReadingTime; + lastRead.timeDeltaReadings = currTime - lastRead.currentReadingTime; + lastRead.currentReadingTime = currTime; +} + +static inline uint16_t mapADCToMAP(uint16_t mapADC, int8_t mapMin, uint16_t mapMax) { + int16_t mapped = fastMap10Bit(mapADC, mapMin, mapMax); //Get the current MAP value + return max((int16_t)0, mapped); //Sanity check +} + +static inline void setMAPValuesFromReadings(const map_adc_readings_t &readings, const config2 &page2, bool useEMAP, statuses ¤t) { + current.MAP = mapADCToMAP(readings.mapADC, page2.mapMin, page2.mapMax); //Get the current MAP value + //Repeat for EMAP if it's enabled + if(useEMAP) { + current.EMAP = mapADCToMAP(readings.emapADC, page2.EMAPMin, page2.EMAPMax); + } +} + +static map_algorithm_t mapAlgorithmState; + +#if defined(UNIT_TEST) +map_last_read_t& getMapLast(void){ + return mapAlgorithmState.lastReading; +} +#endif + +static void initialiseMAP(void) { + (void)memset(&mapAlgorithmState, 0, sizeof(mapAlgorithmState)); +} + +void readMAP(void) +{ + // Read sensor(s) + mapAlgorithmState.sensorReadings = readMapSensors(mapAlgorithmState.sensorReadings, configPage4, configPage6.useEMAP); + + // Process sensor readings according to user chosen sampling algorithm + if (processMapReadings(currentStatus, configPage2, mapAlgorithmState)) { + // Sampling algorithm has decided that the new readings should be used + + // Roll over the last reading + resetMAPLast(mapAlgorithmState.lastReading, currentStatus.MAP); + + // Convert from filtered sensor readings to kPa + setMAPValuesFromReadings(mapAlgorithmState.sensorReadings, configPage2, configPage6.useEMAP, currentStatus); + } +} + +/** @brief Get the MAP change between the last 2 readings */ +int16_t getMAPDelta(void) { + return (int16_t)currentStatus.MAP - (int16_t)mapAlgorithmState.lastReading.lastMAPValue; +} + +/** @brief Get the time in µS between the last 2 MAP readings */ +uint32_t getMAPDeltaTime(void) { + return mapAlgorithmState.lastReading.timeDeltaReadings; +} + +// ========================================== TPS ========================================== + void readTPS(bool useFilter) { currentStatus.TPSlast = currentStatus.TPS; - #if defined(ANALOG_ISR) - byte tempTPS = fastMap1023toX(AnChannel[pinTPS-A0], 255); //Get the current raw TPS ADC value and map it into a byte - #else - analogRead(pinTPS); - byte tempTPS = fastMap1023toX(analogRead(pinTPS), 255); //Get the current raw TPS ADC value and map it into a byte - #endif + uint8_t tempTPS = (uint8_t)fastMap10Bit(readAnalogSensor(pinTPS), 0U, 255U); //Get the current raw TPS ADC value and map it into a uint8_t + //The use of the filter can be overridden if required. This is used on startup to disable priming pulse if flood clear is wanted - if(useFilter == true) { currentStatus.tpsADC = ADC_FILTER(tempTPS, configPage4.ADCFILTER_TPS, currentStatus.tpsADC); } + if(useFilter == true) { currentStatus.tpsADC = (uint8_t)LOW_PASS_FILTER((uint16_t)tempTPS, configPage4.ADCFILTER_TPS, (uint16_t)currentStatus.tpsADC); } else { currentStatus.tpsADC = tempTPS; } - byte tempADC = currentStatus.tpsADC; //The tempADC value is used in order to allow TunerStudio to recover and redo the TPS calibration if this somehow gets corrupted + uint8_t tempADC = currentStatus.tpsADC; //The tempADC value is used in order to allow TunerStudio to recover and redo the TPS calibration if this somehow gets corrupted if(configPage2.tpsMax > configPage2.tpsMin) { //Check that the ADC values fall within the min and max ranges (Should always be the case, but noise can cause these to fluctuate outside the defined range). - if (currentStatus.tpsADC < configPage2.tpsMin) { tempADC = configPage2.tpsMin; } - else if(currentStatus.tpsADC > configPage2.tpsMax) { tempADC = configPage2.tpsMax; } + tempADC = clamp(tempADC, configPage2.tpsMin, configPage2.tpsMax); currentStatus.TPS = map(tempADC, configPage2.tpsMin, configPage2.tpsMax, 0, 200); //Take the raw TPS ADC value and convert it into a TPS% based on the calibrated values } else @@ -490,20 +566,19 @@ void readTPS(bool useFilter) //This case occurs when the TPS +5v and gnd are wired backwards, but the user wishes to retain this configuration. //In such a case, tpsMin will be greater then tpsMax and hence checks and mapping needs to be reversed - tempADC = UINT8_MAX - currentStatus.tpsADC; //Reverse the ADC values - uint16_t tempTPSMax = UINT8_MAX - configPage2.tpsMax; - uint16_t tempTPSMin = UINT8_MAX - configPage2.tpsMin; + tempADC = UINT8_MAX - tempADC; //Reverse the ADC values + uint8_t tempTPSMax = UINT8_MAX - configPage2.tpsMax; + uint8_t tempTPSMin = UINT8_MAX - configPage2.tpsMin; //All checks below are reversed from the standard case above - if (tempADC > tempTPSMax) { tempADC = tempTPSMax; } - else if(tempADC < tempTPSMin) { tempADC = tempTPSMin; } + tempADC = clamp(tempADC, tempTPSMin, tempTPSMax); currentStatus.TPS = map(tempADC, tempTPSMin, tempTPSMax, 0, 200); } //Check whether the closed throttle position sensor is active if(configPage2.CTPSEnabled == true) { - if(configPage2.CTPSPolarity == 0) { currentStatus.CTPSActive = !digitalRead(pinCTPS); } //Normal mode (ground switched) + if(configPage2.CTPSPolarity == 0U) { currentStatus.CTPSActive = !digitalRead(pinCTPS); } //Normal mode (ground switched) else { currentStatus.CTPSActive = digitalRead(pinCTPS); } //Inverted mode (5v activates closed throttle position sensor) } else { currentStatus.CTPSActive = 0; } @@ -511,16 +586,9 @@ void readTPS(bool useFilter) void readCLT(bool useFilter) { - unsigned int tempReading; - #if defined(ANALOG_ISR) - tempReading = AnChannel[pinCLT-A0]; //Get the current raw CLT value - #else - tempReading = analogRead(pinCLT); - tempReading = analogRead(pinCLT); - //tempReading = fastMap1023toX(analogRead(pinCLT), 511); //Get the current raw CLT value - #endif + uint16_t tempReading = readAnalogSensor(pinCLT); //The use of the filter can be overridden if required. This is used on startup so there can be an immediately accurate coolant value for priming - if(useFilter == true) { currentStatus.cltADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_CLT, currentStatus.cltADC); } + if(useFilter == true) { currentStatus.cltADC = LOW_PASS_FILTER(tempReading, configPage4.ADCFILTER_CLT, currentStatus.cltADC); } else { currentStatus.cltADC = tempReading; } currentStatus.coolant = table2D_getValue(&cltCalibrationTable, currentStatus.cltADC) - CALIBRATION_TEMPERATURE_OFFSET; //Temperature calibration values are stored as positive bytes. We subtract 40 from them to allow for negative temperatures @@ -528,89 +596,89 @@ void readCLT(bool useFilter) void readIAT(void) { - unsigned int tempReading; - #if defined(ANALOG_ISR) - tempReading = AnChannel[pinIAT-A0]; //Get the current raw IAT value - #else - tempReading = analogRead(pinIAT); - tempReading = analogRead(pinIAT); - #endif - currentStatus.iatADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_IAT, currentStatus.iatADC); + currentStatus.iatADC = LOW_PASS_FILTER(readAnalogSensor(pinIAT), configPage4.ADCFILTER_IAT, currentStatus.iatADC); currentStatus.IAT = table2D_getValue(&iatCalibrationTable, currentStatus.iatADC) - CALIBRATION_TEMPERATURE_OFFSET; } +// ========================================== Baro ========================================== + +/* +* The highest sea-level pressure on Earth occurs in Siberia, where the Siberian High often attains a sea-level pressure above 105 kPa; +* with record highs close to 108.5 kPa. +* The lowest possible baro reading is based on an altitude of 3500m above sea level. +*/ +static inline bool isValidBaro(uint8_t baro) { +static constexpr uint16_t BARO_MIN = 65U; +static constexpr uint16_t BARO_MAX = 108U; + + return (baro >= BARO_MIN) && (baro <= BARO_MAX); +} + +static inline void setBaroFromSensorReading(uint16_t sensorReading) { + currentStatus.baroADC = sensorReading; + int16_t tempValue = fastMap10Bit(currentStatus.baroADC, configPage2.baroMin, configPage2.baroMax); + currentStatus.baro = (uint8_t)max((int16_t)0, tempValue); +} + +// Should only be called when the engine isn't running. +static inline void setBaroFromMAP(void) { + uint16_t tempReading = mapADCToMAP(readMAPSensor(pinMAP), configPage2.mapMin, configPage2.mapMax); + if (isValidBaro(tempReading)) //Safety check to ensure the baro reading is within the physical limits + { + currentStatus.baro = tempReading; + storeLastBaro(currentStatus.baro); + } +} + void readBaro(void) { - if ( configPage6.useExtBaro != 0 ) - { - int tempReading; + if ( configPage6.useExtBaro != 0U ) { // readings - #if defined(ANALOG_ISR_MAP) - tempReading = AnChannel[pinBaro-A0]; - #else - tempReading = analogRead(pinBaro); - tempReading = analogRead(pinBaro); - #endif + setBaroFromSensorReading(LOW_PASS_FILTER(readMAPSensor(pinBaro), configPage4.ADCFILTER_BARO, currentStatus.baroADC)); //Very weak filter + // If no dedicated baro sensor is available, attempt to get a reading from the MAP sensor. This can only be done if the engine is not running. + } else if ((currentStatus.RPM == 0U) && !engineIsRunning(micros()-MICROS_PER_SEC)) { + setBaroFromMAP(); + } else { + // Do nothing - baro remains at last read value & MISRA checker is kept happy. + } +} - if(currentStatus.initialisationComplete == true) { currentStatus.baroADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_BARO, currentStatus.baroADC); }//Very weak filter - else { currentStatus.baroADC = tempReading; } //Baro reading (No filter) - - currentStatus.baro = fastMap10Bit(currentStatus.baroADC, configPage2.baroMin, configPage2.baroMax); //Get the current MAP value +static inline void initialiseBaro(void) { + if ( configPage6.useExtBaro != 0U ) + { + // Use raw unfiltered value initially + setBaroFromSensorReading(readMAPSensor(pinBaro)); } else { - /* - * If no dedicated baro sensor is available, attempt to get a reading from the MAP sensor. This can only be done if the engine is not running. - * 1. Verify that the engine is not running - * 2. Verify that the reading from the MAP sensor is within the possible physical limits - */ - //Attempt to use the last known good baro reading from EEPROM as a starting point - byte lastBaro = readLastBaro(); - if ((lastBaro >= BARO_MIN) && (lastBaro <= BARO_MAX)) //Make sure it's not invalid (Possible on first run etc) - { currentStatus.baro = lastBaro; } //last baro correction - else { currentStatus.baro = 100; } //Fall back position. - - //Verify the engine isn't running by confirming RPM is 0 and it has been at least 1 second since the last tooth was detected - unsigned long timeToLastTooth = (micros() - toothLastToothTime); - if((currentStatus.RPM == 0) && (timeToLastTooth > MICROS_PER_SEC)) - { - instanteneousMAPReading(); //Get the current MAP value - /* - * The highest sea-level pressure on Earth occurs in Siberia, where the Siberian High often attains a sea-level pressure above 105 kPa; - * with record highs close to 108.5 kPa. - * The lowest possible baro reading is based on an altitude of 3500m above sea level. - */ - if ((currentStatus.MAP >= BARO_MIN) && (currentStatus.MAP <= BARO_MAX)) //Safety check to ensure the baro reading is within the physical limits - { - currentStatus.baro = currentStatus.MAP; - storeLastBaro(currentStatus.baro); - } - } + uint8_t lastBaro = readLastBaro(); + // Make sure it's not invalid (Possible on first run etc) + currentStatus.baro = isValidBaro(lastBaro) ? lastBaro : 100U; + // We assume external callers already made sure the engine isn't running + setBaroFromMAP(); } } +void initialiseMAPBaro(void) { + initialiseMAP(); + initialiseBaro(); +} + +// ========================================== O2 ========================================== + void readO2(void) { //An O2 read is only performed if an O2 sensor type is selected. This is to prevent potentially dangerous use of the O2 readings prior to proper setup/calibration - if(configPage6.egoType > 0) + if(configPage6.egoType > 0U) { - unsigned int tempReading; - #if defined(ANALOG_ISR) - tempReading = AnChannel[pinO2-A0]; //Get the current O2 value. - #else - tempReading = analogRead(pinO2); - tempReading = analogRead(pinO2); - //tempReading = fastMap1023toX(analogRead(pinO2), 511); //Get the current O2 value. - #endif - currentStatus.O2ADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_O2, currentStatus.O2ADC); - //currentStatus.O2 = o2CalibrationTable[currentStatus.O2ADC]; + currentStatus.O2ADC = LOW_PASS_FILTER(readAnalogSensor(pinO2), configPage4.ADCFILTER_O2, currentStatus.O2ADC); currentStatus.O2 = table2D_getValue(&o2CalibrationTable, currentStatus.O2ADC); } else { - currentStatus.O2ADC = 0; - currentStatus.O2 = 0; + currentStatus.O2ADC = 0U; + currentStatus.O2 = 0U; } } @@ -619,27 +687,13 @@ void readO2_2(void) { //Second O2 currently disabled as its not being used //Get the current O2 value. - unsigned int tempReading; - #if defined(ANALOG_ISR) - tempReading = AnChannel[pinO2_2-A0]; //Get the current O2 value. - #else - tempReading = analogRead(pinO2_2); - tempReading = analogRead(pinO2_2); - //tempReading = fastMap1023toX(analogRead(pinO2_2), 511); //Get the current O2 value. - #endif - currentStatus.O2_2ADC = ADC_FILTER(tempReading, configPage4.ADCFILTER_O2, currentStatus.O2_2ADC); + currentStatus.O2_2ADC = LOW_PASS_FILTER(readAnalogSensor(pinO2_2), configPage4.ADCFILTER_O2, currentStatus.O2_2ADC); currentStatus.O2_2 = table2D_getValue(&o2CalibrationTable, currentStatus.O2_2ADC); } void readBat(void) { - int tempReading; - #if defined(ANALOG_ISR) - tempReading = fastMap1023toX(AnChannel[pinBat-A0], 245); //Get the current raw Battery value. Permissible values are from 0v to 24.5v (245) - #else - tempReading = analogRead(pinBat); - tempReading = fastMap1023toX(analogRead(pinBat), 245); //Get the current raw Battery value. Permissible values are from 0v to 24.5v (245) - #endif + int16_t tempReading = fastMap10Bit(readAnalogSensor(pinBat), 0, 245); //Get the current raw Battery value. Permissible values are from 0v to 24.5v (245) //Apply the offset calibration value to the reading tempReading += configPage4.batVoltCorrect; @@ -651,7 +705,7 @@ void readBat(void) //The following is a check for if the voltage has jumped up from under 5.5v to over 7v. //If this occurs, it's very likely that the system has gone from being powered by USB to being powered from the 12v power source. //Should that happen, we re-trigger the fuel pump priming and idle homing (If using a stepper) - if( (currentStatus.battery10 < 55) && (tempReading > 70) && (currentStatus.RPM == 0) ) + if( (currentStatus.battery10 < 55U) && (tempReading > 70) && (currentStatus.RPM == 0U) ) { //Re-prime the fuel pump fpPrimeTime = currentStatus.secl; @@ -665,7 +719,7 @@ void readBat(void) } } - currentStatus.battery10 = ADC_FILTER(tempReading, configPage4.ADCFILTER_BAT, currentStatus.battery10); + currentStatus.battery10 = LOW_PASS_FILTER(tempReading, configPage4.ADCFILTER_BAT, currentStatus.battery10); } /** @@ -675,16 +729,16 @@ void readBat(void) * historyIndex = 0 = Latest entry * historyIndex = 1 = 2nd entry entry */ -uint32_t vssGetPulseGap(byte historyIndex) +uint32_t vssGetPulseGap(uint8_t historyIndex) { uint32_t tempGap = 0; noInterrupts(); int8_t tempIndex = vssIndex - historyIndex; - if(tempIndex < 0) { tempIndex += VSS_SAMPLES; } + if(tempIndex < 0) { tempIndex += (int8_t)VSS_SAMPLES; } if(tempIndex > 0) { tempGap = vssTimes[tempIndex] - vssTimes[tempIndex - 1]; } - else { tempGap = vssTimes[0] - vssTimes[(VSS_SAMPLES-1)]; } + else { tempGap = vssTimes[0] - vssTimes[(VSS_SAMPLES-1U)]; } interrupts(); return tempGap; @@ -694,10 +748,10 @@ uint16_t getSpeed(void) { uint16_t tempSpeed = 0; // Get VSS from CAN, Serial or Analog by using Aux input channels. - if(configPage2.vssMode == 1) + if(configPage2.vssMode == 1U) { // Direct reading from Aux channel - if (configPage2.vssPulsesPerKm == 0) + if (configPage2.vssPulsesPerKm == 0U) { tempSpeed = currentStatus.canin[configPage2.vssAuxCh]; } @@ -706,37 +760,38 @@ uint16_t getSpeed(void) { tempSpeed = (currentStatus.canin[configPage2.vssAuxCh] / configPage2.vssPulsesPerKm); } - tempSpeed = ADC_FILTER(tempSpeed, configPage2.vssSmoothing, currentStatus.vss); //Apply speed smoothing factor + tempSpeed = LOW_PASS_FILTER(tempSpeed, configPage2.vssSmoothing, currentStatus.vss); //Apply speed smoothing factor } // Interrupt driven mode - else if(configPage2.vssMode > 1) + else if(configPage2.vssMode > 1U) { - uint32_t pulseTime = 0; - uint32_t vssTotalTime = 0; + uint32_t pulseTime = 0U; + uint32_t vssTotalTime = 0U; //Add up the time between the teeth. Note that the total number of gaps is equal to the number of samples minus 1 - for(byte x = 0; x<(VSS_SAMPLES-1); x++) + for(uint8_t x = 0U; x<(VSS_SAMPLES-1U); x++) { vssTotalTime += vssGetPulseGap(x); } - pulseTime = vssTotalTime / (VSS_SAMPLES - 1); + pulseTime = vssTotalTime / ((uint32_t)VSS_SAMPLES - 1UL); if ( (micros() - vssTimes[vssIndex]) > MICROS_PER_SEC ) { tempSpeed = 0; } // Check that the car hasn't come to a stop. Is true if last pulse was more than 1 second ago else { tempSpeed = MICROS_PER_HOUR / (pulseTime * configPage2.vssPulsesPerKm); //Convert the pulse gap into km/h - tempSpeed = ADC_FILTER(tempSpeed, configPage2.vssSmoothing, currentStatus.vss); //Apply speed smoothing factor + tempSpeed = LOW_PASS_FILTER(tempSpeed, configPage2.vssSmoothing, currentStatus.vss); //Apply speed smoothing factor } - if(tempSpeed > 1000) { tempSpeed = currentStatus.vss; } //Safety check. This usually occurs when there is a hardware issue - + if(tempSpeed > 1000U) { tempSpeed = currentStatus.vss; } //Safety check. This usually occurs when there is a hardware issue + } else { + // Do nothing } return tempSpeed; } byte getGear(void) { - byte tempGear = 0; //Unknown gear - if(currentStatus.vss > 0) + byte tempGear = 0U; //Unknown gear + if(currentStatus.vss > 0U) { //If the speed is non-zero, default to the last calculated gear tempGear = currentStatus.gear; @@ -749,6 +804,9 @@ byte getGear(void) else if( (pulsesPer1000rpm > (configPage2.vssRatio4 - VSS_GEAR_HYSTERESIS)) && (pulsesPer1000rpm < (configPage2.vssRatio4 + VSS_GEAR_HYSTERESIS)) ) { tempGear = 4; } else if( (pulsesPer1000rpm > (configPage2.vssRatio5 - VSS_GEAR_HYSTERESIS)) && (pulsesPer1000rpm < (configPage2.vssRatio5 + VSS_GEAR_HYSTERESIS)) ) { tempGear = 5; } else if( (pulsesPer1000rpm > (configPage2.vssRatio6 - VSS_GEAR_HYSTERESIS)) && (pulsesPer1000rpm < (configPage2.vssRatio6 + VSS_GEAR_HYSTERESIS)) ) { tempGear = 6; } + else { + // Do nothing + } } return tempGear; @@ -757,23 +815,13 @@ byte getGear(void) byte getFuelPressure(void) { int16_t tempFuelPressure = 0; - uint16_t tempReading; - if(configPage10.fuelPressureEnable > 0) + if(configPage10.fuelPressureEnable > 0U) { - //Perform ADC read - #if defined(ANALOG_ISR) - tempReading = AnChannel[pinFuelPressure-A0]; - #else - tempReading = analogRead(pinFuelPressure); - tempReading = analogRead(pinFuelPressure); - #endif - - tempFuelPressure = fastMap10Bit(tempReading, configPage10.fuelPressureMin, configPage10.fuelPressureMax); - tempFuelPressure = ADC_FILTER(tempFuelPressure, ADCFILTER_PSI_DEFAULT, currentStatus.fuelPressure); //Apply smoothing factor + tempFuelPressure = fastMap10Bit(readAnalogSensor(pinFuelPressure), configPage10.fuelPressureMin, configPage10.fuelPressureMax); + tempFuelPressure = LOW_PASS_FILTER(tempFuelPressure, ADCFILTER_PSI_DEFAULT, currentStatus.fuelPressure); //Apply smoothing factor //Sanity checks - if(tempFuelPressure > configPage10.fuelPressureMax) { tempFuelPressure = configPage10.fuelPressureMax; } - if(tempFuelPressure < 0 ) { tempFuelPressure = 0; } //prevent negative values, which will cause problems later when the values aren't signed. + tempFuelPressure = clamp(tempFuelPressure, (int16_t)0, (int16_t)configPage10.fuelPressureMax); } return (byte)tempFuelPressure; @@ -782,24 +830,14 @@ byte getFuelPressure(void) byte getOilPressure(void) { int16_t tempOilPressure = 0; - uint16_t tempReading; - if(configPage10.oilPressureEnable > 0) + if(configPage10.oilPressureEnable > 0U) { //Perform ADC read - #if defined(ANALOG_ISR) - tempReading = AnChannel[pinOilPressure-A0]; - #else - tempReading = analogRead(pinOilPressure); - tempReading = analogRead(pinOilPressure); - #endif - - - tempOilPressure = fastMap10Bit(tempReading, configPage10.oilPressureMin, configPage10.oilPressureMax); - tempOilPressure = ADC_FILTER(tempOilPressure, ADCFILTER_PSI_DEFAULT, currentStatus.oilPressure); //Apply smoothing factor + tempOilPressure = fastMap10Bit(readAnalogSensor(pinOilPressure), configPage10.oilPressureMin, configPage10.oilPressureMax); + tempOilPressure = LOW_PASS_FILTER(tempOilPressure, ADCFILTER_PSI_DEFAULT, currentStatus.oilPressure); //Apply smoothing factor //Sanity check - if(tempOilPressure > configPage10.oilPressureMax) { tempOilPressure = configPage10.oilPressureMax; } - if(tempOilPressure < 0 ) { tempOilPressure = 0; } //prevent negative values, which will cause problems later when the values aren't signed. + tempOilPressure = clamp(tempOilPressure, (int16_t)0, (int16_t)configPage10.oilPressureMax); } @@ -808,7 +846,6 @@ byte getOilPressure(void) uint8_t getAnalogKnock(void) { - uint16_t tempReading; uint8_t pinKnock = A15; //Default value in case the user has not selected an analog pin in TunerStudio if(configPage10.knock_pin >=47U) { @@ -816,16 +853,7 @@ uint8_t getAnalogKnock(void) } //Perform ADC read - #if defined(ANALOG_ISR) - tempReading = AnChannel[pinKnock-A0]; - #else - tempReading = analogRead(pinKnock); - tempReading = analogRead(pinKnock); - #endif - - tempReading = fastMap1023toX(tempReading, 255); - - return (uint8_t)tempReading; + return (uint8_t)fastMap10Bit(readAnalogSensor(pinKnock), 0U, 255U); } /* @@ -836,8 +864,8 @@ void flexPulse(void) { if(READ_FLEX() == true) { - unsigned long tempPW = (micros() - flexStartTime); //Calculate the pulse width - flexPulseWidth = ADC_FILTER(tempPW, configPage4.FILTER_FLEX, flexPulseWidth); + uint16_t tempPW = clamp(micros() - flexStartTime, 0UL, (unsigned long)UINT16_MAX); //Calculate the pulse width + flexPulseWidth = LOW_PASS_FILTER(tempPW, configPage4.FILTER_FLEX, flexPulseWidth); ++flexCounter; } else @@ -852,7 +880,7 @@ void flexPulse(void) */ void knockPulse(void) { - if( (currentStatus.MAP < (configPage10.knock_maxMAP*2)) && (currentStatus.RPMdiv100 < configPage10.knock_maxRPM) ) + if( (currentStatus.MAP < (configPage10.knock_maxMAP*2U)) && (currentStatus.RPMdiv100 < configPage10.knock_maxRPM) ) { if(!BIT_CHECK(currentStatus.status5, BIT_STATUS5_KNOCK_ACTIVE)) { currentStatus.knockCount++; } //If knock is not currently active we count every pulse. If knock is already active then additional pulses will be counted in correctionKnockTiming() BIT_SET(currentStatus.status5, BIT_STATUS5_KNOCK_PULSE); @@ -867,22 +895,15 @@ void vssPulse(void) { //TODO: Add basic filtering here vssIndex++; - if(vssIndex == VSS_SAMPLES) { vssIndex = 0; } + if(vssIndex == VSS_SAMPLES) { vssIndex = 0U; } vssTimes[vssIndex] = micros(); } +// Read the Aux analog value for pin set by analogPin uint16_t readAuxanalog(uint8_t analogPin) { - //read the Aux analog value for pin set by analogPin - unsigned int tempReading; - #if defined(ANALOG_ISR) - tempReading = AnChannel[analogPin-A0]; //Get the current raw Auxanalog value - #else - tempReading = analogRead(analogPin); - tempReading = analogRead(analogPin); - #endif - return tempReading; + return readAnalogSensor(analogPin); // readAnalogSensor is inlined within this CPP file. } uint16_t readAuxdigital(uint8_t digitalPin) diff --git a/speeduino/sensors.h b/speeduino/sensors.h index 824dddd0..3da4d84d 100644 --- a/speeduino/sensors.h +++ b/speeduino/sensors.h @@ -5,29 +5,25 @@ // The following are alpha values for the ADC filters. // Their values are from 0 to 240, with 0 being no filtering and 240 being maximum -#define ADCFILTER_TPS_DEFAULT 50 -#define ADCFILTER_CLT_DEFAULT 180 -#define ADCFILTER_IAT_DEFAULT 180 -#define ADCFILTER_O2_DEFAULT 128 -#define ADCFILTER_BAT_DEFAULT 128 -#define ADCFILTER_MAP_DEFAULT 20 //This is only used on Instantaneous MAP readings and is intentionally very weak to allow for faster response -#define ADCFILTER_BARO_DEFAULT 64 +#define ADCFILTER_TPS_DEFAULT 50U +#define ADCFILTER_CLT_DEFAULT 180U +#define ADCFILTER_IAT_DEFAULT 180U +#define ADCFILTER_O2_DEFAULT 128U +#define ADCFILTER_BAT_DEFAULT 128U +#define ADCFILTER_MAP_DEFAULT 20U //This is only used on Instantaneous MAP readings and is intentionally very weak to allow for faster response +#define ADCFILTER_BARO_DEFAULT 64U -#define ADCFILTER_PSI_DEFAULT 150 //not currently configurable at runtime, used for misc pressure sensors, oil, fuel, etc. +#define ADCFILTER_PSI_DEFAULT 150U //not currently configurable at runtime, used for misc pressure sensors, oil, fuel, etc. -#define FILTER_FLEX_DEFAULT 75 +#define FILTER_FLEX_DEFAULT 75U -#define BARO_MIN 65 -#define BARO_MAX 108 - -#define VSS_GEAR_HYSTERESIS 10 -#define VSS_SAMPLES 4 //Must be a power of 2 and smaller than 255 +#define VSS_GEAR_HYSTERESIS 10U +#define VSS_SAMPLES 4U //Must be a power of 2 and smaller than 255 #define TPS_READ_FREQUENCY 30 //ONLY VALID VALUES ARE 15 or 30!!! extern volatile byte flexCounter; -extern volatile unsigned long flexStartTime; -extern volatile unsigned long flexPulseWidth; +extern volatile uint32_t flexPulseWidth; #if defined(CORE_AVR) #define READ_FLEX() ((*flex_pin_port & flex_pin_mask) ? true : false) @@ -35,21 +31,7 @@ extern volatile unsigned long flexPulseWidth; #define READ_FLEX() digitalRead(pinFlex) #endif -#define ADMUX_DEFAULT_CONFIG 0x40 //AVCC reference, ADC0 input, right adjusted, ADC enabled - -extern unsigned int MAPcount; //Number of samples taken in the current MAP cycle -extern uint32_t MAPcurRev; //Tracks which revolution we're sampling on extern bool auxIsEnabled; -extern uint16_t MAPlast; /**< The previous MAP reading */ -extern unsigned long MAP_time; //The time the MAP sample was taken -extern unsigned long MAPlast_time; //The time the previous MAP sample was taken - -/** - * @brief Simple low pass IIR filter macro for the analog inputs - * This is effectively implementing the smooth filter from playground.arduino.cc/Main/Smooth - * But removes the use of floats and uses 8 bits of fixed precision. - */ -#define ADC_FILTER(input, alpha, prior) (((long)input * (256 - alpha) + ((long)prior * alpha))) >> 8 void initialiseADC(void); void readTPS(bool useFilter=true); //Allows the option to override the use of the filter @@ -69,8 +51,18 @@ void readIAT(void); void readO2(void); void readBat(void); void readBaro(void); + +/** @brief Initialize the MAP calculation & Baro values */ +void initialiseMAPBaro(void); + void readMAP(void); -void instanteneousMAPReading(void); + uint8_t getAnalogKnock(void); +/** @brief Get the MAP change between the last 2 readings */ +int16_t getMAPDelta(void); + +/** @brief Get the time in µS between the last 2 MAP readings */ +uint32_t getMAPDeltaTime(void); + #endif // SENSORS_H diff --git a/speeduino/sensors_map_structs.h b/speeduino/sensors_map_structs.h new file mode 100644 index 00000000..3ccc8c57 --- /dev/null +++ b/speeduino/sensors_map_structs.h @@ -0,0 +1,71 @@ +#pragma once + +/// @cond +#include + +// These are private to the MAP sampling algorithms but are broken out here +// to support unit testing of the algorithm implementations + +struct map_last_read_t { + uint16_t lastMAPValue; // kPA + uint32_t currentReadingTime; // µS +#if defined(__UINT24_MAX__) + // Maximum time between readings is ~3s (at min RPM). 24 bits is enough for that + __uint24 timeDeltaReadings; +#else + uint32_t timeDeltaReadings; +#endif +}; + +// A pair of ADC sensor readings +struct map_adc_readings_t { + uint16_t mapADC; + uint16_t emapADC; +}; + +// Working state for the cycle average sampling algorithm +struct map_cycle_average_t { + uint8_t cycleStartIndex; +#if defined(__UINT24_MAX__) + // Maximum revolution time is ~1.5s (at min RPM). At a 1KHz sampling rate & 2 revolutions, + // we'll store 3000 readings each at maximum of 1023 (~3000000 max). So a 24-bit value + // should be plenty. + __uint24 mapAdcRunningTotal; + __uint24 emapAdcRunningTotal; +#else + uint32_t mapAdcRunningTotal; + uint32_t emapAdcRunningTotal; +#endif + uint16_t sampleCount; +}; + +// Working state for the cycle minimum sampling algorithm +struct map_cycle_min_t { + uint8_t cycleStartIndex; + uint16_t mapMinimum; +}; + +// Working state for the event average sampling algorithm +struct map_event_average_t { +#if defined(__UINT24_MAX__) + __uint24 mapAdcRunningTotal; +#else + uint32_t mapAdcRunningTotal; +#endif + uint16_t sampleCount; + uint8_t eventStartIndex; +}; + +// The overall MAP sampling system working state +struct map_algorithm_t { + map_last_read_t lastReading; + map_adc_readings_t sensorReadings; + + union { + map_cycle_average_t cycle_average; + map_cycle_min_t cycle_min; + map_event_average_t event_average; + }; +}; + +/// @endcond \ No newline at end of file diff --git a/speeduino/speeduino.ino b/speeduino/speeduino.ino index fd67af4e..d69e7e07 100644 --- a/speeduino/speeduino.ino +++ b/speeduino/speeduino.ino @@ -88,7 +88,16 @@ inline uint16_t applyFuelTrimToPW(trimTable3d *pTrimTable, int16_t fuelLoad, int * - Can be tested for certain frequency interval being expired by (eg) BIT_CHECK(LOOP_TIMER, BIT_TIMER_15HZ) * */ -void loop(void) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" +// Sometimes loop() is inlined by LTO & sometimes not +// When not inlined, there is a huge difference in stack usage: 60+ bytes +// That eats into available RAM. +// Adding __attribute__((always_inline)) forces the LTO process to inline. +// +// Since the function is declared in an Arduino header, we can't change +// it to inline, so we need to suppress the resulting warning. +void __attribute__((always_inline)) loop(void) { mainLoopCount++; LOOP_TIMER = TIMER_mask; @@ -138,8 +147,7 @@ void loop(void) } currentLoopTime = micros_safe(); - uint32_t timeToLastTooth = (currentLoopTime - toothLastToothTime); - if ( (timeToLastTooth < MAX_STALL_TIME) || (toothLastToothTime > currentLoopTime) ) //Check how long ago the last tooth was seen compared to now. If it was more than half a second ago then the engine is probably stopped. toothLastToothTime can be greater than currentLoopTime if a pulse occurs between getting the latest time and doing the comparison + if ( engineIsRunning(currentLoopTime) ) { currentStatus.longRPM = getRPM(); //Long RPM is included here currentStatus.RPM = currentStatus.longRPM; @@ -158,17 +166,12 @@ void loop(void) currentStatus.PW1 = 0; currentStatus.VE = 0; currentStatus.VE2 = 0; - toothLastToothTime = 0; - toothLastSecToothTime = 0; - //toothLastMinusOneToothTime = 0; + resetDecoder(); currentStatus.hasSync = false; BIT_CLEAR(currentStatus.status3, BIT_STATUS3_HALFSYNC); currentStatus.runSecs = 0; //Reset the counter for number of seconds running. currentStatus.startRevolutions = 0; - toothSystemCount = 0; - secondaryToothCount = 0; - MAPcurRev = 0; - MAPcount = 0; + initialiseMAPBaro(); currentStatus.rpmDOT = 0; AFRnextCycle = 0; ignitionCount = 0; @@ -1197,6 +1200,8 @@ void loop(void) BIT_CLEAR(currentStatus.status3, BIT_STATUS3_RESET_PREVENT); } } //loop() +#pragma GCC diagnostic pop + #endif //Unit test guard /** diff --git a/speeduino/timers.cpp b/speeduino/timers.cpp index 14f4fea0..6025a97a 100644 --- a/speeduino/timers.cpp +++ b/speeduino/timers.cpp @@ -300,22 +300,22 @@ void oneMSInterval(void) //Most ARM chips can simply call a function byte tempEthPct = 0; if(flexCounter < configPage2.flexFreqLow) { - tempEthPct = 0; //Standard GM Continental sensor reads from 50Hz (0 ethanol) to 150Hz (Pure ethanol). Subtracting 50 from the frequency therefore gives the ethanol percentage. - flexCounter = 0; + tempEthPct = 0U; //Standard GM Continental sensor reads from 50Hz (0 ethanol) to 150Hz (Pure ethanol). Subtracting 50 from the frequency therefore gives the ethanol percentage. + flexCounter = 0U; } else if (flexCounter > (configPage2.flexFreqHigh + 1) ) //1 pulse buffer { if(flexCounter < (configPage2.flexFreqLow + 19)) //20Hz above the max freq is considered an error condition. Everything below that should be treated as max value { - tempEthPct = 100; - flexCounter = 0; + tempEthPct = 100U; + flexCounter = 0U; } else { //This indicates an error condition. Spec of the sensor is that errors are above 170Hz) - tempEthPct = 0; - flexCounter = 0; + tempEthPct = 0U; + flexCounter = 0U; } } else @@ -325,9 +325,9 @@ void oneMSInterval(void) //Most ARM chips can simply call a function } //Off by 1 error check - if (tempEthPct == 1) { tempEthPct = 0; } + if (tempEthPct == 1U) { tempEthPct = 0U; } - currentStatus.ethanolPct = ADC_FILTER(tempEthPct, configPage4.FILTER_FLEX, currentStatus.ethanolPct); + currentStatus.ethanolPct = (uint8_t)LOW_PASS_FILTER((uint16_t)tempEthPct, configPage4.FILTER_FLEX, (uint16_t)currentStatus.ethanolPct); //Continental flex sensor fuel temperature can be read with following formula: (Temperature = (41.25 * pulse width(ms)) - 81.25). 1000μs = -40C and 5000μs = 125C flexPulseWidth = constrain(flexPulseWidth, 1000UL, 5000UL); diff --git a/speeduino/unit_testing.h b/speeduino/unit_testing.h new file mode 100644 index 00000000..810539b8 --- /dev/null +++ b/speeduino/unit_testing.h @@ -0,0 +1,33 @@ +#pragma once + +/** + * @file + * @brief Unit testability support + * + */ + +#if !defined(UNIT_TEST) +/** + * @brief Mark an entity as having static (internal) linkage, unless a unit + * test is in progress - then the entity is given external linkage so the test can access it. + * + * Most useful for translation unit scoped variables. I.e. static within a CPP file + * + */ +#define TESTABLE_STATIC static +#else +#define TESTABLE_STATIC +#endif + +#if !defined(UNIT_TEST) +/** + * @brief Mark an entity as having static (internal) linkage & inlined, unless a unit + * test is in progress - then the entity is given external linkage so the test can access it. + * + * Most useful for translation unit scoped functions. I.e. "static inline" within a CPP file + * + */ +#define TESTABLE_INLINE_STATIC static inline +#else +#define TESTABLE_INLINE_STATIC +#endif \ No newline at end of file diff --git a/test/test_decoders/SuzukiK6A/test_getCrankAngle.cpp b/test/test_decoders/SuzukiK6A/test_getCrankAngle.cpp index 7b3620ef..0bf0f886 100644 --- a/test/test_decoders/SuzukiK6A/test_getCrankAngle.cpp +++ b/test/test_decoders/SuzukiK6A/test_getCrankAngle.cpp @@ -1,4 +1,5 @@ #include +#include "../../test_utils.h" #include "decoders.h" #include "init.h" @@ -6,6 +7,7 @@ static void test_k6a_getCrankAngle_tooth(uint8_t toothNum, uint16_t expectedCran triggerSetup_SuzukiK6A(); configPage4.triggerAngle = 0U; + extern volatile unsigned long toothLastToothTime; toothLastToothTime = micros() - 150U; toothCurrentCount = toothNum; // Allow some variance since the algorithm relies on calling micros(); @@ -54,13 +56,17 @@ static void test_k6a_getCrankAngle_tooth8(void) { void testSuzukiK6A_getCrankAngle() { - RUN_TEST(test_k6a_getCrankAngle_tooth0); - RUN_TEST(test_k6a_getCrankAngle_tooth1); - RUN_TEST(test_k6a_getCrankAngle_tooth2); - RUN_TEST(test_k6a_getCrankAngle_tooth3); - RUN_TEST(test_k6a_getCrankAngle_tooth4); - RUN_TEST(test_k6a_getCrankAngle_tooth5); - RUN_TEST(test_k6a_getCrankAngle_tooth6); - RUN_TEST(test_k6a_getCrankAngle_tooth7); - RUN_TEST(test_k6a_getCrankAngle_tooth8); + SET_UNITY_FILENAME() { + + RUN_TEST(test_k6a_getCrankAngle_tooth0); + RUN_TEST(test_k6a_getCrankAngle_tooth1); + RUN_TEST(test_k6a_getCrankAngle_tooth2); + RUN_TEST(test_k6a_getCrankAngle_tooth3); + RUN_TEST(test_k6a_getCrankAngle_tooth4); + RUN_TEST(test_k6a_getCrankAngle_tooth5); + RUN_TEST(test_k6a_getCrankAngle_tooth6); + RUN_TEST(test_k6a_getCrankAngle_tooth7); + RUN_TEST(test_k6a_getCrankAngle_tooth8); + + } } \ No newline at end of file diff --git a/test/test_decoders/SuzukiK6A/test_triggerSetEndTeeth.cpp b/test/test_decoders/SuzukiK6A/test_triggerSetEndTeeth.cpp index 51fb6051..0a2cbda4 100644 --- a/test/test_decoders/SuzukiK6A/test_triggerSetEndTeeth.cpp +++ b/test/test_decoders/SuzukiK6A/test_triggerSetEndTeeth.cpp @@ -1,6 +1,7 @@ -#include -#include #include +#include "decoders.h" +#include "globals.h" +#include "../../test_utils.h" #include "schedule_calcs.h" static void test_setup_SuzukiK6A() @@ -271,23 +272,26 @@ void test_K6A_newIgn_trigNeg366() void testSuzukiK6A_setEndTeeth() { - RUN_TEST(test_k6A_newIgn_trig0_1); - RUN_TEST(test_k6A_newIgn_trig90_1); - RUN_TEST(test_k6A_newIgn_trig180_1); - RUN_TEST(test_k6A_newIgn_trig270_1); - RUN_TEST(test_k6A_newIgn_trig360_1); - RUN_TEST(test_k6A_newIgn_trigNeg90_1); - RUN_TEST(test_k6A_newIgn_trigNeg180_1); - RUN_TEST(test_k6A_newIgn_trigNeg270_1); - RUN_TEST(test_k6A_newIgn_trigNeg360_1); + SET_UNITY_FILENAME() { - RUN_TEST(test_k6A_newIgn_trig0_2); - RUN_TEST(test_k6A_newIgn_trig90_2); - RUN_TEST(test_k6A_newIgn_trig180_2); - RUN_TEST(test_k6A_newIgn_trig270_2); - RUN_TEST(test_K6A_newIgn_trig366); - RUN_TEST(test_k6A_newIgn_trigNeg90_2); - RUN_TEST(test_k6A_newIgn_trigNeg180_2); - RUN_TEST(test_k6A_newIgn_trigNeg270_2); - RUN_TEST(test_K6A_newIgn_trigNeg366); + RUN_TEST(test_k6A_newIgn_trig0_1); + RUN_TEST(test_k6A_newIgn_trig90_1); + RUN_TEST(test_k6A_newIgn_trig180_1); + RUN_TEST(test_k6A_newIgn_trig270_1); + RUN_TEST(test_k6A_newIgn_trig360_1); + RUN_TEST(test_k6A_newIgn_trigNeg90_1); + RUN_TEST(test_k6A_newIgn_trigNeg180_1); + RUN_TEST(test_k6A_newIgn_trigNeg270_1); + RUN_TEST(test_k6A_newIgn_trigNeg360_1); + + RUN_TEST(test_k6A_newIgn_trig0_2); + RUN_TEST(test_k6A_newIgn_trig90_2); + RUN_TEST(test_k6A_newIgn_trig180_2); + RUN_TEST(test_k6A_newIgn_trig270_2); + RUN_TEST(test_K6A_newIgn_trig366); + RUN_TEST(test_k6A_newIgn_trigNeg90_2); + RUN_TEST(test_k6A_newIgn_trigNeg180_2); + RUN_TEST(test_k6A_newIgn_trigNeg270_2); + RUN_TEST(test_K6A_newIgn_trigNeg366); + } } \ No newline at end of file diff --git a/test/test_decoders/general.cpp b/test/test_decoders/general.cpp new file mode 100644 index 00000000..1ddf7bcb --- /dev/null +++ b/test/test_decoders/general.cpp @@ -0,0 +1,33 @@ +#include +#include "../test_utils.h" +#include "decoders.h" + +static void test_engineIsRunning(void) +{ + extern volatile unsigned long toothLastToothTime; + extern unsigned long MAX_STALL_TIME; + + MAX_STALL_TIME = 1000; + toothLastToothTime = 0; + TEST_ASSERT_TRUE(engineIsRunning(toothLastToothTime+MAX_STALL_TIME-1UL)); + TEST_ASSERT_FALSE(engineIsRunning(toothLastToothTime+MAX_STALL_TIME)); + TEST_ASSERT_FALSE(engineIsRunning(toothLastToothTime+MAX_STALL_TIME+1UL)); + + // Simulate an interrupt for a pulse being triggered between a call + // to micros() (1000) and the call to engineIsRunning() + toothLastToothTime = 2500; + TEST_ASSERT_TRUE(engineIsRunning(1000UL)); + + TEST_ASSERT_TRUE(engineIsRunning(2499UL)); + TEST_ASSERT_TRUE(engineIsRunning(2500UL)); + TEST_ASSERT_TRUE(engineIsRunning(2501UL)); + + TEST_ASSERT_FALSE(engineIsRunning(toothLastToothTime+MAX_STALL_TIME)); +} + +void testDecoder_General() +{ + SET_UNITY_FILENAME() { + RUN_TEST(test_engineIsRunning); + } +} \ No newline at end of file diff --git a/test/test_decoders/test_decoders.cpp b/test/test_decoders/test_decoders.cpp index 1c2ae6c6..d230e0b1 100644 --- a/test/test_decoders/test_decoders.cpp +++ b/test/test_decoders/test_decoders.cpp @@ -11,6 +11,8 @@ #include "NGC/test_ngc.h" #include "SuzukiK6A/SuzukiK6A.h" +extern void testDecoder_General(void); + void setup() { pinMode(LED_BUILTIN, OUTPUT); @@ -29,6 +31,7 @@ void setup() testNGC(); testSuzukiK6A_setEndTeeth(); testSuzukiK6A_getCrankAngle(); + testDecoder_General(); UNITY_END(); // stop unit testing } diff --git a/test/test_fuel/test_corrections.cpp b/test/test_fuel/test_corrections.cpp index a3f7207e..f1b44d49 100644 --- a/test/test_fuel/test_corrections.cpp +++ b/test/test_fuel/test_corrections.cpp @@ -1,12 +1,12 @@ #include +#include "../test_utils.h" #include "globals.h" #include "corrections.h" #include "test_corrections.h" -#include "../test_utils.h" #include "init.h" #include "sensors.h" #include "speeduino.h" -#include "../test_utils.h" +#include "sensors_map_structs.h" extern void construct2dTables(void); @@ -1175,6 +1175,7 @@ static void test_corrections_TAE() RUN_TEST_P(test_corrections_TAE_timout); } +extern map_last_read_t& getMapLast(void); //********************************************************************************************************************** //Setup a basic MAE enrichment curve, threshold etc that are common to all tests. Specifica values maybe updated in each individual test @@ -1198,9 +1199,8 @@ static void test_corrections_MAE_negative_mapdot() disable_AE_taper(); configPage2.decelAmount = 50; - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 25000UL; - MAPlast = 50; + getMapLast().timeDeltaReadings = 25000UL; + getMapLast().lastMAPValue = 50; currentStatus.MAP = 40; uint16_t accelValue = correctionAccel(); //Run the AE calcs @@ -1216,9 +1216,8 @@ static void test_corrections_MAE_no_rpm_taper() setup_MAE(); disable_AE_taper(); - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 25000UL; - MAPlast = 40; + getMapLast().timeDeltaReadings = 25000UL; + getMapLast().lastMAPValue = 40; currentStatus.MAP = 50; uint16_t accelValue = correctionAccel(); //Run the AE calcs TEST_ASSERT_EQUAL(400, currentStatus.mapDOT); @@ -1228,9 +1227,8 @@ static void test_corrections_MAE_no_rpm_taper() // No change reset_AE(); - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 1000UL; - MAPlast = 40; + getMapLast().timeDeltaReadings = 1000UL; + getMapLast().lastMAPValue = 40; currentStatus.MAP = 40; accelValue = correctionAccel(); //Run the AE calcs TEST_ASSERT_EQUAL(0, currentStatus.mapDOT); @@ -1240,9 +1238,8 @@ static void test_corrections_MAE_no_rpm_taper() // Small change over small time period reset_AE(); - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 1000UL; - MAPlast = 40; + getMapLast().timeDeltaReadings = 1000UL; + getMapLast().lastMAPValue = 40; currentStatus.MAP = 41; accelValue = correctionAccel(); //Run the AE calcs TEST_ASSERT_EQUAL(1000, currentStatus.mapDOT); @@ -1252,9 +1249,8 @@ static void test_corrections_MAE_no_rpm_taper() // Small change over long (>UINT16_MAX) time period reset_AE(); - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + UINT16_MAX*2UL; - MAPlast = 40; + getMapLast().timeDeltaReadings = UINT16_MAX*2UL; + getMapLast().lastMAPValue = 40; currentStatus.MAP = 41; accelValue = correctionAccel(); //Run the AE calcs TEST_ASSERT_EQUAL(7, currentStatus.mapDOT); @@ -1264,9 +1260,8 @@ static void test_corrections_MAE_no_rpm_taper() // Large change over small time period reset_AE(); - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 1000UL; - MAPlast = 10; + getMapLast().timeDeltaReadings = 1000UL; + getMapLast().lastMAPValue = 10; currentStatus.MAP = 1000; accelValue = correctionAccel(); //Run the AE calcs TEST_ASSERT_EQUAL(6960, currentStatus.mapDOT); @@ -1276,9 +1271,8 @@ static void test_corrections_MAE_no_rpm_taper() // Large change over long (>UINT16_MAX) time period reset_AE(); - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + UINT16_MAX*2UL; - MAPlast = 10; + getMapLast().timeDeltaReadings = UINT16_MAX*2UL; + getMapLast().lastMAPValue = 10; currentStatus.MAP = 1000; accelValue = correctionAccel(); //Run the AE calcs TEST_ASSERT_EQUAL(6930, currentStatus.mapDOT); @@ -1296,9 +1290,8 @@ static void test_corrections_MAE_50pc_rpm_taper() configPage2.aeTaperMin = 10; //1000 configPage2.aeTaperMax = 50; //5000 - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 25000UL; - MAPlast = 40; + getMapLast().timeDeltaReadings = 25000UL; + getMapLast().lastMAPValue = 40; currentStatus.MAP = 50; uint16_t accelValue = correctionAccel(); //Run the AE calcs @@ -1317,9 +1310,8 @@ static void test_corrections_MAE_110pc_rpm_taper() configPage2.aeTaperMin = 10; //1000 configPage2.aeTaperMax = 50; //5000 - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 25000UL; - MAPlast = 40; + getMapLast().timeDeltaReadings = 25000UL; + getMapLast().lastMAPValue = 40; currentStatus.MAP = 50; uint16_t accelValue = correctionAccel(); //Run the AE calcs @@ -1338,9 +1330,8 @@ static void test_corrections_MAE_under_threshold() configPage2.aeTaperMin = 10; //1000 configPage2.aeTaperMax = 50; //5000 - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 25000UL; - MAPlast = 0; + getMapLast().timeDeltaReadings = 25000UL; + getMapLast().lastMAPValue = 0; currentStatus.MAP = 6; configPage2.maeThresh = 241; //Above the reading of 240%/s @@ -1356,9 +1347,8 @@ static void test_corrections_MAE_50pc_warmup_taper() setup_MAE(); disable_AE_taper(); - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 25000UL; - MAPlast = 40; + getMapLast().timeDeltaReadings = 25000UL; + getMapLast().lastMAPValue = 40; currentStatus.MAP = 50; //Set a cold % of 50% increase @@ -1381,9 +1371,8 @@ static void test_corrections_MAE_timout() disable_AE_taper(); // Confirm MAE is on - MAPlast_time = UINT16_MAX*2UL; - MAP_time = MAPlast_time + 25000UL; - MAPlast = 40; + getMapLast().timeDeltaReadings = 25000UL; + getMapLast().lastMAPValue = 40; currentStatus.MAP = 50; configPage2.aeTime = 0; // This should cause the current cycle to expire & the next one to not occur. TEST_ASSERT_EQUAL((100+132), correctionAccel()); diff --git a/test/test_math/main.cpp b/test/test_math/main.cpp index e4094ecc..43ebe9ad 100644 --- a/test/test_math/main.cpp +++ b/test/test_math/main.cpp @@ -5,6 +5,7 @@ extern void testPercent(void); extern void testDivision(void); extern void testBitShift(void); +extern void test_LOW_PASS_FILTER(void); #define UNITY_EXCLUDE_DETAILS @@ -22,6 +23,7 @@ void setup() testPercent(); testDivision(); testBitShift(); + test_LOW_PASS_FILTER(); UNITY_END(); // stop unit testing } diff --git a/test/test_math/test_low_pass_filter.cpp b/test/test_math/test_low_pass_filter.cpp new file mode 100644 index 00000000..9af77de8 --- /dev/null +++ b/test/test_math/test_low_pass_filter.cpp @@ -0,0 +1,44 @@ +#include +#include "maths.h" +#include "../test_utils.h" + +static void test_U16_min(void) { + // Passing zero for the filter value should return the input value + TEST_ASSERT_EQUAL_UINT16(0, LOW_PASS_FILTER(0U, 0U, 0U)); + TEST_ASSERT_EQUAL_UINT16(1234U, LOW_PASS_FILTER(1234U, 0U, 0U)); + TEST_ASSERT_EQUAL_UINT16(1234U, LOW_PASS_FILTER(1234U, 0U, 9999U)); +} + +static void test_U16_max(void) { + // Passing UINT8_MAX for the filter value should make the input close to the previous value + TEST_ASSERT_EQUAL_UINT16(0, LOW_PASS_FILTER(0U, UINT8_MAX, 0U)); + TEST_ASSERT_EQUAL_UINT16(4U, LOW_PASS_FILTER(1234U, UINT8_MAX, 0U)); + TEST_ASSERT_EQUAL_UINT16(9964U, LOW_PASS_FILTER(1234U, UINT8_MAX, 9999U)); +} + +static void test_S16_min(void) { + // Passing zero for the filter value should return the input value + TEST_ASSERT_EQUAL_INT16(0, LOW_PASS_FILTER(0, 0U, 0)); + TEST_ASSERT_EQUAL_INT16(1234, LOW_PASS_FILTER(1234, 0U, 0)); + TEST_ASSERT_EQUAL_INT16(-1234, LOW_PASS_FILTER(-1234, 0U, 0)); + TEST_ASSERT_EQUAL_INT16(1234, LOW_PASS_FILTER(1234, 0U, 9999)); + TEST_ASSERT_EQUAL_INT16(-1234, LOW_PASS_FILTER(-1234, 0U, 9999)); +} + +static void test_S16_max(void) { + // Passing UINT8_MAX for the filter value should make the input close to the previous value + TEST_ASSERT_EQUAL_INT16(0, LOW_PASS_FILTER(0, UINT8_MAX, 0)); + TEST_ASSERT_EQUAL_INT16(4, LOW_PASS_FILTER(1234, UINT8_MAX, 0)); + TEST_ASSERT_EQUAL_INT16(-4, LOW_PASS_FILTER(-1234, UINT8_MAX, 0)); + TEST_ASSERT_EQUAL_INT16(9964, LOW_PASS_FILTER(1234, UINT8_MAX, 9999)); + TEST_ASSERT_EQUAL_INT16(-9964, LOW_PASS_FILTER(-1234, UINT8_MAX, -9999)); +} + +void test_LOW_PASS_FILTER(void) { + SET_UNITY_FILENAME() { + RUN_TEST(test_U16_min); + RUN_TEST(test_U16_max); + RUN_TEST(test_S16_min); + RUN_TEST(test_S16_max); + } +} \ No newline at end of file diff --git a/test/test_sensors/main.cpp b/test/test_sensors/main.cpp new file mode 100644 index 00000000..20118ef9 --- /dev/null +++ b/test/test_sensors/main.cpp @@ -0,0 +1,33 @@ +#include +#include + + +#define UNITY_EXCLUDE_DETAILS + +extern void test_fastMap10Bit(void); +extern void test_map_sampling(void); + +void setup() +{ + pinMode(LED_BUILTIN, OUTPUT); + + // NOTE!!! Wait for >2 secs + // if board doesn't support software reset via Serial.DTR/RTS + delay(2000); + + UNITY_BEGIN(); // IMPORTANT LINE! + + test_fastMap10Bit(); + test_map_sampling(); + + UNITY_END(); // stop unit testing +} + +void loop() +{ + // Blink to indicate end of test + digitalWrite(LED_BUILTIN, HIGH); + delay(250); + digitalWrite(LED_BUILTIN, LOW); + delay(250); +} \ No newline at end of file diff --git a/test/test_sensors/test_fastMap10Bit.cpp b/test/test_sensors/test_fastMap10Bit.cpp new file mode 100644 index 00000000..c718ec7f --- /dev/null +++ b/test/test_sensors/test_fastMap10Bit.cpp @@ -0,0 +1,25 @@ +#include +#include "../test_utils.h" + +extern int16_t fastMap10Bit(uint16_t value, int16_t rangeMin, int16_t rangeMax); + +static void test_fastMap10Bit_negative_range(void) { + TEST_ASSERT_EQUAL_INT(-1500, fastMap10Bit(0, -1500, -500)); + TEST_ASSERT_EQUAL_INT(-501, fastMap10Bit(1023, -1500, -500)); + // Greater than 10-bit input + TEST_ASSERT_NOT_EQUAL_INT(-501, fastMap10Bit(1023*2, -1500, -500)); +} + +static void test_fastMap10Bit_positive_range(void) { + TEST_ASSERT_EQUAL_INT(500, fastMap10Bit(0, 500, 1500)); + TEST_ASSERT_EQUAL_INT(1499, fastMap10Bit(1023, 500, 1500)); + // Greater than 10-bit input + TEST_ASSERT_NOT_EQUAL_INT(1499, fastMap10Bit(1023*2, 500, 1500)); +} + +void test_fastMap10Bit(void) { + SET_UNITY_FILENAME() { + RUN_TEST(test_fastMap10Bit_negative_range); + RUN_TEST(test_fastMap10Bit_positive_range); + } +} diff --git a/test/test_sensors/test_map_sampling.cpp b/test/test_sensors/test_map_sampling.cpp new file mode 100644 index 00000000..27c3a674 --- /dev/null +++ b/test/test_sensors/test_map_sampling.cpp @@ -0,0 +1,355 @@ +#include +#include "../test_utils.h" +#include "sensors_map_structs.h" + +static void test_instantaneous(void) { + extern bool instanteneousMAPReading(void); + + TEST_ASSERT_TRUE(instanteneousMAPReading()); +} + +extern bool cycleAverageMAPReading(const statuses ¤t, const config2 &page2, map_cycle_average_t &cycle_average, map_adc_readings_t &sensorReadings); +extern bool canUseCycleAverage(const statuses ¤t, const config2 &page2); + +static void enable_cycle_average(statuses ¤t, config2 &page2) { + current.RPMdiv100 = 43; + page2.mapSwitchPoint = 15; + current.startRevolutions = 55; + current.hasSync = true; + current.status3 = 0U; +} + +static void test_canUseCycleAverge(void) { + statuses current; + config2 page2; + enable_cycle_average(current, page2); + + TEST_ASSERT_TRUE(canUseCycleAverage(current, page2)); + + current.hasSync = false; + TEST_ASSERT_FALSE(canUseCycleAverage(current, page2)); + current.hasSync = true; + + current.startRevolutions = 1; + TEST_ASSERT_FALSE(canUseCycleAverage(current, page2)); + current.startRevolutions = 55; + + current.RPMdiv100 = page2.mapSwitchPoint-1; + TEST_ASSERT_FALSE(canUseCycleAverage(current, page2)); + current.RPMdiv100 = page2.mapSwitchPoint; + TEST_ASSERT_FALSE(canUseCycleAverage(current, page2)); + current.RPMdiv100 = page2.mapSwitchPoint+1; + TEST_ASSERT_TRUE(canUseCycleAverage(current, page2)); +} + +struct cycleAverageMAPReading_test_data { + statuses current; + config2 page2; + map_cycle_average_t cycle_average; + map_adc_readings_t sensorReadings; +}; + +static void setup_cycle_average(cycleAverageMAPReading_test_data &test_data) { + enable_cycle_average(test_data.current, test_data.page2); + test_data.cycle_average.cycleStartIndex = 0; + test_data.cycle_average.sampleCount = 0; + test_data.cycle_average.emapAdcRunningTotal = 0; + test_data.cycle_average.mapAdcRunningTotal = 0; +} + +static void test_cycleAverageMAPReading_fallback_instantaneous(void) { + cycleAverageMAPReading_test_data test_data; + setup_cycle_average(test_data); + + test_data.current.hasSync = false; + test_data.sensorReadings.mapADC = 0x1234; + test_data.sensorReadings.emapADC = 0x1234; + + TEST_ASSERT_TRUE(cycleAverageMAPReading(test_data.current, test_data.page2, test_data.cycle_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(1, test_data.cycle_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.cycle_average.mapAdcRunningTotal); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.emapADC, test_data.cycle_average.emapAdcRunningTotal); + + // Repeat - should get same result. + TEST_ASSERT_TRUE(cycleAverageMAPReading(test_data.current, test_data.page2, test_data.cycle_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(1, test_data.cycle_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.cycle_average.mapAdcRunningTotal); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.emapADC, test_data.cycle_average.emapAdcRunningTotal); +} + +static void test_cycleAverageMAPReading(void) { + cycleAverageMAPReading_test_data test_data; + setup_cycle_average(test_data); + + test_data.cycle_average.cycleStartIndex = test_data.current.startRevolutions; + test_data.sensorReadings.mapADC = 100; + test_data.sensorReadings.emapADC = 200; + // Accumulate a few samples + TEST_ASSERT_FALSE(cycleAverageMAPReading(test_data.current, test_data.page2, test_data.cycle_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(1, test_data.cycle_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.cycle_average.mapAdcRunningTotal); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.emapADC, test_data.cycle_average.emapAdcRunningTotal); + test_data.sensorReadings.mapADC = 300; + test_data.sensorReadings.emapADC = 500; + TEST_ASSERT_FALSE(cycleAverageMAPReading(test_data.current, test_data.page2, test_data.cycle_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(2, test_data.cycle_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(400, test_data.cycle_average.mapAdcRunningTotal); + TEST_ASSERT_EQUAL_UINT(700, test_data.cycle_average.emapAdcRunningTotal); + ++test_data.current.startRevolutions; + test_data.sensorReadings.mapADC = 500; + test_data.sensorReadings.emapADC = 700; + TEST_ASSERT_FALSE(cycleAverageMAPReading(test_data.current, test_data.page2, test_data.cycle_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(3, test_data.cycle_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(900, test_data.cycle_average.mapAdcRunningTotal); + TEST_ASSERT_EQUAL_UINT(1400, test_data.cycle_average.emapAdcRunningTotal); + + // Leave the current cycle + ++test_data.current.startRevolutions; + TEST_ASSERT_TRUE(cycleAverageMAPReading(test_data.current, test_data.page2, test_data.cycle_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(900/3, test_data.sensorReadings.mapADC); + TEST_ASSERT_EQUAL_UINT(1400/3, test_data.sensorReadings.emapADC); + + TEST_ASSERT_EQUAL_UINT(1, test_data.cycle_average.sampleCount); + TEST_ASSERT_NOT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.cycle_average.mapAdcRunningTotal); + TEST_ASSERT_EQUAL_UINT(500, test_data.cycle_average.mapAdcRunningTotal); + TEST_ASSERT_NOT_EQUAL_UINT(test_data.sensorReadings.emapADC, test_data.cycle_average.emapAdcRunningTotal); + TEST_ASSERT_EQUAL_UINT(700, test_data.cycle_average.emapAdcRunningTotal); +} + +static void test_cycleAverageMAPReading_nosamples(void) { + cycleAverageMAPReading_test_data test_data; + setup_cycle_average(test_data); + + test_data.cycle_average.cycleStartIndex = test_data.current.startRevolutions; + test_data.sensorReadings.mapADC = 100; + test_data.sensorReadings.emapADC = 200; + + ++test_data.current.startRevolutions; + ++test_data.current.startRevolutions; + + TEST_ASSERT_EQUAL_UINT(0, test_data.cycle_average.sampleCount); + TEST_ASSERT_TRUE(cycleAverageMAPReading(test_data.current, test_data.page2, test_data.cycle_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(100, test_data.sensorReadings.mapADC); + TEST_ASSERT_EQUAL_UINT(200, test_data.sensorReadings.emapADC); + + TEST_ASSERT_EQUAL_UINT(1, test_data.cycle_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.cycle_average.mapAdcRunningTotal); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.emapADC, test_data.cycle_average.emapAdcRunningTotal); +} + +extern bool cycleMinimumMAPReading(const statuses ¤t, const config2 &page2, map_cycle_min_t &cycle_min, map_adc_readings_t &sensorReadings); + +struct cycleMinmumMAPReading_test_data { + statuses current; + config2 page2; + map_cycle_min_t cycle_min; + map_adc_readings_t sensorReadings; +}; + +static void setup_cycle_minimum(cycleMinmumMAPReading_test_data &test_data) { + test_data.current.RPMdiv100 = 43; + test_data.page2.mapSwitchPoint = 15; + test_data.cycle_min.cycleStartIndex = 0; + test_data.cycle_min.mapMinimum = UINT16_MAX; +} + +// static void test_cycleMinimumMAPReading_fallback_instantaneous(void) { +// cycleMinmumMAPReading_test_data test_data; +// setup_cycle_minimum(test_data); + +// test_data.current.RPMdiv100 = test_data.page2.mapSwitchPoint - 1U; +// test_data.sensorReadings.mapADC = 300; +// test_data.sensorReadings.emapADC = 500; + +// TEST_ASSERT_TRUE(cycleMinimumMAPReading(test_data.current, test_data.page2, test_data.cycle_min, test_data.sensorReadings)); +// TEST_ASSERT_EQUAL_UINT(UINT16_MAX, test_data.cycle_min.mapMinimum); + +// // Repeat - should get same result. +// TEST_ASSERT_TRUE(cycleMinimumMAPReading(test_data.current, test_data.page2, test_data.cycle_min, test_data.sensorReadings)); +// TEST_ASSERT_EQUAL_UINT(UINT16_MAX, test_data.cycle_min.mapMinimum); +// } + +static void test_cycleMinimumMAPReading(void) { + cycleMinmumMAPReading_test_data test_data; + setup_cycle_minimum(test_data); + + + test_data.cycle_min.cycleStartIndex = test_data.current.startRevolutions; + test_data.sensorReadings.mapADC = 100; + test_data.sensorReadings.emapADC = 200; + // Accumulate a few samples + TEST_ASSERT_FALSE(cycleMinimumMAPReading(test_data.current, test_data.page2, test_data.cycle_min, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.cycle_min.mapMinimum); + test_data.sensorReadings.mapADC = 300; + test_data.sensorReadings.emapADC = 500; + TEST_ASSERT_FALSE(cycleMinimumMAPReading(test_data.current, test_data.page2, test_data.cycle_min, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(100, test_data.cycle_min.mapMinimum); + ++test_data.current.startRevolutions; + test_data.sensorReadings.mapADC = 50; + test_data.sensorReadings.emapADC = 170; + TEST_ASSERT_FALSE(cycleMinimumMAPReading(test_data.current, test_data.page2, test_data.cycle_min, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.cycle_min.mapMinimum); + + // Leave the current cycle + ++test_data.current.startRevolutions; + test_data.sensorReadings.mapADC = 300; + test_data.sensorReadings.emapADC = 500; + TEST_ASSERT_TRUE(cycleMinimumMAPReading(test_data.current, test_data.page2, test_data.cycle_min, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(50U, test_data.sensorReadings.mapADC); + + TEST_ASSERT_NOT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.cycle_min.mapMinimum); + TEST_ASSERT_EQUAL_UINT(300, test_data.cycle_min.mapMinimum); + TEST_ASSERT_EQUAL_UINT8(test_data.current.startRevolutions, test_data.cycle_min.cycleStartIndex); +} + +extern bool canUseEventAverage(const statuses ¤t, const config2 &page2); +extern bool eventAverageMAPReading(const statuses ¤t, const config2 &page2, map_event_average_t &eventAverage, map_adc_readings_t &sensorReadings); + +static void enable_event_average(statuses ¤t, config2 &page2) { + current.RPMdiv100 = 43; + page2.mapSwitchPoint = 15; + current.startRevolutions = 55; + current.hasSync = true; + current.status3 = 0U; + current.engineProtectStatus = 0U; +} + +static void test_canUseEventAverage(void) { + statuses current; + config2 page2; + enable_event_average(current, page2); + + TEST_ASSERT_TRUE(canUseEventAverage(current, page2)); + + current.hasSync = false; + TEST_ASSERT_FALSE(canUseEventAverage(current, page2)); + current.hasSync = true; + + current.startRevolutions = 1; + TEST_ASSERT_FALSE(canUseEventAverage(current, page2)); + current.startRevolutions = 55; + + current.RPMdiv100 = page2.mapSwitchPoint-1; + TEST_ASSERT_FALSE(canUseEventAverage(current, page2)); + current.RPMdiv100 = page2.mapSwitchPoint; + TEST_ASSERT_FALSE(canUseEventAverage(current, page2)); + current.RPMdiv100 = page2.mapSwitchPoint+1; + TEST_ASSERT_TRUE(canUseEventAverage(current, page2)); + + current.engineProtectStatus = 1; + TEST_ASSERT_FALSE(canUseEventAverage(current, page2)); + current.engineProtectStatus = 0; +} + + +struct eventAverageMAPReading_test_data { + statuses current; + config2 page2; + map_event_average_t event_average; + map_adc_readings_t sensorReadings; +}; + +static void setup_event_average(eventAverageMAPReading_test_data &test_data) { + enable_event_average(test_data.current, test_data.page2); + test_data.event_average.eventStartIndex = 0; + test_data.event_average.sampleCount = 0; + test_data.event_average.mapAdcRunningTotal = 0; + ignitionCount = 0; +} + +static void test_eventAverageMAPReading_fallback_instantaneous(void) { + eventAverageMAPReading_test_data test_data; + setup_event_average(test_data); + + test_data.current.hasSync = false; + test_data.sensorReadings.mapADC = 0x1234; + test_data.sensorReadings.emapADC = 0x1234; + + TEST_ASSERT_TRUE(eventAverageMAPReading(test_data.current, test_data.page2, test_data.event_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(1, test_data.event_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.event_average.mapAdcRunningTotal); + + // Repeat - should get same result. + TEST_ASSERT_TRUE(eventAverageMAPReading(test_data.current, test_data.page2, test_data.event_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(1, test_data.event_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.event_average.mapAdcRunningTotal); +} + + +static void test_eventAverageMAPReading(void) { + eventAverageMAPReading_test_data test_data; + setup_event_average(test_data); + + test_data.event_average.eventStartIndex = (uint8_t)ignitionCount; + test_data.sensorReadings.mapADC = 100; + test_data.sensorReadings.emapADC = 200; + // Accumulate a few samples + TEST_ASSERT_FALSE(eventAverageMAPReading(test_data.current, test_data.page2, test_data.event_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(1, test_data.event_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.event_average.mapAdcRunningTotal); + test_data.sensorReadings.mapADC = 300; + test_data.sensorReadings.emapADC = 500; + TEST_ASSERT_FALSE(eventAverageMAPReading(test_data.current, test_data.page2, test_data.event_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(2, test_data.event_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(400, test_data.event_average.mapAdcRunningTotal); + test_data.sensorReadings.mapADC = 500; + test_data.sensorReadings.emapADC = 700; + TEST_ASSERT_FALSE(eventAverageMAPReading(test_data.current, test_data.page2, test_data.event_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(3, test_data.event_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(900, test_data.event_average.mapAdcRunningTotal); + + // Leave the current cycle + ++ignitionCount; + TEST_ASSERT_TRUE(eventAverageMAPReading(test_data.current, test_data.page2, test_data.event_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(900/3, test_data.sensorReadings.mapADC); + + TEST_ASSERT_EQUAL_UINT(1, test_data.event_average.sampleCount); + TEST_ASSERT_NOT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.event_average.mapAdcRunningTotal); + TEST_ASSERT_EQUAL_UINT(500, test_data.event_average.mapAdcRunningTotal); +} + +static void test_eventAverageMAPReading_nosamples(void) { + eventAverageMAPReading_test_data test_data; + setup_event_average(test_data); + + test_data.event_average.eventStartIndex = (uint8_t)ignitionCount; + test_data.sensorReadings.mapADC = 100; + test_data.sensorReadings.emapADC = 200; + + ++ignitionCount; + ++ignitionCount; + + TEST_ASSERT_EQUAL_UINT(0, test_data.event_average.sampleCount); + TEST_ASSERT_TRUE(eventAverageMAPReading(test_data.current, test_data.page2, test_data.event_average, test_data.sensorReadings)); + TEST_ASSERT_EQUAL_UINT(100, test_data.sensorReadings.mapADC); + TEST_ASSERT_EQUAL_UINT(200, test_data.sensorReadings.emapADC); + + TEST_ASSERT_EQUAL_UINT(1, test_data.event_average.sampleCount); + TEST_ASSERT_EQUAL_UINT(test_data.sensorReadings.mapADC, test_data.event_average.mapAdcRunningTotal); +} + +extern uint16_t validateFilterMapSensorReading(uint16_t reading, uint8_t alpha, uint16_t prior); + +static void test_validateFilterMapSensorReading(void) { + TEST_ASSERT_EQUAL_UINT(0, validateFilterMapSensorReading(0, 0, 0)); + TEST_ASSERT_EQUAL_UINT(100, validateFilterMapSensorReading(0, 0, 100)); + TEST_ASSERT_EQUAL_UINT(333, validateFilterMapSensorReading(333, 0, 100)); + TEST_ASSERT_EQUAL_UINT(217, validateFilterMapSensorReading(333, 127, 100)); +} + +void test_map_sampling(void) { + SET_UNITY_FILENAME() { + RUN_TEST(test_instantaneous); + RUN_TEST(test_canUseCycleAverge); + RUN_TEST(test_cycleAverageMAPReading_fallback_instantaneous); + RUN_TEST(test_cycleAverageMAPReading); + RUN_TEST(test_cycleAverageMAPReading_nosamples); + // RUN_TEST(test_cycleMinimumMAPReading_fallback_instantaneous); + RUN_TEST(test_cycleMinimumMAPReading); + RUN_TEST(test_canUseEventAverage); + RUN_TEST(test_eventAverageMAPReading_fallback_instantaneous); + RUN_TEST(test_eventAverageMAPReading); + RUN_TEST(test_eventAverageMAPReading_nosamples); + RUN_TEST(test_validateFilterMapSensorReading); + } +} \ No newline at end of file