Skip to content

Conversation

@SamrutGadde
Copy link
Contributor

Addresses issue #3.

  • Defines a SyncState struct which contains all information related to cam and crank position.
  • Using a high priority engine task, we look for sync by using two cam teeth.
    • Look at the comment in detectSync() to see how our crank and cam line up with this implementation.
  • To get proper crank angle, we use an Exponential Moving Average to smooth out the instantaneous time period between crank teeth.

@SamrutGadde SamrutGadde self-assigned this Oct 12, 2025
@SamrutGadde
Copy link
Contributor Author

SamrutGadde commented Oct 19, 2025

HITL Evidence:

Optimal Crank/Cam:
image

Last Cam Tooth Wrong Position:
Makes sense that crank index is being reset to 10, since other two deltas are not valid.
image

Used with latest commit from ECU_HITL.

Copy link

@KshitijKapoor8 KshitijKapoor8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I could tell, all of the logic and code is sound. I had some performance and interrupt-context concerns.

Might be worth it to consult with someone who knows about engines to go over high level synch logic with before merging.

volatile double tooth_period_us = 0.0;

// Engine phase (0 = 0-360, 1 = 360-720).
bool engine_phase;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be marked volatile, as we use it in both ISR and task

volatile uint32_t last_cam_time_us = 0;

// Instantaneous period between crank teeth (in microseconds).
volatile double tooth_period_us = 0.0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using a 64 bit value on a 32 bit processor in ISR is probably a bad idea, as it's not atomic and can tear mid write

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably just avoid most floating point arithmetic in ISR context, as that could be messy at high RPMs

Comment on lines +77 to +79
inline uint8_t get_cam_delta() {
return cam_crank_counter - last_cam_crank_counter;
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just as a safety thing, if this underflows we would see garbage data that we would not thing is garbage
doing this wrap safe would be:

uint64_t diff = (uint64_t)cam_crank_counter - (uint64_t)last_cam_crank_counter;
return (uint8_t)(diff % NUM_CRANK_TEETH)

Comment on lines +17 to +39
for (;;) {
// Wait for sync to be acquired by cam tooth interrupts
if (!syncState.synced) {
// Sleep briefly to avoid busy-waiting
osDelay(1);
continue;
}

// We're synced - perform engine control operations
float current_angle = get_current_engine_angle();

// TODO: Schedule fuel injection events
// TODO: Schedule ignition events

// ULOG_INFO("EngAngle: %.2f", current_angle);

if (!syncState.synced) {
ULOG_WARNING("Lost sync! Re-acquiring...");
continue;
}

// Sleep briefly - actual timing is interrupt-driven
osDelay(1);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be wrong, but I think this is a TOCTOU issue with syncState.synced

// crank teeth and 3 cam teeth, two opposing one
// 30deg offset.
#define NUM_CRANK_TEETH 12
#define DEG_BTWN_TEETH (uint8_t)(360 / NUM_CRANK_TEETH)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be kept as float as this is used in float math and would require constant promotions i think

Comment on lines +94 to +95
if (syncState.crank_index == NUM_CRANK_TEETH - 1) {
syncState.engine_phase = !syncState.engine_phase;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure we can always assume this to be true? what happens if we get shifted ever?

Comment on lines +108 to +112
// If we're synced, validate that the cam delta matches expectations
if (syncState.synced && syncState.last_cam_crank_counter > 0) {
// Valid deltas are 2, 10, or 12 teeth between cam pulses
// Any other delta means we lost sync (missed teeth)
if (delta != 2 && delta != 10 && delta != 12) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why these numbers?

* @param None
* @retval Fraction of tooth passed (0.0 to 1.0)
*/
float get_current_fraction_of_tooth();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unused

* @param None
* @retval Current engine angle in degrees (0.0 to 720.0)
*/
float get_current_engine_angle();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unused

@@ -0,0 +1,362 @@
#include "mock_hal.h"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a test to verify that the EMA updates to tooth_period_us is applied and correct

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants