Skip to content
63 changes: 62 additions & 1 deletion Inc/HALAL/Models/SPI/SPI2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#ifndef SPI2_HPP
#define SPI2_HPP

#include "hal_wrapper.h"
#include "C++Utilities/CppImports.hpp"
#include "HALAL/Models/GPIO.hpp"
#include "HALAL/Models/Pin.hpp"
Expand Down Expand Up @@ -520,6 +521,29 @@ struct SPIDomain {
SPI_TypeDef* instance;

volatile bool* operation_flag = nullptr;
volatile uint32_t error_count = 0;
volatile bool was_aborted = false;

bool recover() {
// Abort any ongoing SPI operation
HAL_SPI_Abort(&hspi);

uint32_t newErrorCount = error_count + 1; // Cause volatile in c++ freacking sucks
error_count = newErrorCount;
was_aborted = true;
operation_flag = nullptr;

// Reset SPI state
auto status = HAL_SPI_DeInit(&hspi);
if (status != HAL_OK) {
return false;
}
status = HAL_SPI_Init(&hspi);
if (status != HAL_OK) {
return false;
}
return true;
}
};

static inline Instance* spi_instances[max_instances];
Expand All @@ -543,6 +567,23 @@ struct SPIDomain {

SPIWrapper(Instance& instance) : spi_instance{instance} {}

/**
* @brief Aborts any ongoing SPI operation and recovers the peripheral.
*/
void abort_and_recover() { spi_instance.recover(); }

/**
* @brief Checks if the SPI instance was aborted since the last reset.
*/
bool was_aborted() const { return spi_instance.was_aborted; }

void clear_abort_flag() { spi_instance.was_aborted = false; }

/**
* @brief Gets the error count for this SPI instance.
*/
uint32_t get_error_count() const { return spi_instance.error_count; }

/**
* @brief Sends data over SPI in blocking mode.
*/
Expand Down Expand Up @@ -979,6 +1020,26 @@ struct SPIDomain {
}
}

/**
* @brief Aborts any ongoing SPI operation and recovers the peripheral.
*/
void abort_and_recover() { spi_instance.recover(); }

/**
* @brief Gets the error count for this SPI instance.
*/
uint32_t get_error_count() const { return spi_instance.error_count; }

/**
* @brief Indicates whether the last operation was aborted.
*/
bool was_aborted() const { return spi_instance.was_aborted; }

/**
* @brief Clears the aborted state flag for this SPI instance.
*/
void clear_abort_flag() { spi_instance.was_aborted = false; }

/**
* @brief Listens for data over SPI using DMA, uses an optional operation flag to signal
* completion.
Expand Down Expand Up @@ -1458,4 +1519,4 @@ struct SPIDomain {

} // namespace ST_LIB

#endif // SPI2_HPP
#endif // SPI2_HPP
2 changes: 2 additions & 0 deletions Inc/MockedDrivers/common.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <stdint.h>

#ifndef glue
#define glue_(a, b) a##b
#define glue(a, b) glue_(a, b)
Expand Down
1 change: 1 addition & 0 deletions Inc/MockedDrivers/stm32h7xx_hal_mock.h
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,7 @@ uint32_t HAL_ADC_GetState(const ADC_HandleTypeDef* hadc);
uint32_t HAL_ADC_GetError(const ADC_HandleTypeDef* hadc);

HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef* hspi);
HAL_StatusTypeDef HAL_SPI_DeInit(SPI_HandleTypeDef* hspi);
HAL_StatusTypeDef HAL_SPI_Abort(SPI_HandleTypeDef* hspi);
HAL_StatusTypeDef
HAL_SPI_Transmit(SPI_HandleTypeDef* hspi, uint8_t* pData, uint16_t Size, uint32_t Timeout);
Expand Down
21 changes: 20 additions & 1 deletion Src/HALAL/Models/SPI/SPI2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef* hspi) {

if (inst->operation_flag != nullptr) {
*(inst->operation_flag) = true;
inst->operation_flag = nullptr; // Clear pointer after setting flag
}
}

Expand All @@ -114,24 +115,42 @@ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef* hspi) {
auto& spi_instances = ST_LIB::SPIDomain::spi_instances;

uint32_t error_code = hspi->ErrorCode;
ST_LIB::SPIDomain::Instance* inst = nullptr;
uint32_t inst_idx = 0;

if (spi_instances[0] != nullptr && hspi == &spi_instances[0]->hspi) {
inst = spi_instances[0];
inst_idx = 0;
} else if (spi_instances[1] != nullptr && hspi == &spi_instances[1]->hspi) {
inst = spi_instances[1];
inst_idx = 1;
} else if (spi_instances[2] != nullptr && hspi == &spi_instances[2]->hspi) {
inst = spi_instances[2];
inst_idx = 2;
} else if (spi_instances[3] != nullptr && hspi == &spi_instances[3]->hspi) {
inst = spi_instances[3];
inst_idx = 3;
} else if (spi_instances[4] != nullptr && hspi == &spi_instances[4]->hspi) {
inst = spi_instances[4];
inst_idx = 4;
} else if (spi_instances[5] != nullptr && hspi == &spi_instances[5]->hspi) {
inst = spi_instances[5];
inst_idx = 5;
} else {
ErrorHandler("SPI IRQ Callback called but instance is null");
return;
}

ErrorHandler("SPI%i failed with error number %u", inst_idx + 1, error_code);
if (!inst->recover()) {
ErrorHandler(
"SPI%i failed with error number %u (recovery failed, error count: %u)",
inst_idx + 1,
error_code,
inst->error_count
);
}

(void)error_code;
(void)inst_idx;
}
}
10 changes: 10 additions & 0 deletions Src/MockedDrivers/mocked_hal_spi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ extern "C" HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef* hspi) {
return take_status();
}

extern "C" HAL_StatusTypeDef HAL_SPI_DeInit(SPI_HandleTypeDef* hspi) {
g_state.last_handle = hspi;
if (hspi == nullptr) {
return HAL_ERROR;
}
hspi->State = HAL_SPI_STATE_RESET;
hspi->ErrorCode = HAL_SPI_ERROR_NONE;
return take_status();
}

extern "C" HAL_StatusTypeDef HAL_SPI_Abort(SPI_HandleTypeDef* hspi) {
g_state.calls[static_cast<std::size_t>(ST_LIB::MockedHAL::SPIOperation::Abort)]++;
g_state.last_handle = hspi;
Expand Down
42 changes: 39 additions & 3 deletions Tests/spi2_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,18 +299,54 @@ TEST_F(SPI2Test, CallbacksWithUnknownHandleTriggerErrorPath) {
EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1);
}

TEST_F(SPI2Test, ErrorCallbackOnKnownHandleTriggersErrorPath) {
TEST_F(SPI2Test, ErrorCallbackOnKnownHandleRecoversWithoutErrorPath) {
ST_LIB::TestErrorHandler::set_fail_on_error(false);
init_spi<ST_LIB::SPIDomain::SPIMode::MASTER, ST_LIB::SPIConfigTypes::DataSize::SIZE_8BIT>(
20'000'000U
auto& instance =
init_spi<ST_LIB::SPIDomain::SPIMode::MASTER, ST_LIB::SPIConfigTypes::DataSize::SIZE_8BIT>(
20'000'000U
);
ST_LIB::SPIDomain::SPIWrapper<master8_request> spi(instance);

const auto before_abort =
ST_LIB::MockedHAL::spi_get_call_count(ST_LIB::MockedHAL::SPIOperation::Abort);
const auto before_init =
ST_LIB::MockedHAL::spi_get_call_count(ST_LIB::MockedHAL::SPIOperation::Init);

auto* hspi = ST_LIB::MockedHAL::spi_get_last_handle();
ASSERT_NE(hspi, nullptr);
hspi->ErrorCode = 0x55U;
HAL_SPI_ErrorCallback(hspi);

EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 0);
EXPECT_EQ(
ST_LIB::MockedHAL::spi_get_call_count(ST_LIB::MockedHAL::SPIOperation::Abort),
before_abort + 1U
);
EXPECT_EQ(
ST_LIB::MockedHAL::spi_get_call_count(ST_LIB::MockedHAL::SPIOperation::Init),
before_init + 1U
);
EXPECT_EQ(spi.get_error_count(), 1U);
EXPECT_TRUE(spi.was_aborted());
}

TEST_F(SPI2Test, ErrorCallbackOnKnownHandleTriggersErrorPathWhenRecoveryFails) {
ST_LIB::TestErrorHandler::set_fail_on_error(false);
auto& instance =
init_spi<ST_LIB::SPIDomain::SPIMode::MASTER, ST_LIB::SPIConfigTypes::DataSize::SIZE_8BIT>(
20'000'000U
);
ST_LIB::SPIDomain::SPIWrapper<master8_request> spi(instance);

auto* hspi = ST_LIB::MockedHAL::spi_get_last_handle();
ASSERT_NE(hspi, nullptr);
hspi->ErrorCode = 0x55U;
ST_LIB::MockedHAL::spi_set_status(HAL_ERROR);
HAL_SPI_ErrorCallback(hspi);

EXPECT_EQ(ST_LIB::TestErrorHandler::call_count, 1);
EXPECT_EQ(spi.get_error_count(), 1U);
EXPECT_TRUE(spi.was_aborted());
}

TEST_F(SPI2Test, SlaveWrapperDMAOperations) {
Expand Down