# TACOS 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...). 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 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/ ... ``` ### 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 */ extern void startTACOS(void *); startTACOS(argument); /* Infinite loop */ for(;;) { osDelay(1); } /* USER CODE END StartDefaultTask */ } ``` 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_ // 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_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 // Uses the default configuration for TACOS. #include #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 ``` ### 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 ``` > [!WARNING] > A thread's priority must be strictly lower than the statemachine's priority. Unless manually changed, this is always `osPriorityHigh`. 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 startup() { // ###### 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 ``` The function `startup()` is a weakly implemented function that is executed right before TACOS initializes its statemachine task. It serves as an entry point for the user to initialize all busses, threads and rtos2-utils stuff that is needed for the application to fulfill its purpose. 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. 2. Add the following code to the `sta/config.hpp` file: ``` #define STA_TACOS_CAN_BUS_ENABLE ``` PS: For not officially supported chips add this: ``` #define STA_STM32_CAN_HANDLE {YOUR_HCAN_HANDLE} ``` 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.