diff --git a/Inc/HALAL/Models/SPI/SPI2.hpp b/Inc/HALAL/Models/SPI/SPI2.hpp index 18ab84c9..bdf7a2e1 100644 --- a/Inc/HALAL/Models/SPI/SPI2.hpp +++ b/Inc/HALAL/Models/SPI/SPI2.hpp @@ -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" @@ -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]; @@ -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. */ @@ -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. @@ -1458,4 +1519,4 @@ struct SPIDomain { } // namespace ST_LIB -#endif // SPI2_HPP \ No newline at end of file +#endif // SPI2_HPP diff --git a/Inc/MockedDrivers/common.hpp b/Inc/MockedDrivers/common.hpp index 38804014..dfc023ed 100644 --- a/Inc/MockedDrivers/common.hpp +++ b/Inc/MockedDrivers/common.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #ifndef glue #define glue_(a, b) a##b #define glue(a, b) glue_(a, b) diff --git a/Inc/MockedDrivers/stm32h7xx_hal_mock.h b/Inc/MockedDrivers/stm32h7xx_hal_mock.h index f556b313..8ae94e74 100644 --- a/Inc/MockedDrivers/stm32h7xx_hal_mock.h +++ b/Inc/MockedDrivers/stm32h7xx_hal_mock.h @@ -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); diff --git a/Src/HALAL/Models/SPI/SPI2.cpp b/Src/HALAL/Models/SPI/SPI2.cpp index f860af79..16f8dbbe 100644 --- a/Src/HALAL/Models/SPI/SPI2.cpp +++ b/Src/HALAL/Models/SPI/SPI2.cpp @@ -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 } } @@ -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; } } \ No newline at end of file diff --git a/Src/MockedDrivers/mocked_hal_spi.cpp b/Src/MockedDrivers/mocked_hal_spi.cpp index dff3c52a..cce4643c 100644 --- a/Src/MockedDrivers/mocked_hal_spi.cpp +++ b/Src/MockedDrivers/mocked_hal_spi.cpp @@ -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(ST_LIB::MockedHAL::SPIOperation::Abort)]++; g_state.last_handle = hspi; diff --git a/Tests/spi2_test.cpp b/Tests/spi2_test.cpp index 46f13dbf..e0d50fb6 100644 --- a/Tests/spi2_test.cpp +++ b/Tests/spi2_test.cpp @@ -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( - 20'000'000U + auto& instance = + init_spi( + 20'000'000U + ); + ST_LIB::SPIDomain::SPIWrapper 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( + 20'000'000U + ); + ST_LIB::SPIDomain::SPIWrapper 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) {