From cf09e7beb1447fcbd07dfba0df696a12c7d290da Mon Sep 17 00:00:00 2001 From: CarlWachter Date: Sun, 3 Nov 2024 12:42:37 +0100 Subject: [PATCH 01/16] gitignore: ds_store --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 64dcbf5..3c4eb09 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ docs/latex # Config include/sta/config.hpp +# Mac OS X +.DS_Store From f0c5faf29ecc685749b48a161f8d2f7e16980ddd Mon Sep 17 00:00:00 2001 From: CarlWachter Date: Sun, 3 Nov 2024 12:43:46 +0100 Subject: [PATCH 02/16] refactor: moved startup from rtos2-utils to TACOS --- README.md | 6 ++--- include/sta/tacos/c_api/startup.h | 22 +++++++++++++++++ src/startup.cpp | 40 +++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 include/sta/tacos/c_api/startup.h diff --git a/README.md b/README.md index 5fec4c4..0a16532 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,13 @@ Properties -> C/C++ General -> Paths and Symbols -> Includes -> GNU C++ -> Add.. Properties -> C/C++ General -> Paths and Symbols -> Source Location -> Add Folder... ``` -Create a new thread via the project's IOC and call `startALPAKA()` from this thread. If your thread is called `defaultTask`, the corresponding function `StartDefaultTask` generated in `Core/Src/freertos.c` should look like this: +Create a new thread via the project's IOC and call `startTACOS()` from this thread. If your thread is called `defaultTask`, the corresponding function `StartDefaultTask` generated in `Core/Src/freertos.c` should look like this: ``` void StartDefaultTask(void *argument) { /* USER CODE BEGIN StartDefaultTask */ - extern void startALPAKA(void *); - startALPAKA(argument); + extern void startTACOS(void *); + startTACOS(argument); /* Infinite loop */ for(;;) diff --git a/include/sta/tacos/c_api/startup.h b/include/sta/tacos/c_api/startup.h new file mode 100644 index 0000000..3876718 --- /dev/null +++ b/include/sta/tacos/c_api/startup.h @@ -0,0 +1,22 @@ +#ifndef STA_TACOS_C_API_STARTUP_H +#define STA_TACOS_C_API_STARTUP_H + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @brief + * + * @param arg Default task argument + */ +void startTACOS(void * arg); + + +#ifdef __cplusplus +} +#endif + + +#endif // STA_TACOS_C_API_STARTUP_H diff --git a/src/startup.cpp b/src/startup.cpp index 6934df5..0f450f5 100644 --- a/src/startup.cpp +++ b/src/startup.cpp @@ -21,11 +21,18 @@ // sta-core-specific imports. #include +#include #include #include +#include #include +// rtos2-utils-specific includes. +#include +#include + // Tacos-specific includes. +#include #include #include #include @@ -72,7 +79,7 @@ namespace sta STM32UART * intf_ptr = new STM32UART(getUARThandle(), settings, mutex); Debug = new PrintableUART(intf_ptr); } - } + } // namespace tacos #endif // STA_DEBUGGING_ENABLED namespace tacos @@ -142,12 +149,7 @@ namespace sta CanBus::instance()->start(); } #endif //STA_TACOS_CAN_BUS_ENABLED - } // namespace tacos - - namespace rtos - { - // Override the weak implementation of startupExtras provided in rtos2-utils. void startupExtras(void * argument) { #ifdef STA_DEBUGGING_ENABLED @@ -166,7 +168,31 @@ namespace sta tacos::initCanBus(); #endif // STA_TACOS_CAN_BUS_ENABLED } - } // namespace rtos + } // namespace tacos } // namespace sta +void startTACOS(void * arg) +{ + STA_ASSERT_MSG(osKernelGetState() != osKernelInactive, "Cannot call startTACOS() before osKernelInitialize()"); + // Initialize HAL + sta::initHAL(); + + // Initialize RTOS system resources + sta::rtos::initSystem(); + + // Call further initialization code + sta::tacos::startupExtras(arg); + + // Wake threads +#ifdef STA_RTOS_SYSTEM_EVENTS_ENABLE + sta::rtos::signalStartupEvent(); +#endif // STA_RTOS_SYSTEM_EVENTS_ENABLE + + // Check if called from thread + if (osThreadGetId() != nullptr) + { + // Terminate current thread + osThreadExit(); + } +} From 53ad885099ff44fa18d7bf2364d6d566d40d2f23 Mon Sep 17 00:00:00 2001 From: CarlWachter Date: Sun, 3 Nov 2024 13:13:58 +0100 Subject: [PATCH 03/16] refactor: merged manger task into statemachine task --- include/sta/tacos.hpp | 3 +- include/sta/tacos/configs/default.hpp | 2 - include/sta/tacos/manager.hpp | 124 -------------------------- include/sta/tacos/statemachine.hpp | 61 ++++++++++++- include/sta/tacos/thread.hpp | 2 +- src/can_bus.cpp | 4 +- src/manager.cpp | 110 ----------------------- src/startup.cpp | 20 ----- src/statemachine.cpp | 73 ++++++++++++++- src/watchdog.cpp | 4 +- 10 files changed, 137 insertions(+), 266 deletions(-) delete mode 100644 include/sta/tacos/manager.hpp delete mode 100644 src/manager.cpp diff --git a/include/sta/tacos.hpp b/include/sta/tacos.hpp index 7f9ef4a..fa88c28 100644 --- a/include/sta/tacos.hpp +++ b/include/sta/tacos.hpp @@ -9,7 +9,6 @@ #define STA_TACOS_HPP #include -#include #include #include @@ -74,7 +73,7 @@ namespace sta { std::shared_ptr thread_ptr = std::make_shared(args...); - Manager::instance()->registerThread(thread_ptr, states); + Statemachine::instance()->registerThread(thread_ptr, states); return thread_ptr; } diff --git a/include/sta/tacos/configs/default.hpp b/include/sta/tacos/configs/default.hpp index c8a698f..bc791bb 100644 --- a/include/sta/tacos/configs/default.hpp +++ b/include/sta/tacos/configs/default.hpp @@ -2,13 +2,11 @@ #define STA_TACOS_CONFIGS_DEFAULT_HPP // Generally, we assume the TACOS threads to have the highest priorties. -#define STA_TACOS_MANAGER_PRIORITY osPriorityHigh #define STA_TACOS_STATEMACHINE_PRIORITY osPriorityHigh #define STA_TACOS_WATCHDOG_PRIORITY osPriorityHigh #define STA_TACOS_CAN_BUS_PRIORITY osPriorityHigh // Set the Stack size for the TACOS threads, to 0 to use the default stack size, set in the ioc -#define STA_TACOS_MANAGER_STACK_SIZE 0 #define STA_TACOS_STATEMACHINE_STACK_SIZE 0 // Per default, we assume state 0 to be the initial state. diff --git a/include/sta/tacos/manager.hpp b/include/sta/tacos/manager.hpp deleted file mode 100644 index 65f51c5..0000000 --- a/include/sta/tacos/manager.hpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * manager.hpp - * - * Created on: Sep 19, 2023 - * Author: Dario - */ - -#ifndef INCLUDE_STA_TACOS_MANAGER_HPP_ -#define INCLUDE_STA_TACOS_MANAGER_HPP_ - - -#include - -#if !defined(STA_TACOS_MANAGER_PRIORITY) && !defined(DOXYGEN) -# error "Manger task priority not specified in config.hpp" -#else - -#include -#include -#include -#include -#include - -/** - * @defgroup tacos_manager Manager Task - * @ingroup tacos - * @brief Manager task for TACOS. - */ - -namespace sta -{ - namespace tacos - { - /** - * @brief Manager class for Tacos. - * - * @ingroup tacos_manager - */ - class Manager : public TacosThread - { - public: - /** - * @brief Get the singleton instance of the manager. - * - * @ingroup tacos_manager - */ - static Manager* instance() - { - static CGuard g; - - if (!_instance) - { - // Create a the manager singleton instance. - Manager::_instance = new Manager(); - } - - return _instance; - } - - /** - * @brief Register a thread to be managed by the manager. - */ - void registerThread(std::shared_ptr thread, std::set states); - - /** - * @brief Get the Active Threads object - * - * @return std::vector> - */ - std::vector> getActiveThreads(); - - void init() override; - - void func() override; - private: - static Manager* _instance; - - class CGuard - { - public: - ~CGuard() - { - if( NULL != Manager::_instance ) - { - delete Manager::_instance; - Manager::_instance = NULL; - } - } - }; - - Manager(); - - Manager(const Manager&); - - //~Manager(); - - /** - * @brief Forces only threads of current state to run. - */ - void updateThreads(); - - /** - * @brief Starts all threads which should be running in the given state. Does nothing if the state is already running. - */ - void startThreads(uint16_t state); - - /** - * @brief Stops all threads which should not be running in the given state. - */ - void stopThreads(uint16_t state); - - /** - * @brief Pointers to all threads which are managed by the manager. - * - * @ingroup tacos_manager - */ - std::vector> threads_[STA_TACOS_NUM_STATES]; - }; - } // namespace tacos -} // namespace sta - -#endif // STA_TACOS_MANAGER_PRIORITY - -#endif /* INCLUDE_STA_TACOS_MANAGER_HPP_ */ diff --git a/include/sta/tacos/statemachine.hpp b/include/sta/tacos/statemachine.hpp index 452809b..9217c1c 100644 --- a/include/sta/tacos/statemachine.hpp +++ b/include/sta/tacos/statemachine.hpp @@ -69,6 +69,9 @@ #include #include +#include +#include +#include #include #include @@ -134,11 +137,15 @@ namespace sta public: /** * @brief The global event signaling a state change. + * + * @ingroup tacos_statemachine */ static RtosEvent stateChangeEvent; /** * @brief Getter function for the singleton instance. + * + * @ingroup tacos_statemachine */ static Statemachine* instance() { @@ -146,7 +153,7 @@ namespace sta if (!_instance) { - // Create the manager singleton instance. + // Create the statemachine singleton instance. Statemachine::_instance = new Statemachine(); } @@ -155,6 +162,8 @@ namespace sta /** * @brief Returns the statemachine's current state. + * + * @ingroup tacos_statemachine */ uint16_t getCurrentState() const; @@ -165,6 +174,8 @@ namespace sta * @param to The state to transition to. * @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions. * @param force If true, the state transition will be executed regardless of the current state. + * + * @ingroup tacos_statemachine */ void requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout = 0, bool force = false, bool publish = true); @@ -175,9 +186,27 @@ namespace sta * @param to The state to transition to. * @param millis the number of milliseconds to wait before triggering the transition. * @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions. + * + * @ingroup tacos_statemachine */ void requestTimedStateTransition(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout = 0, bool publish = true); + /** + * @brief Register a thread to be managed by the statemachine. + * + * @ingroup tacos_statemachine + */ + void registerThread(std::shared_ptr thread, std::set states); + + /** + * @brief Get the Active Threads object + * + * @return std::vector> + * + * @ingroup tacos_statemachine + */ + std::vector> getActiveThreads(); + void init() override; void func() override; @@ -208,9 +237,32 @@ namespace sta * @brief Starts the lockoutTimer for the desired duration. * * @param millis The duration of the timer in milliseconds. + * + * @ingroup tacos_statemachine */ void setLockoutTimer(uint32_t millis); + /** + * @brief Forces only threads of current state to run. + * + * @ingroup tacos_statemachine + */ + void updateThreads(); + + /** + * @brief Starts all threads which should be running in the given state. Does nothing if the state is already running. + * + * @ingroup tacos_statemachine + */ + void startThreads(uint16_t state); + + /** + * @brief Stops all threads which should not be running in the given state. + * + * @ingroup tacos_statemachine + */ + void stopThreads(uint16_t state); + private: uint16_t currentState_; @@ -218,6 +270,13 @@ namespace sta RtosTimer failsafeTimer_; RtosQueue queue_; + + /** + * @brief Pointers to all threads which are managed by the statemachine. + * + * @ingroup tacos_statemachine + */ + std::vector> threads_[STA_TACOS_NUM_STATES]; }; } // namespace tacos } // namespace sta diff --git a/include/sta/tacos/thread.hpp b/include/sta/tacos/thread.hpp index a60064d..32d582e 100644 --- a/include/sta/tacos/thread.hpp +++ b/include/sta/tacos/thread.hpp @@ -79,7 +79,7 @@ namespace sta * @brief Create a new thread with the given name and priority. * * @param name The thread's name. This is used for debugging. - * @param prio The thread's priority. Generally, this should be lower than the manager and statemachine priority. + * @param prio The thread's priority. Generally, this should be lower than the statemachine priority. * @param stack_size The stack size for the task. The default is 0, i.e. the stack size specified in the FreeRTOS settings. * @param cb_size The control block size for the task. The default is 0, i.e. the size specified in the FreeRTOS settings. */ diff --git a/src/can_bus.cpp b/src/can_bus.cpp index 78686d3..06ad880 100644 --- a/src/can_bus.cpp +++ b/src/can_bus.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include extern CAN_HandleTypeDef STA_STM32_CAN_HANDLE; @@ -64,7 +64,7 @@ namespace sta { // Append to the correct thread's queue - for (std::shared_ptr thread : Manager::instance()->getActiveThreads()) + for (std::shared_ptr thread : Statemachine::instance()->getActiveThreads()) { if (thread->getCanID() == sysMsg.header.sid) { diff --git a/src/manager.cpp b/src/manager.cpp deleted file mode 100644 index 1d59de1..0000000 --- a/src/manager.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * manager.cpp - * - * Created on: Sep 19, 2023 - * Author: Dario - */ - -#include -#include -#include - -#include - -#include -#include - - -namespace sta -{ - namespace tacos - { - void Manager::registerThread(std::shared_ptr thread, std::set states) - { - for (uint16_t state : states) - { - STA_ASSERT(state < STA_TACOS_NUM_STATES); - - threads_[state].push_back(thread); - } - } - - std::vector> Manager::getActiveThreads() - { - return threads_[tacos::getState()]; - } - - void Manager::startThreads(uint16_t state) - { - STA_ASSERT(state < STA_TACOS_NUM_STATES); - - for (std::shared_ptr thread : threads_[state]) - { - if (!thread->isRunning()) - { - thread->start(); - } - } - } - - void Manager::stopThreads(uint16_t state) - { - std::set> terminated; - - for (uint16_t other = 0; other < STA_TACOS_NUM_STATES; ++other) - { - if (other == state) - { - continue; - } - - for (std::shared_ptr thread : threads_[other]) - { - // If the thread is currently running but not part of the set of threads that should be running... - if (thread->isRunning() && terminated.count(thread) == 0 && std::count(threads_[state].begin(), threads_[state].end(), thread) == 0) - { - // ...politely request termination. - thread->requestTermination(); - terminated.emplace(thread); - } - } - } - } - - void Manager::updateThreads() - { - uint16_t state = Statemachine::instance()->getCurrentState(); - - stopThreads(state); - startThreads(state); - } - - void Manager::init() - { - startThreads(Statemachine::instance()->getCurrentState()); - } - - void Manager::func() - { - Statemachine::stateChangeEvent.wait(EventFlags::ALL, osWaitForever); - - HeapStats_t stats; - vPortGetHeapStats(&stats); - - // Start all new tasks and stop all the tasks that aren't supposed to be running. - updateThreads(); - } - - Manager::Manager() - : TacosThread{"Manager", STA_TACOS_MANAGER_PRIORITY, STA_TACOS_MANAGER_STACK_SIZE}, - threads_{} - { - - } - - Manager* Manager::_instance = nullptr; - - } // namespace tacos -} // namespace sta - - diff --git a/src/startup.cpp b/src/startup.cpp index 0f450f5..175cf54 100644 --- a/src/startup.cpp +++ b/src/startup.cpp @@ -33,7 +33,6 @@ // Tacos-specific includes. #include -#include #include #include #include @@ -101,23 +100,6 @@ namespace sta Statemachine::instance()->start(); } - /** - * @brief Function that is called before the manager task is started. Override it to adjust - * the manager to your specifications. - * - * @ingroup tacos_startup - */ - STA_WEAK - void onManagerInit() - {} - - void initManager() - { - onManagerInit(); - - Manager::instance()->start(); - } - #ifdef STA_TACOS_WATCHDOG_ENABLED STA_WEAK void onWatchdogInit() @@ -158,8 +140,6 @@ namespace sta tacos::initStatemachine(); - tacos::initManager(); - #ifdef STA_TACOS_WATCHDOG_ENABLED tacos::initWatchdog(); #endif // STA_TACOS_WATCHDOG_ENABLED diff --git a/src/statemachine.cpp b/src/statemachine.cpp index d970a57..762e48b 100644 --- a/src/statemachine.cpp +++ b/src/statemachine.cpp @@ -9,6 +9,7 @@ #include #include +#include namespace sta @@ -20,14 +21,15 @@ namespace sta currentState_{STA_TACOS_INITIAL_STATE}, lockoutTimer_{[](void *){}, nullptr}, failsafeTimer_{[](void *){}, nullptr}, - queue_{STA_TACOS_STATEMACHINE_QUEUE_LENGTH} + queue_{STA_TACOS_STATEMACHINE_QUEUE_LENGTH}, + threads_{} { STA_ASSERT(STA_TACOS_INITIAL_STATE < STA_TACOS_NUM_STATES); } void Statemachine::init() { - + startThreads(getCurrentState()); } void Statemachine::func() @@ -64,6 +66,13 @@ namespace sta { setLockoutTimer(transition.lockout); } + + // get heap stats at the end of the state transition + HeapStats_t stats; + vPortGetHeapStats(&stats); + + // Start all new tasks and stop all the tasks that aren't supposed to be running. + updateThreads(); } } @@ -130,6 +139,66 @@ namespace sta lockoutTimer_.start(millis); } + void Statemachine::registerThread(std::shared_ptr thread, std::set states) + { + for (uint16_t state : states) + { + STA_ASSERT(state < STA_TACOS_NUM_STATES); + + threads_[state].push_back(thread); + } + } + + std::vector> Statemachine::getActiveThreads() + { + return threads_[tacos::getState()]; + } + + void Statemachine::startThreads(uint16_t state) + { + STA_ASSERT(state < STA_TACOS_NUM_STATES); + + for (std::shared_ptr thread : threads_[state]) + { + if (!thread->isRunning()) + { + thread->start(); + } + } + } + + void Statemachine::stopThreads(uint16_t state) + { + std::set> terminated; + + for (uint16_t other = 0; other < STA_TACOS_NUM_STATES; ++other) + { + if (other == state) + { + continue; + } + + for (std::shared_ptr thread : threads_[other]) + { + // If the thread is currently running but not part of the set of threads that should be running... + if (thread->isRunning() && terminated.count(thread) == 0 && std::count(threads_[state].begin(), threads_[state].end(), thread) == 0) + { + // ...politely request termination. + thread->requestTermination(); + terminated.emplace(thread); + } + } + } + } + + void Statemachine::updateThreads() + { + uint16_t state = getCurrentState(); + + stopThreads(state); + startThreads(state); + } + Statemachine* Statemachine::_instance = nullptr; RtosEvent Statemachine::stateChangeEvent; diff --git a/src/watchdog.cpp b/src/watchdog.cpp index 2550893..cf91b5a 100644 --- a/src/watchdog.cpp +++ b/src/watchdog.cpp @@ -2,7 +2,7 @@ #ifdef STA_TACOS_WATCHDOG_ENABLED -#include +#include namespace sta { @@ -10,7 +10,7 @@ namespace sta { void Watchdog::func() { - for (std::shared_ptr thread : Manager::instance()->getActiveThreads()) + for (std::shared_ptr thread : Statemachine::instance()->getActiveThreads()) { switch (thread->getStatus()) { From 593c93012d1393b77fa19584bd351f0dd756b1e6 Mon Sep 17 00:00:00 2001 From: CarlWachter Date: Sun, 3 Nov 2024 13:25:22 +0100 Subject: [PATCH 04/16] feat: user definable callback on state transition --- include/sta/tacos/statemachine.hpp | 13 +++++++++++++ src/statemachine.cpp | 3 +++ 2 files changed, 16 insertions(+) diff --git a/include/sta/tacos/statemachine.hpp b/include/sta/tacos/statemachine.hpp index 9217c1c..07fbdea 100644 --- a/include/sta/tacos/statemachine.hpp +++ b/include/sta/tacos/statemachine.hpp @@ -66,6 +66,7 @@ #include #include #include +#include #include #include @@ -278,6 +279,18 @@ namespace sta */ std::vector> threads_[STA_TACOS_NUM_STATES]; }; + + /** + * @brief Callback that is called when a state transition occurs. + * + * @param from The state we transitioned from. + * @param to The state we transitioned to. + * @param lockout The lockout time after the transition. + * + * @ingroup tacos_statemachine + */ + STA_WEAK + void onStateTransition(uint16_t from, uint16_t to, uint32_t lockout){} } // namespace tacos } // namespace sta diff --git a/src/statemachine.cpp b/src/statemachine.cpp index 762e48b..2d10a67 100644 --- a/src/statemachine.cpp +++ b/src/statemachine.cpp @@ -71,6 +71,9 @@ namespace sta HeapStats_t stats; vPortGetHeapStats(&stats); + // Execute the user-defined callback. + sta::tacos::onStateTransition(transition.from, transition.to, transition.lockout); + // Start all new tasks and stop all the tasks that aren't supposed to be running. updateThreads(); } From 743e9205e619575b03d0819d4162caaab5943acd Mon Sep 17 00:00:00 2001 From: CarlWachter Date: Sun, 3 Nov 2024 13:42:25 +0100 Subject: [PATCH 05/16] cleanup: separate functions for requesting and forcing state transitions, fix publishing behavior --- include/sta/tacos.hpp | 16 ++++++++++-- include/sta/tacos/statemachine.hpp | 17 +++++++++++-- src/can_bus.cpp | 2 +- src/statemachine.cpp | 40 +++++++++++++++++++----------- src/tacos.cpp | 9 +++++-- 5 files changed, 62 insertions(+), 22 deletions(-) diff --git a/include/sta/tacos.hpp b/include/sta/tacos.hpp index fa88c28..99fbc2c 100644 --- a/include/sta/tacos.hpp +++ b/include/sta/tacos.hpp @@ -41,11 +41,23 @@ namespace sta * @param from The start we want to transition from. * @param to The state we want to transition to. * @param lockout An optional timer blocking state transition for a given time. - * @param force If true, the state transition will be executed regardless of the current state. + * @param publish If true, the state transition will be published via CAN. * * @ingroup tacos_api */ - void setState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool force = false, bool publish = false); + void requestState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true); + + /** + * @brief Request a state transition. Invalid state transitions will be dismissed. + * + * @param from The start we want to transition from. + * @param to The state we want to transition to. + * @param lockout An optional timer blocking state transition for a given time. + * @param publish If true, the state transition will be published via CAN. + * + * @ingroup tacos_api + */ + void forceState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true); /** * @brief Request a state transition after a given time has passed. Invalid state transitions will be dismissed. diff --git a/include/sta/tacos/statemachine.hpp b/include/sta/tacos/statemachine.hpp index 07fbdea..d9fd517 100644 --- a/include/sta/tacos/statemachine.hpp +++ b/include/sta/tacos/statemachine.hpp @@ -174,11 +174,23 @@ namespace sta * @param from The state which we want to leave. This is used to filter out obsolete transitions. * @param to The state to transition to. * @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions. - * @param force If true, the state transition will be executed regardless of the current state. + * @param publish If true, the state transition will be published via CAN. * * @ingroup tacos_statemachine */ - void requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout = 0, bool force = false, bool publish = true); + void requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true); + + /** + * @brief Request a state transition from a state to another. + * + * @param from The state which we want to leave. This is used to filter out obsolete transitions. + * @param to The state to transition to. + * @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions. + * @param publish If true, the state transition will be published via CAN. + * + * @ingroup tacos_statemachine + */ + void forceStateTransition(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true); /** * @brief Request a state transition after a given time has passed. @@ -187,6 +199,7 @@ namespace sta * @param to The state to transition to. * @param millis the number of milliseconds to wait before triggering the transition. * @param lockout The minimum number of milliseconds we expect to stay in this state. This is used to block premature transitions. + * @param publish If true, the state transition will be published via CAN. * * @ingroup tacos_statemachine */ diff --git a/src/can_bus.cpp b/src/can_bus.cpp index 06ad880..bc4efda 100644 --- a/src/can_bus.cpp +++ b/src/can_bus.cpp @@ -160,7 +160,7 @@ namespace sta STA_ASSERT(header.payloadLength == 2); // First byte of payload is the origin state, second byte is the destination state. Transition is forced - tacos::setState(payload[0], payload[1], 0, true); + tacos::forceState(payload[0], payload[1], 0, true); return true; } diff --git a/src/statemachine.cpp b/src/statemachine.cpp index 2d10a67..03c5f51 100644 --- a/src/statemachine.cpp +++ b/src/statemachine.cpp @@ -44,8 +44,11 @@ namespace sta STA_ASSERT(transition.to < STA_TACOS_NUM_STATES); #ifdef STA_TACOS_CAN_BUS_ENABLED - // Publish the state via CAN bus. - tacos::publishState(transition.from, transition.to, 0); + if (transition.publish) + { + // Publish the state via CAN bus. + tacos::publishState(transition.from, transition.to, 0); + } #endif // STA_TACOS_CAN_BUS_ENABLED // Perform the transition and notify the threads. The event flags are set @@ -84,7 +87,7 @@ namespace sta return currentState_; } - void Statemachine::requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool force /* = 0 */, bool publish /* = true */) + void Statemachine::requestStateTransition(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool publish /* = true */) { StateTransition transition; transition.from = from; @@ -93,17 +96,28 @@ namespace sta transition.lockout = lockout; transition.publish = publish; - // Force the transition if requested, but only if the requested state is different from the current one. - if (force && transition.to != currentState_){ + // Try to add a state transition request to the queue. Don't wait if another + // thread is already requesting a state change. + queue_.put(transition, 0); + } + + void Statemachine::forceStateTransition(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool publish /* = true */) + { + // Force the transition, but only if the requested state is different from the current one. + if (to != currentState_){ // Perform the transition and notify the threads. The event flags are set // here in order to allow threads to react immediately. - currentState_ = transition.to; + currentState_ = to; #ifdef STA_TACOS_CAN_BUS_ENABLED - tacos::publishState(transition.from, transition.to, transition.lockout); + if (publish) + { + // Publish the state via CAN bus. + tacos::publishState(from, to, 0); + } #endif // STA_TACOS_CAN_BUS_ENABLED - Statemachine::stateChangeEvent.set(transition.event); + Statemachine::stateChangeEvent.set(EventFlags::NORMAL); Statemachine::stateChangeEvent.clear(EventFlags::ALL); if (failsafeTimer_.isRunning()) @@ -112,14 +126,10 @@ namespace sta } // Start the lockout timer if requested. - if (transition.lockout != 0) + if (lockout != 0) { - setLockoutTimer(transition.lockout); + setLockoutTimer(lockout); } - } else { - // Try to add a state transition request to the queue. Don't wait if another - // thread is already requesting a state change. - queue_.put(transition, 0); } } @@ -128,7 +138,7 @@ namespace sta STA_ASSERT(to < STA_TACOS_NUM_STATES); failsafeTimer_.setCallback([from, to, lockout, publish](void* arg) { - Statemachine::instance()->requestStateTransition(from, to, lockout, false, publish); + Statemachine::instance()->requestStateTransition(from, to, lockout, publish); }, NULL); failsafeTimer_.start(millis); diff --git a/src/tacos.cpp b/src/tacos.cpp index f1c615f..94491f0 100644 --- a/src/tacos.cpp +++ b/src/tacos.cpp @@ -16,9 +16,14 @@ namespace sta return Statemachine::instance()->getCurrentState(); } - void setState(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool force /* = false */, bool publish /* = false */) + void requestState(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool publish /* = true */) { - Statemachine::instance()->requestStateTransition(from, to, lockout, force, publish); + Statemachine::instance()->requestStateTransition(from, to, lockout, publish); + } + + void forceState(uint32_t from, uint32_t to, uint32_t lockout /* = 0 */, bool publish /* = true */) + { + Statemachine::instance()->forceStateTransition(from, to, lockout, publish); } void setStateTimed(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout /* = 0 */, bool publish /* = false */) From 648fa3866799286927774483dcde79aeef06f460 Mon Sep 17 00:00:00 2001 From: CarlWachter Date: Sun, 3 Nov 2024 14:57:27 +0100 Subject: [PATCH 06/16] fix(statemachine): perform manager roles and call callback when transition is forced --- src/statemachine.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/statemachine.cpp b/src/statemachine.cpp index 03c5f51..e84c614 100644 --- a/src/statemachine.cpp +++ b/src/statemachine.cpp @@ -130,6 +130,16 @@ namespace sta { setLockoutTimer(lockout); } + + // get heap stats at the end of the state transition + HeapStats_t stats; + vPortGetHeapStats(&stats); + + // Execute the user-defined callback. + sta::tacos::onStateTransition(transition.from, transition.to, transition.lockout); + + // Start all new tasks and stop all the tasks that aren't supposed to be running. + updateThreads(); } } From 9e4ac9d73ae52e5afc44d0b4a30fb7b3646d588b Mon Sep 17 00:00:00 2001 From: CarlWachter Date: Sun, 3 Nov 2024 15:09:58 +0100 Subject: [PATCH 07/16] doc: Added READMEs for TACOS usage --- README.md | 190 ++++++++++++++++++++++++++++-- include/sta/README.md | 93 +++++++++++++++ include/sta/tacos/README.md | 23 ++++ include/sta/tacos/c_api/README.md | 3 + 4 files changed, 296 insertions(+), 13 deletions(-) create mode 100644 include/sta/README.md create mode 100644 include/sta/tacos/README.md create mode 100644 include/sta/tacos/c_api/README.md diff --git a/README.md b/README.md index 0a16532..d28f2d1 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,63 @@ # TACOS -This is the Trajectory Analysis Control OS (TACOS) that serves as a starting point for developing TACOS-based projects, by implementing threads in the App directory. TACOS is a subset of the features provided by ALPAKA. In particular, TACOS consists of the STM32-specific components of sta-core, rtos-2 and rtos2-utils. +This is the Trajectory Analysis Control OS (TACOS) that serves as a framework for flight computer development. TACOS offers a state machine, a CAN bus interface, a watchdog and other HAL features through it's submodules. It runs on cmsis-rtos2 FreeRTOS on STM32 microcontrollers with C++ (maybe future versions will offer external C interfaces to support a wide array of languages...). -## Setting Up TACOS +To use TACOS one should implement threads, which fulfill the various roles of the module in the App directory. TACOS utilizes [ALPAKA](https://git.intern.spaceteamaachen.de/ALPAKA) features, in particular requiring [sta-core](https://git.intern.spaceteamaachen.de/ALPAKA/sta-core) and [rtos2-utils](https://git.intern.spaceteamaachen.de/ALPAKA/rtos2-utils), as such it requires these to be in it's include path. -Add this as a library an existing CubeIDE project using FreeRTOS. Generally, we advise you to add it as a submodule. Make sure that you add the include paths for Tacos, i.e. sta-core and rtos2-utils, to the project with the following steps: -``` -Properties -> C/C++ General -> Paths and Symbols -> Includes -> GNU C -> Add... -Properties -> C/C++ General -> Paths and Symbols -> Includes -> GNU C++ -> Add... -Properties -> C/C++ General -> Paths and Symbols -> Source Location -> Add Folder... +## Setting up a TACOS project + +### Setting up the project + +First one must create a new CubeIDE project with FreeRTOS. To avoid doing that however we recommend using the [ioc-collection](https://git.intern.spaceteamaachen.de/ALPAKA/ioc-collection) to get a preconfigured IOC for the STM microcontroller you are using. From here follow the following steps: + +1. ```Import -> General -> Import an Existing STM32CubeMX Configuration File (.ioc)``` +2. Select the .ioc file from the ioc-collection +3. Enter the project name and location you want to save the project to +4. Select C++ as the target language +5. Click "Finish" + + +### Setting up the folder structure + +Now it is necessary to setup the dependencies and include paths for TACOS. For this first create a new folder in the project directory called `Libs`. Then create another folder in the project directory called `App` with the subfolders `Inc` and `Src`. Now also create a folder called `sta` in the `Inc` folder. Finally add the empty files `App/Inc/sta/config.hpp` and `App/Src/startup.cpp`. + +Now your project should look like this: +``` +... +App/ +├── Inc/ +│ ├── sta/ +│ │ └── config.hpp +├── Src/ +│ └── startup.cpp +Libs/ +... ``` -Create a new thread via the project's IOC and call `startTACOS()` from this thread. If your thread is called `defaultTask`, the corresponding function `StartDefaultTask` generated in `Core/Src/freertos.c` should look like this: +### Setting up the dependencies + +First it is recommended to initialize a git repository in the project folder with `git init`. Then add the TACOS, sta-core and rtos2-utils repositories as submodules in the `Libs` folder with the following commands: +```bash +cd Libs +git submodule add https://git.intern.spaceteamaachen.de/ALPAKA/TACOS.git +git submodule add https://git.intern.spaceteamaachen.de/ALPAKA/sta-core.git +git submodule add https://git.intern.spaceteamaachen.de/ALPAKA/rtos2-utils.git ``` + +Make sure that you add the include paths for TACOS, sta-core and rtos2-utils to the project with the following steps: +1. `Properties -> C/C++ General -> Paths and Symbols -> Includes -> GNU C -> Add...` +2. Select `Add to all languages` and `Is a workspace path` +3. Click on `Workspace` and select a folder from the `YOUR_PROJECT_FOLDER/(Libs|App)` directory + - Always select the `include` or `Inc` folder for the include paths + - If the path you want to add is not in the list, refresh the project with `F5` in the `Project Explorer` and try again +4. Repeat for TACOS, sta-core, rtos2-utils and the App folder +5. `Properties -> C/C++ General -> Paths and Symbols -> Source Location -> Add Folder...` + - Add the `App` and `Libs` folders + +### Starting TACOS + +Navigate to the `Core/Src/freertos.c` file and add the following code to the `StartDefaultTask` function: +```cpp void StartDefaultTask(void *argument) { /* USER CODE BEGIN StartDefaultTask */ @@ -28,9 +73,11 @@ void StartDefaultTask(void *argument) } ``` -## Configuring TACOS +This will start the TACOS startup and initialize all TACOS threads (which will then initialize yours). + +### Configuring TACOS In order to use TACOS, you need to provide a configuration file in the path `sta/config.hpp`. The following code is an example for a TACOS-project using default configuration: -``` +```cpp #ifndef INC_STA_CONFIG_HPP_ #define INC_STA_CONFIG_HPP_ @@ -50,6 +97,7 @@ In order to use TACOS, you need to provide a configuration file in the path `sta #define STA_RTOS_SYSTEM_EVENTS_ENABLE // #define STA_RTOS_SYSTEM_WATCHDOG_ENABLE // #define STA_RTOS_WATCHDOG_ENABLE +// #define STA_TACOS_WATCHDOG_FREQUENCY 10000 #define STA_CAN_BUS_ENABLE // Statemachine settings. @@ -61,12 +109,85 @@ In order to use TACOS, you need to provide a configuration file in the path `sta #endif /* INC_STA_CONFIG_HPP_ */ ``` PS: For not officially supported chips use this as the include: -``` +```cpp #include #define STA_MCU_LITTLE_ENDIAN #define STA_PLATFORM_STM32 ``` -## Setting up the CAN Bus + +### Implementing your own threads + +Let's create a simple thread that prints "Hello World" every second. First create a new file in the `App/Inc/tasks` folder called `spam_task.hpp`. Then add the following code: +```cpp +#ifndef INC_TASKS_SPAM_TASK_HPP_ +#define INC_TASKS_SPAM_TASK_HPP_ + +#include + +namespace tasks +{ + class SpamTask : public sta::tacos::TacosThread { + public: + SpamTask(); + + // One time function that is called when the thread is created. + void init() override; + + // Repeatable function that is called every time the thread is executed. + void func() override; + }; +} // namespace tasks + +#endif /* INC_TASKS_SPAM_TASK_HPP_ */ +``` +This code defines a new thread that inherits from `TacosThread` and implements the `init` and `func` functions. The `init` function is called once when the thread is created and the `func` function is called every time the thread is executed. + +Now create a new file in the `App/Src/tasks` folder called `spam_task.cpp` and add the following code: +```cpp +#include + +namespace tasks { + SpamTask::SpamTask() : + TacosThread("SPAM", osPriorityNormal){} + + void SpamTask::init() { + // Nothing to init... + } + + void SpamTask::func() { + // Print "Hello World" every second. + STA_DEBUG_PRINTLN("Hello World"); + this->periodicDelay(1); // Execute this function with 1 Hz. + } +} // namespace tasks +``` + +To start this thread, we first need to fill out the `startup.cpp` file. This file may look like this: +```cpp +#include +#include + +#include + +namespace sta +{ + namespace tacos + { + void onStatemachineInit() + { + // ###### Register different threads for different states here. ###### + // Register a "Spam Task" thread for all states except 1 and 2. + sta::tacos::addThread(ALL_STATES - state_set{1,2}); + + STA_DEBUG_PRINTF("The answer to everything is %d", 42); + } + } // namespace tacos +} // namespace sta +``` + +And that's it! Now you have a thread that prints "Hello World" every second. Simply build the project and flash it to your microcontroller and be amazed by the Spam! + +### Setting up the CAN Bus To enable the CAN Bus two things need to be done: 1. Enable CAN in the IOC with the RX0 and RX1 Interrupts enabled. @@ -79,4 +200,47 @@ PS: For not officially supported chips add this: #define STA_STM32_CAN_HANDLE {YOUR_HCAN_HANDLE} ``` -After this messages will automatically be forwarded to the task with it's ID. To send messages use the interface defined in `tacos.hpp`. \ No newline at end of file +There are two options for handling incoming CAN messages: +1. If `#define STA_CAN_BUS_FWD_ENABLE` is set, the messages will be forwarded to the task with the ID of the message. + - Tasks set their ID with `setID(uint32_t id)` in their constructor. + - From here they can handle the message by going through their `CAN_queue_` with `CanSysMsg msg; CAN_queue_.get(&msg);` +2. All messages will trigger the weakly defined handleSysMessage callback. + - This could be implemented like this: +```cpp +namespace sta +{ + namespace tacos + { + bool handleSysMessage(CanMsgHeader &header, uint8_t *payload) + { + // Print the message ID and the first byte of the payload. + //(please don't do this in production, it will crash the system sooner or later) + STA_DEBUG_PRINTF("> ID: %d", header.sid); + + switch (header.sid) + { + // State transition message + case STA_TACOS_CAN_BUS_SYS_MSG_ID: + // First byte of payload is the origin state, second byte is the destination state + tacos::setState(payload[0], payload[1], 0, true); + return true; + + case MODULE_SW_RESET_CAN_ID: + HAL_NVIC_SystemReset(); + + return true; // :) + + // ... + + default: + return false; + } + + return false; // I know, i know, this is not necessary, but it's good practice. And you know what they say about good practice: Do it! + } + } +} +``` + +### Further information +To look into other function of TACOS please consult the READMEs in the include folder or the doxygen documentation. Also consult the sta-core and rtos2-utils READMEs for further information on the features that TACOS uses. \ No newline at end of file diff --git a/include/sta/README.md b/include/sta/README.md new file mode 100644 index 0000000..b39d0bc --- /dev/null +++ b/include/sta/README.md @@ -0,0 +1,93 @@ +# TACOS.hpp + +The TACOS API is defined in the tacos.hpp, in normal use cases you should only need to include this file. + +## Functions + +```cpp +uint16_t getState() +``` + +Retrieves the current state of the TACOS state machine. + +--- +```cpp +void requestState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true) +``` + +Requests a state transition. Invalid state transitions will be dismissed. First come, first serve. + +- **Parameters**: + - `from`: The starting state for the transition. + - `to`: The target state to transition to. + - `lockout`: (Optional) A timer to block further transitions. + - `publish`: (Optional) Whether to publish the state transition to the CAN bus. + +```cpp +void forceState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true) +``` + +Forces a state transition. Will be ignored if already in the given state. Triggers instantly. + +- **Parameters**: + - `from`: The starting state for the transition. + - `to`: The target state to transition to. + - `lockout`: (Optional) A timer to block further transitions. + - `publish`: (Optional) Whether to publish the state transition to the CAN bus. + +--- + +```cpp +void setStateTimed(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout = 0, bool publish = false) +``` + +Requests a state transition after a specified time. + +- **Parameters**: + - `from`: The starting state. + - `to`: The target state. + - `millis`: The wait time in milliseconds before requesting the transition. + - `lockout`: (Optional) A timer for blocking subsequent transitions. + +--- + +```cpp +template std::shared_ptr addThread(std::set states, Args ... args) +``` + +Registers a new thread to be run by TACOS. + +- **Template Parameters**: + - `T`: The class type of the thread, which should inherit from `TacosThread`. +- **Parameters**: + - `states`: A set of states in which the thread should be active. + - `args`: The constructor arguments for the thread class. + +--- + +### CAN Bus Functions (Conditional) + +The following functions are available only if `STA_TACOS_CAN_BUS_ENABLED` is defined: + +```cpp +bool queueCanBusMsg(CanSysMsg & msg, uint32_t timeout) +``` + +Queues a message to be sent over the CAN bus. + +- **Parameters**: + - `msg`: The message to be sent. + - `timeout`: The maximum time to wait for sending the message. + +--- + +```cpp +bool publishState(uint32_t from, uint32_t to, uint32_t timeout = 0) +``` + +Publishes a state transition message to the CAN bus. + +- **Parameters**: + - `from`: The starting state for the transition. + - `to`: The target state. + - `timeout`: (Optional) A timeout for CAN communication. \ No newline at end of file diff --git a/include/sta/tacos/README.md b/include/sta/tacos/README.md new file mode 100644 index 0000000..cf4aafa --- /dev/null +++ b/include/sta/tacos/README.md @@ -0,0 +1,23 @@ +# TACOS tasks and prototypes + +## Statemachine +The statemachine is the core of TACOS. It is responsible for managing the state of the system and executing the threads that are registered to it. The statemachine is a singleton and can be accessed via `sta::tacos::Statemachine::instance()`. + +For further info check the file or the doxygen documentation. + +## Watchdog +The watchdog checks if all threads are setting a flag after every func call. If a thread does not set the flag in the given interval the thread is restarted. + +The watchdog is enabled and configured with the following defines: +```cpp +#define STA_RTOS_WATCHDOG_ENABLE +#define STA_TACOS_WATCHDOG_FREQUENCY 10000 +``` + +## CAN Bus +The CAN bus is used to communicate between different devices. The CAN bus task is triggered by IRQ from the transceiver or if something is inserted into it's output queue. + +This thing is a huge steaming (but functioning) pile of shit. Further documentation is on hold until rework. + +## Thread.hpp +Here the TacosThread is defined. Very straight forward. Just take a look inside. \ No newline at end of file diff --git a/include/sta/tacos/c_api/README.md b/include/sta/tacos/c_api/README.md new file mode 100644 index 0000000..a842e64 --- /dev/null +++ b/include/sta/tacos/c_api/README.md @@ -0,0 +1,3 @@ +# TACOS C API + +Currently the only C API is the entry point for starting up TACOS. This will be extended to provide full functionality via C calling conventions. \ No newline at end of file From 0304bb2a4a8e64ade4c7b0f9d9075b182e2351e1 Mon Sep 17 00:00:00 2001 From: dario Date: Sun, 1 Dec 2024 12:16:56 +0000 Subject: [PATCH 08/16] Updated some config.hpp explanation --- README.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6195cf9..9270b23 100644 --- a/README.md +++ b/README.md @@ -93,17 +93,12 @@ In order to use TACOS, you need to provide a configuration file in the path `sta #define STA_PRINTF_USE_STDLIB // Enable debug serial output and assertions. -#define STA_ASSERT_FORCE +#define STA_ASSERT_ENABLED #define STA_DEBUGGING_ENABLED // Enable Features -#define STA_RTOS_SYSTEM_EVENTS_ENABLE -// #define STA_RTOS_SYSTEM_WATCHDOG_ENABLE -// #define STA_RTOS_WATCHDOG_ENABLE -// #define STA_TACOS_WATCHDOG_FREQUENCY 10000 -#define STA_CAN_BUS_ENABLE -// Statemachine settings. +// Statemachine settings. How many states does your statemachine have? #define STA_TACOS_NUM_STATES 3 // Uses the default configuration for TACOS. From 9ff5e34a27be5c0d4f890c714baf63d580831862 Mon Sep 17 00:00:00 2001 From: dario Date: Sun, 1 Dec 2024 13:40:04 +0100 Subject: [PATCH 09/16] Updated Readme.md --- README.md | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 9270b23..c624a7b 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ This is the Trajectory Analysis Control OS (TACOS) that serves as a framework fo To use TACOS one should implement threads, which fulfill the various roles of the module in the App directory. TACOS utilizes [ALPAKA](https://git.intern.spaceteamaachen.de/ALPAKA) features, in particular requiring [sta-core](https://git.intern.spaceteamaachen.de/ALPAKA/sta-core) and [rtos2-utils](https://git.intern.spaceteamaachen.de/ALPAKA/rtos2-utils), as such it requires these to be in it's include path. -## Setting up a TACOS project +## Setting up a TACOS Project -### Setting up the project +### Setting up the Project First one must create a new CubeIDE project with FreeRTOS. To avoid doing that however we recommend using the [ioc-collection](https://git.intern.spaceteamaachen.de/ALPAKA/ioc-collection) to get a preconfigured IOC for the STM microcontroller you are using. From here follow the following steps: @@ -17,7 +17,7 @@ First one must create a new CubeIDE project with FreeRTOS. To avoid doing that h 5. Click "Finish" -### Setting up the folder structure +### Setting up the Folder Structure Now it is necessary to setup the dependencies and include paths for TACOS. For this first create a new folder in the project directory called `Libs`. Then create another folder in the project directory called `App` with the subfolders `Inc` and `Src`. Now also create a folder called `sta` in the `Inc` folder. Finally add the empty files `App/Inc/sta/config.hpp` and `App/Src/startup.cpp`. @@ -34,7 +34,7 @@ Libs/ ... ``` -### Setting up the dependencies +### Setting up the Dependencies First it is recommended to initialize a git repository in the project folder with `git init`. Then add the TACOS, sta-core and rtos2-utils repositories as submodules in the `Libs` folder with the following commands: ```bash @@ -113,7 +113,7 @@ PS: For not officially supported chips use this as the include: #define STA_PLATFORM_STM32 ``` -### Implementing your own threads +### Implementing Your Own Threads Let's create a simple thread that prints "Hello World" every second. First create a new file in the `App/Inc/tasks` folder called `spam_task.hpp`. Then add the following code: ```cpp @@ -190,7 +190,7 @@ The function `startup()` is a weakly implemented function that is executed right And that's it! Now you have a thread that prints "Hello World" every second. Simply build the project and flash it to your microcontroller and be amazed by the Spam! -### Setting up the CAN Bus +### \[Optional\] Setting up the CAN Bus To enable the CAN Bus two things need to be done: 1. Enable CAN in the IOC with the RX0 and RX1 Interrupts enabled. @@ -245,5 +245,19 @@ namespace sta } ``` +## TACOS Usage Guide + +Almost all of the important aspects of working with TACOS have already been discussed when setting up the project itself. The following sections will give you an in-depth explanation of how to use the statemachine, CAN-Bus and inter-thread communication. + +### Using the Statemachine + +The statemachine forms the heart and soul of a TACOS-based project. Upon initialization, TACOS starts a statemachine that manages the system state and the currently active threads. As seen before, whenever we pass a new thread to TACOS we also have to provide all states in which the thread should run. After each state transition from state $ x $ to state $ y $ the statemachine task performs two actions: +1. All threads that should run in state $ y $ but are not currently running are started. +2. All threads that should not run in state $ y $ but are currently running are stopped. +> [!IMPORTANT] +> The statemachine does immediately stop a thread and deletes it from memory. Instead, the thread is allowed to finish the current execution of its `func` before entering a blocked state. This allows the thread to release all its resources. + +A state transition can be triggered by calling the functions `requestState()`, `forceState()` or `setStateTimed()` that are provided in `sta/tacos.hpp`. Additionally, state transitions can be triggered remotely using the CAN-Bus. This is discussed in more detail in the section discussing the CAN Bus. + ### Further information To look into other function of TACOS please consult the READMEs in the include folder or the doxygen documentation. Also consult the sta-core and rtos2-utils READMEs for further information on the features that TACOS uses. \ No newline at end of file From e89b21ddcc9c34a6f0e7c7099a545533b5fecc98 Mon Sep 17 00:00:00 2001 From: dario Date: Sun, 1 Dec 2024 15:02:15 +0100 Subject: [PATCH 10/16] Documentation for the statemachine. --- README.md | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c624a7b..e75b21f 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ namespace sta ## TACOS Usage Guide -Almost all of the important aspects of working with TACOS have already been discussed when setting up the project itself. The following sections will give you an in-depth explanation of how to use the statemachine, CAN-Bus and inter-thread communication. +Almost all of the important aspects of working with TACOS have already been discussed when setting up the project itself. The following sections will give you an in-depth explanation of how to use the statemachine, inter-thread communication and the CAN-Bus. ### Using the Statemachine @@ -257,7 +257,119 @@ The statemachine forms the heart and soul of a TACOS-based project. Upon initial > [!IMPORTANT] > The statemachine does immediately stop a thread and deletes it from memory. Instead, the thread is allowed to finish the current execution of its `func` before entering a blocked state. This allows the thread to release all its resources. -A state transition can be triggered by calling the functions `requestState()`, `forceState()` or `setStateTimed()` that are provided in `sta/tacos.hpp`. Additionally, state transitions can be triggered remotely using the CAN-Bus. This is discussed in more detail in the section discussing the CAN Bus. +In order to fully understand the statemachine, we have to take a look at the _lockout_ and _failsafe timer_. These lockout and failsafe timers are the result of design choices made during early stages of STAHR. The goal was to combine the state estimation (i.e. sensor fusion using a Kalman filter) with timer-based safety mechanisms. For example, our goal was to block any state transition to the state `DROGUE` before 60 seconds after liftoff. Additionally, a timer was started to automatically switch to state `DROGUE` after 120 seconds after liftoff. + +These safety mechanisms resulted in the implementation of the lockout and failsafe timer in TACOS: +1. The lockout timer can be started after a state transition. As long as it is running, all state transitions are blocked by the statemachines, unless the user actively chooses to bypass the safety mechanism using `forceState()`. +2. The failsafe timer can be used to request a state transition after a certain period of time has elapsed. This transition will be blocked if the lockout time is running at that time. The failsafe timer obeys the following rules: + - A timed state transition can be requested even when the lockout timer is active. It only matters if the lockout timer is running at the end of the time span of the lockout timer. + - If a state transition is triggered before the end of the time span, the failsafe timer is stopped. + +A state transition can be triggered by calling the functions `requestState()`, `forceState()` or `setStateTimed()` that are provided in `sta/tacos.hpp`. Additionally, state transitions can be triggered remotely using the CAN-Bus, however, this is discussed in more detail in the section on the CAN Bus. + +#### `requestState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true)` + +Requests a state transition from state `from` to the state `to`. Will be blocked if `from` is not the current state or if the lockout time is running. Optinally, use the `lockout` parameter to also start the lockout timer if the state transition is successful. The argument `publish` tells the TACOS to also publish the state on via the CAN bus + +#### `forceState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true)` + +Forces a state transition from state `from` to the state `to`, bypassing the lockout timer. However, in order to protect the integrity of the statemachine, `from` has to match the current state. Similar to `requestState()`, an optional lockout timer can be set and the state can be published via CAN. + +#### `setStateTimed(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout = 0, bool publish = false)` + +Schedules a state transition from state `from` to state `to` in `millis` milliseconds. Additionally, a lockout timer of `lockout` milliseconds can be set which will become active _after_ successful state transition from `from` to `to`. Similarly, setting `publish` to `true` will result in a successful state transition being broadcast on the CAN bus. + +> [!IMPORTANT] +> If the failsafe timer is already running with a different state transition, calling `setStateTimed` will overwrite this request. + +#### Example Usage + +Generally, the state transitions are requested in the `startup()` function or in TacosThread instances implemented by the user. It is good practise to give your states names by defining an enum in a header file `states.hpp` that can be included everywhere in your project. + +```cpp +#ifndef MY_PROJECT_STATES_HPP +#define MY_PROJECT_STATES_HPP + +namespace my_project +{ + enum class States : uint16_t + { + STARTUP = 0, + PING = 1, + PONG = 2 + }; +} + +#endif // MY_PROJECT_STATES_HPP +``` + +This gives us three states: `STARTUP`, `PING` and `PONG`. Generally, these names have no meaning for TACOS but they make your software more readable. Next, we define two modified tasks based on `SpamTask` for our project: +```cpp +#include +#include +#include + +namespace tasks { + PingTask::PingTask() : + TacosThread("PING", osPriorityNormal){} + + void PingTask::func() { + sleep(100); + + STA_DEBUG_PRINTLN("PING"); + + sta::tacos::requestState(my_project::PING, my_project::PONG); + } + + PongTask::PongTask() : + TacosThread("PONG", osPriorityNormal){} + + void PongTask::func() { + sleep(100); + + STA_DEBUG_PRINTLN("PONG"); + + sta::tacos::requestState(my_project::PONG, my_project::PING); + } +} // namespace tasks +``` +> [!IMPORTANT] +> Generally, you want both tasks to be implemented in separate .cpp files. + +Using these two threads we can implement our `startup()` function: + +```cpp + +#include +#include +#include + +#include + +namespace sta +{ + namespace tacos + { + void startup() + { + // Register a "PingTask" thread for the state PING. + sta::tacos::addThread({my_project::PING}); + sta::tacos::addThread({my_project::PONG}); + + // Start with the spam after one second. + sta::tacos::setStateTimed(my_project::STARTUP, my_project::PING, 1000); + } + } // namespace tacos +} // namespace sta +``` + +The resulting program switches between the states `PING` and `PONG` and alternately outputs "PING" and "PONG" via UART. While this is just a toy example, building more complicated applications is not much harder! + +### Using Inter-Thread Communication + +> [!IMPORTANT] +> Coming soon! + ### Further information To look into other function of TACOS please consult the READMEs in the include folder or the doxygen documentation. Also consult the sta-core and rtos2-utils READMEs for further information on the features that TACOS uses. \ No newline at end of file From 36c160e8e351c69db3377cd656b3224b844071f9 Mon Sep 17 00:00:00 2001 From: dario Date: Sun, 1 Dec 2024 15:11:42 +0100 Subject: [PATCH 11/16] Updated statemachine documentation to reference existing documentation --- README.md | 21 +++------------------ include/sta/README.md | 9 ++++++--- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index e75b21f..29c757b 100644 --- a/README.md +++ b/README.md @@ -260,27 +260,12 @@ The statemachine forms the heart and soul of a TACOS-based project. Upon initial In order to fully understand the statemachine, we have to take a look at the _lockout_ and _failsafe timer_. These lockout and failsafe timers are the result of design choices made during early stages of STAHR. The goal was to combine the state estimation (i.e. sensor fusion using a Kalman filter) with timer-based safety mechanisms. For example, our goal was to block any state transition to the state `DROGUE` before 60 seconds after liftoff. Additionally, a timer was started to automatically switch to state `DROGUE` after 120 seconds after liftoff. These safety mechanisms resulted in the implementation of the lockout and failsafe timer in TACOS: -1. The lockout timer can be started after a state transition. As long as it is running, all state transitions are blocked by the statemachines, unless the user actively chooses to bypass the safety mechanism using `forceState()`. -2. The failsafe timer can be used to request a state transition after a certain period of time has elapsed. This transition will be blocked if the lockout time is running at that time. The failsafe timer obeys the following rules: +1. **Lockout Timer**: The lockout timer can be started after a state transition. As long as it is running, all state transitions are blocked by the statemachines, unless the user actively chooses to bypass the safety mechanism using `forceState()`. +2. **Failsafe Timer** The failsafe timer can be used to schedule a state transition after a certain period of time has elapsed. This transition will be blocked if the lockout timer is running at that time. The failsafe timer obeys the following rules: - A timed state transition can be requested even when the lockout timer is active. It only matters if the lockout timer is running at the end of the time span of the lockout timer. - If a state transition is triggered before the end of the time span, the failsafe timer is stopped. -A state transition can be triggered by calling the functions `requestState()`, `forceState()` or `setStateTimed()` that are provided in `sta/tacos.hpp`. Additionally, state transitions can be triggered remotely using the CAN-Bus, however, this is discussed in more detail in the section on the CAN Bus. - -#### `requestState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true)` - -Requests a state transition from state `from` to the state `to`. Will be blocked if `from` is not the current state or if the lockout time is running. Optinally, use the `lockout` parameter to also start the lockout timer if the state transition is successful. The argument `publish` tells the TACOS to also publish the state on via the CAN bus - -#### `forceState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true)` - -Forces a state transition from state `from` to the state `to`, bypassing the lockout timer. However, in order to protect the integrity of the statemachine, `from` has to match the current state. Similar to `requestState()`, an optional lockout timer can be set and the state can be published via CAN. - -#### `setStateTimed(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout = 0, bool publish = false)` - -Schedules a state transition from state `from` to state `to` in `millis` milliseconds. Additionally, a lockout timer of `lockout` milliseconds can be set which will become active _after_ successful state transition from `from` to `to`. Similarly, setting `publish` to `true` will result in a successful state transition being broadcast on the CAN bus. - -> [!IMPORTANT] -> If the failsafe timer is already running with a different state transition, calling `setStateTimed` will overwrite this request. +A state transition can be triggered by calling the functions `requestState()`, `forceState()` or `setStateTimed()` that are provided in `sta/tacos.hpp`. Take a look at `include/sta/README.md` for more details on the functions. Additionally, state transitions can be triggered remotely using the CAN-Bus, however, this is discussed in more detail in the section on the CAN Bus. #### Example Usage diff --git a/include/sta/README.md b/include/sta/README.md index b39d0bc..31632bd 100644 --- a/include/sta/README.md +++ b/include/sta/README.md @@ -15,7 +15,7 @@ Retrieves the current state of the TACOS state machine. void requestState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true) ``` -Requests a state transition. Invalid state transitions will be dismissed. First come, first serve. +Requests a state transition. Invalid state transitions will be dismissed. First come, first serve. Can be blocked by the lockout timer. - **Parameters**: - `from`: The starting state for the transition. @@ -27,7 +27,7 @@ Requests a state transition. Invalid state transitions will be dismissed. First void forceState(uint32_t from, uint32_t to, uint32_t lockout = 0, bool publish = true) ``` -Forces a state transition. Will be ignored if already in the given state. Triggers instantly. +Forces a state transition. Will be ignored if already in the given state. Triggers instantly. Ignores the lockout timer. - **Parameters**: - `from`: The starting state for the transition. @@ -41,7 +41,7 @@ Forces a state transition. Will be ignored if already in the given state. Trigge void setStateTimed(uint32_t from, uint32_t to, uint32_t millis, uint32_t lockout = 0, bool publish = false) ``` -Requests a state transition after a specified time. +Requests a state transition after a specified time. Equivalent to calling `requestState` after a specified time. - **Parameters**: - `from`: The starting state. @@ -49,6 +49,9 @@ Requests a state transition after a specified time. - `millis`: The wait time in milliseconds before requesting the transition. - `lockout`: (Optional) A timer for blocking subsequent transitions. +> [!IMPORTANT] +> If there is already a timed transition scheduled, calling `setStateTimed` will overwrite this request. + --- ```cpp From 0dacc4e23241d23a6f07ae7734f34f0d88139c11 Mon Sep 17 00:00:00 2001 From: dario Date: Sun, 1 Dec 2024 15:25:07 +0100 Subject: [PATCH 12/16] Fixed important typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29c757b..28fc05c 100644 --- a/README.md +++ b/README.md @@ -255,7 +255,7 @@ The statemachine forms the heart and soul of a TACOS-based project. Upon initial 1. All threads that should run in state $ y $ but are not currently running are started. 2. All threads that should not run in state $ y $ but are currently running are stopped. > [!IMPORTANT] -> The statemachine does immediately stop a thread and deletes it from memory. Instead, the thread is allowed to finish the current execution of its `func` before entering a blocked state. This allows the thread to release all its resources. +> The statemachine does not immediately stop a thread and deletes it from memory. Instead, the thread is allowed to finish the current execution of its `func` before entering a blocked state. This allows the thread to release all its resources. In order to fully understand the statemachine, we have to take a look at the _lockout_ and _failsafe timer_. These lockout and failsafe timers are the result of design choices made during early stages of STAHR. The goal was to combine the state estimation (i.e. sensor fusion using a Kalman filter) with timer-based safety mechanisms. For example, our goal was to block any state transition to the state `DROGUE` before 60 seconds after liftoff. Additionally, a timer was started to automatically switch to state `DROGUE` after 120 seconds after liftoff. From 1f9af8ebc0a5e5f8c497bca2864f71b439158a6f Mon Sep 17 00:00:00 2001 From: dario Date: Sat, 7 Dec 2024 13:53:57 +0000 Subject: [PATCH 13/16] README.md aktualisiert --- README.md | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6195cf9..f04f39c 100644 --- a/README.md +++ b/README.md @@ -84,25 +84,13 @@ In order to use TACOS, you need to provide a configuration file in the path `sta #ifndef INC_STA_CONFIG_HPP_ #define INC_STA_CONFIG_HPP_ -// Using a board with an ASEAG module present. -#define STA_STM32_ASEAG // Use the STM32F407 microprocessor. #include -// Doesn't really do too much right now. Has to be added for successful compilation. -#define STA_PRINTF_USE_STDLIB - // Enable debug serial output and assertions. -#define STA_ASSERT_FORCE +#define STA_ASSERT_ENABLED #define STA_DEBUGGING_ENABLED -// Enable Features -#define STA_RTOS_SYSTEM_EVENTS_ENABLE -// #define STA_RTOS_SYSTEM_WATCHDOG_ENABLE -// #define STA_RTOS_WATCHDOG_ENABLE -// #define STA_TACOS_WATCHDOG_FREQUENCY 10000 -#define STA_CAN_BUS_ENABLE - // Statemachine settings. #define STA_TACOS_NUM_STATES 3 @@ -118,6 +106,9 @@ PS: For not officially supported chips use this as the include: #define STA_PLATFORM_STM32 ``` +> [!WARNING] +> If you want to use debug printing (enabled using the macro `STA_DEBUGGING_ENABLED`) in a TACOS project, you should also enable float formatting under `Project Settings -> C/C++ Build -> MPU/MCU Settings -> Use float with printf from newlib-nano`. This allows you to format print floats using `STA_DEBUG_PRINTF`. + ### Implementing your own threads Let's create a simple thread that prints "Hello World" every second. First create a new file in the `App/Inc/tasks` folder called `spam_task.hpp`. Then add the following code: @@ -148,6 +139,7 @@ This code defines a new thread that inherits from `TacosThread` and implements t Now create a new file in the `App/Src/tasks` folder called `spam_task.cpp` and add the following code: ```cpp #include +#include namespace tasks { SpamTask::SpamTask() : From 4625d72632e73fe6f58adf2b634a587bacaf8991 Mon Sep 17 00:00:00 2001 From: dario Date: Sat, 7 Dec 2024 14:10:42 +0000 Subject: [PATCH 14/16] README.md aktualisiert --- README.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f04f39c..9de5dce 100644 --- a/README.md +++ b/README.md @@ -99,15 +99,34 @@ In order to use TACOS, you need to provide a configuration file in the path `sta #endif /* INC_STA_CONFIG_HPP_ */ ``` -PS: For not officially supported chips use this as the include: + +> [!WARNING] +> If you want to use debug printing (enabled using the macro `STA_DEBUGGING_ENABLED`) in a TACOS project, you should also enable float formatting under `Project Settings -> C/C++ Build -> MPU/MCU Settings -> Use float with printf from newlib-nano`. This allows you to format print floats using `STA_DEBUG_PRINTF`. If this setting is not enabled, your software will crash when debug printing floats. + +The configuration file shown in the example above initializes the project assuming that you are working on a STM32 Nucleo of type F407. Typically, you are using a different microcontroller, however. In this case you can replace the line ```cpp +#include +``` +with the include +```cpp +#include +``` +So far, only a few chips are officially supported. For not officially supported chips use this as the include: +```cpp +#define STA_STM32_SWD_USART_IDX + #include #define STA_MCU_LITTLE_ENDIAN #define STA_PLATFORM_STM32 ``` +> [!NOTE] +> The definition of `STA_STM32_SWD_USART_IDX` allows you to specify which UART handle to use for debug printing. If undefined, a default handle for Nucleos will be used. You can also add the macro `STA_STM32_ASEAG` instead if you are a cool kid using ASEAG-based hardware. -> [!WARNING] -> If you want to use debug printing (enabled using the macro `STA_DEBUGGING_ENABLED`) in a TACOS project, you should also enable float formatting under `Project Settings -> C/C++ Build -> MPU/MCU Settings -> Use float with printf from newlib-nano`. This allows you to format print floats using `STA_DEBUG_PRINTF`. +> [!WARNING] +> The definition of `STA_STM32_SWD_USART_IDX` has to be placed _before_ the include `cpp #include ` + +> [!WARNING] +> Make sure you actually enable the UART bus in the under `Pinout & Configuration -> Connectivity` in the IOC. ### Implementing your own threads From cda1e75f2b416eea6b5518672157501072c6157e Mon Sep 17 00:00:00 2001 From: dario Date: Sat, 7 Dec 2024 14:12:22 +0000 Subject: [PATCH 15/16] README.md aktualisiert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9de5dce..114392f 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ So far, only a few chips are officially supported. For not officially supported #define STA_PLATFORM_STM32 ``` > [!NOTE] -> The definition of `STA_STM32_SWD_USART_IDX` allows you to specify which UART handle to use for debug printing. If undefined, a default handle for Nucleos will be used. You can also add the macro `STA_STM32_ASEAG` instead if you are a cool kid using ASEAG-based hardware. +> The definition of `STA_STM32_SWD_USART_IDX` allows you to specify which UART handle to use for debug printing. If undefined, a default handle for Nucleos will be used. You can also add the macro `STA_STM32_ASEAG` instead if you are a cool kid using ASEAG-based hardware 😎. > [!WARNING] > The definition of `STA_STM32_SWD_USART_IDX` has to be placed _before_ the include `cpp #include ` From 91179bd68462cb4e5971c9d818d0fbf092ad87d4 Mon Sep 17 00:00:00 2001 From: dario Date: Thu, 12 Dec 2024 19:49:37 +0000 Subject: [PATCH 16/16] README.md aktualisiert --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 114392f..e0a6447 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ To use TACOS one should implement threads, which fulfill the various roles of th ### Setting up the project -First one must create a new CubeIDE project with FreeRTOS. To avoid doing that however we recommend using the [ioc-collection](https://git.intern.spaceteamaachen.de/ALPAKA/ioc-collection) to get a preconfigured IOC for the STM microcontroller you are using. From here follow the following steps: +First one must create a new CubeIDE project with FreeRTOS. To avoid doing that however we recommend using the [ioc-collection](https://git.spaceteamaachen.de/ALPAKA/ioc-collection) to get a preconfigured IOC for the STM microcontroller you are using. From here follow the following steps: 1. ```Import -> General -> Import an Existing STM32CubeMX Configuration File (.ioc)``` 2. Select the .ioc file from the ioc-collection