Implemented "full" ignition timing system

This is a first implementation of a "full" timing system, with proper
handling for cases wher the ignition charge time overlaps with the
compression pulse, the spark has a more complex state machine system
with the option to defer charge from the intake pulse to the compression
pulse, and further defferal of the spark release schedule to the ISR
that triggers when the output compare triggers for the charge

Preliminary error counters have been put in place, they still require
handling error conditions that would force a soft shutdown on the
ignition IC. Alongside this the code still needs to be verified so that
all non critical errors (ie missed charge schedules) reset the state
machine and increment a global spark miss counter to avoid having these
errors breaking the state machine as they can easily be recovered from

Cases where the coil would already be charging and an error occurs
require as stated above a full recovery procedure with soft shutdown of
the ignition IC and a clean cycle of the state machine to ensure a full
recovery
This commit is contained in:
h3cx committed 2026-09-26 22:11:51 +02:00
1 parent 0ea7b83bea
commit 7502b354dc
15 files changed
+444 -195

No files matched your search

+88 -30
View File
@@ -8,36 +8,42 @@
#include "macros.h"
#include "main.h"
#include "ring_buffer.h"
#include "stm32h747xx.h"
#include "stm32h7xx.h"
#include "stm32h7xx_hal_tim.h"
#include "tasks.h"
#include "timer_utils.h"
#include <stdint.h>
static inline void TIM2_CH3_SetOCMode(uint32_t oc_mode) {
MODIFY_REG(TIM2->CCMR2, TIM_CCMR2_OC3M | TIM_CCMR2_OC3M_3, oc_mode);
}
void crankHandler(void *argument) {
for (;;) {
osThreadFlagsWait(0x01, osFlagsWaitAny, osWaitForever);
DEBUG_LOG("Crank pulse detected at: %lu\n\r",
ringBufferRead(&state_g.crank_RB, 0));
// FILTER
/* WINDOW FILTER
*
* This filter limits incoming signals to a certain max percentage deviation
* to blank out false triggers that would be impossible in real life
*
* It computes the percantage difference between d1a and d1b then if this
* has an absolute value over a certain threshold the signal is discarded
*/
if (state_g.sync_state == SYNC_OK) {
uint32_t d1a = CRANK(0) - CRANK(1);
uint32_t d1b = CRANK(1) - CRANK(2);
int32_t delta_percentage =
((int64_t)d1a - (int64_t)d1b) * 100 / (int64_t)d1b;
DEBUG_LOG("%ld\n\r", delta_percentage);
if (delta_percentage > 40 || delta_percentage < -40) {
// state_g.sync_state = SYNC_NOT_OK;
// state_g.crank_state = CYCLE_UNKNOWN;
// ringBufferRevert(&state_g.crank_RB, 1);
state_g.crank_RB.w_head -= 1;
// Updated to use clean API
// state_g.crank_RB.w_head -= 1;
ringBufferRevert(&state_g.crank_RB, 1);
continue;
}
}
// INCREMENT SWITCH
switch (state_g.crank_state) {
case CYCLE_COMPRESSION: {
@@ -81,31 +87,83 @@ void crankHandler(void *argument) {
// SPARK SCHEDULE
if (state_g.sync_state == SYNC_OK) {
switch (state_g.crank_state) {
case CYCLE_INTAKE:
case CYCLE_COMPRESSION: {
DEBUG_LOG("Spark schedule reached, congrats\n\r");
uint32_t d1a = CRANK(0) - CRANK(1);
// TODO: Map interpolation rather than SPARK_ADVANCE CONSTANT
uint32_t d_spark = d1a * (45 - SPARK_ADVANCE) / 180;
uint32_t t_spark = CRANK(0) + d_spark;
// TODO: schedule spark
if (state_g.current_spark_state != SPARK_CHARGING) {
uint32_t force_charge_time = __HAL_TIM_GET_COUNTER(&htim2) + 2000;
// TIM2_CH3_SetOCMode(TIM_OCMODE_ACTIVE);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, force_charge_time);
state_g.next_spark_state = SPARK_CHARGING;
osThreadFlagsWait(0x02, osFlagsWaitAny, osWaitForever);
uint32_t d_spark = 0;
if (state_g.crank_state == CYCLE_INTAKE) {
d_spark += d1a;
}
DEBUG_LOG("Spark scheduled for: %lu , current time: %lu\n\r", t_spark,
__HAL_TIM_GET_COUNTER(&htim2));
// TIM2_CH3_SetOCMode(TIM_OCMODE_INACTIVE);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, t_spark);
state_g.next_spark_state = SPARK_IDLE;
d_spark += d1a * (45 - SPARK_ADVANCE) / 180;
uint32_t t_spark = CRANK(0) + d_spark;
uint32_t t_spark_charge = t_spark - SPARK_CHARGE_TIME;
if (state_g.crank_state == CYCLE_INTAKE) {
if (t_spark_charge > CRANK(0) + 500 + ((d1a * 102U) / 100U)) {
state_g.charge_defer = true;
} else {
state_g.charge_defer = false;
if (timer_safe_to_arm(t_spark_charge, &htim2)) {
state_g.current_spark_state = SPARK_PENDING_CHARGE;
TIM2_CH3_SetOCMode(TIM2_CH3_OC_ACTIVE);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, t_spark_charge);
} else {
state_g.current_spark_state = SPARK_IDLE;
state_g.spark_errors.overspeed_error_ctr++;
}
}
break;
}
if (state_g.charge_defer) {
state_g.spark_defer = true;
state_g.t_defer_spark = t_spark;
state_g.charge_defer = false;
if (timer_safe_to_arm(t_spark_charge, &htim2)) {
state_g.current_spark_state = SPARK_PENDING_CHARGE;
TIM2_CH3_SetOCMode(TIM2_CH3_OC_ACTIVE);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, t_spark_charge);
} else {
state_g.spark_errors.defer_charge_error_ctr++;
}
break;
}
switch (state_g.current_spark_state) {
case SPARK_PENDING_CHARGE: {
if (timer_safe_to_arm(t_spark_charge, &htim2)) {
state_g.current_spark_state = SPARK_PENDING_CHARGE;
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, t_spark_charge);
} else {
state_g.spark_errors.revised_charge_error_ctr++;
}
state_g.spark_defer = true;
state_g.t_defer_spark = t_spark;
break;
}
case SPARK_CHARGING: {
if (timer_safe_to_arm(t_spark, &htim2)) {
state_g.current_spark_state = SPARK_PENDING_RELEASE;
TIM2_CH3_SetOCMode(TIM2_CH3_OC_INACTIVE);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, t_spark);
state_g.spark_defer = false;
} else {
// TODO: Implement SSD on ignition IC if charging and complete
// recovery
state_g.spark_errors.task_release_error_ctr++;
}
break;
}
default: {
state_g.spark_errors.state_error_ctr++;
break;
}
}
break;
}
case CYCLE_EXHAUST: {
uint32_t t_injection =
CRANK(0) + (45 + INJECTION_PHASE) * (CRANK(4) - CRANK(0)) / 720;
// TODO: Schedule injection
break;
}