Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Unit Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
gcc \
g++ \
ninja-build

- name: Run tests
run: |
chmod +x run_tests.sh
./run_tests.sh

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test/build/Testing/
if-no-files-found: ignore
23 changes: 13 additions & 10 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ cmake_minimum_required(VERSION 3.22)
# User is free to modify the file as much as necessary
#

# Include toolchain file before project() if not already set
# (CMake presets will set this automatically)
if(NOT CMAKE_TOOLCHAIN_FILE)
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/gcc-arm-none-eabi.cmake")
endif()

# Setup compiler settings
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
Expand All @@ -21,18 +27,15 @@ endif()
# Set the project name
set(CMAKE_PROJECT_NAME CustomECU)

# Include toolchain file
include("cmake/gcc-arm-none-eabi.cmake")
# Core project settings
project(${CMAKE_PROJECT_NAME})
message("Build type: " ${CMAKE_BUILD_TYPE})

# Enable compile command to ease indexing with e.g. clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)

# Enable CMake support for ASM and C languages
enable_language(C ASM)

# Core project settings
project(${CMAKE_PROJECT_NAME})
message("Build type: " ${CMAKE_BUILD_TYPE})
# Enable CMake support for ASM language (after project)
enable_language(ASM)

# Create an executable object type
add_executable(${CMAKE_PROJECT_NAME})
Expand All @@ -48,10 +51,10 @@ target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
# Add user sources here
Core/Src/tasks.c
Core/Src/us_timer.c
Core/Src/ulog.c
Core/Src/sampling.c
Core/Src/sampling.cpp
Core/Src/engine.cpp
)

# Add include paths
Expand Down
16 changes: 12 additions & 4 deletions Core/Inc/tasks.h → Core/Inc/engine.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#ifndef __TASKS_H
#define __TASKS_H
#ifndef __ENGINE_H
#define __ENGINE_H
#ifdef __cplusplus
extern "C" {
#endif

#include <stdbool.h>

// Function Prototypes

Expand All @@ -9,9 +14,12 @@
* spark and fuel injection events.
*
* This task is critical for engine operation and should run at a high priority.
* @param argument: Not used
* @retval None
* @param argument: Not used.
* @retval None.
*/
void criticalEngineTask(void *argument);

#ifdef __cplusplus
}
#endif
#endif /* __ENGINE_H */
8 changes: 0 additions & 8 deletions Core/Inc/sampling.h

This file was deleted.

84 changes: 84 additions & 0 deletions Core/Inc/sampling.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#ifndef __SAMPLING_H
#define __SAMPLING_H
#include <stdbool.h>
#include <stdint.h>

/**
* @brief Callback for when a cam tooth is detected.
* @param None
* @retval None
*/
void on_cam_tooth();

/**
* @brief Callback for when a crank tooth is detected.
* @param None
* @retval None
*/
void on_crank_tooth();

/**
* @brief Get the current fraction of tooth passed since last crank tooth.
* @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


/**
* @brief Get the current engine angle in degrees.
* @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



/**
* @brief Handles synchronization detection with cam and crank signals.
* Specifically for the Honda CBR600CC engine with 12 equally-spaced
* crank teeth and 3 cam teeth, with one offset 30deg.
*
* Updates synced bool in SyncState struct.
*
* @param None.
* @retval None.
*/
void detectSync(void);

// General Engine Synchronization State.
struct SyncState {
// Whether we have locked synchronization or not.
bool synced;

// Crank index.
volatile uint8_t crank_index = 0;

// Monotonic crank counter.
volatile uint64_t crank_counter = 0;

// Monotonic cam crank counter.
volatile uint64_t cam_crank_counter = 0;

// Monotonic cam crank counter for last cam seen.
volatile uint64_t last_cam_crank_counter = 0;

// Last time in micros when we saw a crank tooth.
volatile uint32_t last_crank_time_us = 0;

// Last time in micros when we saw a cam tooth.
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


// 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


// Gets the delta in crank teeth between the last two cam teeth.
inline uint8_t get_cam_delta() {
return cam_crank_counter - last_cam_crank_counter;
};
Comment on lines +77 to +79

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)

};

extern struct SyncState syncState;

#endif
2 changes: 1 addition & 1 deletion Core/Inc/stm32f4xx_it.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
#define __STM32F4xx_IT_H

#ifdef __cplusplus
extern "C" {
extern "C" {
#endif

/* Private includes ----------------------------------------------------------*/
Expand Down
7 changes: 7 additions & 0 deletions Core/Inc/us_timer.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#ifndef __US_TIMER_H
#define __US_TIMER_H
#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>

/**
Expand All @@ -14,4 +18,7 @@ void init_us_timer(void);
*/
uint32_t get_micros(void);

#ifdef __cplusplus
}
#endif
#endif /* __US_TIMER_H */
41 changes: 41 additions & 0 deletions Core/Src/engine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <cmsis_os2.h>
#include <stdint.h>

#include "engine.h"
#include "sampling.hpp"
#include "ulog.h"
#include "us_timer.h"

uint32_t last_time = 0;
uint32_t time = 0;

void criticalEngineTask(void *argument) {
(void)argument; // Unused parameter

ULOG_INFO("Engine task started - waiting for sync...");

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);
Comment on lines +17 to +39

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

}
}
5 changes: 3 additions & 2 deletions Core/Src/freertos.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "us_timer.h"
#include "tasks.h"
#include "engine.h"
#include "ulog.h"
#include "us_timer.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
Expand Down
10 changes: 4 additions & 6 deletions Core/Src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,7 @@ int main(void)
/* USER CODE END 2 */

/* Init scheduler */
osKernelInitialize();

/* Call init function for freertos objects (in cmsis_os2.c) */
osKernelInitialize(); /* Call init function for freertos objects (in cmsis_os2.c) */
MX_FREERTOS_Init();

/* Start scheduler */
Expand Down Expand Up @@ -180,7 +178,8 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
/* USER CODE BEGIN Callback 0 */

/* USER CODE END Callback 0 */
if (htim->Instance == TIM1) {
if (htim->Instance == TIM1)
{
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
Expand All @@ -199,8 +198,7 @@ void Error_Handler(void)

/* USER CODE END Error_Handler_Debug */
}

#ifdef USE_FULL_ASSERT
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
Expand Down
20 changes: 0 additions & 20 deletions Core/Src/sampling.c

This file was deleted.

Loading