Merge pull request 'FreeRTOS PeriodicTask improvement' (#116) from KSat/fsfw:mueller_PeriodicTaskImprovements into master

This commit is contained in:
Steffen Gaisser 2020-07-28 12:45:27 +02:00
commit b74fbbddb9
5 changed files with 165 additions and 79 deletions

View File

@ -1,17 +1,19 @@
#include "PeriodicTask.h"
#include <framework/serviceinterface/ServiceInterfaceStream.h> #include <framework/serviceinterface/ServiceInterfaceStream.h>
#include <framework/tasks/ExecutableObjectIF.h> #include <framework/tasks/ExecutableObjectIF.h>
#include "PeriodicTask.h"
PeriodicTask::PeriodicTask(const char *name, TaskPriority setPriority, PeriodicTask::PeriodicTask(const char *name, TaskPriority setPriority,
TaskStackSize setStack, TaskPeriod setPeriod, TaskStackSize setStack, TaskPeriod setPeriod,
void (*setDeadlineMissedFunc)()) : void (*setDeadlineMissedFunc)()) :
started(false), handle(NULL), period(setPeriod), deadlineMissedFunc( started(false), handle(NULL), period(setPeriod), deadlineMissedFunc(
setDeadlineMissedFunc) { setDeadlineMissedFunc)
{
BaseType_t status = xTaskCreate(taskEntryPoint, name, setStack, this, setPriority, &handle); BaseType_t status = xTaskCreate(taskEntryPoint, name,
setStack, this, setPriority, &handle);
if(status != pdPASS){ if(status != pdPASS){
sif::debug << "PeriodicTask Insufficient heap memory remaining. Status: " sif::debug << "PeriodicTask Insufficient heap memory remaining. "
<< status << std::endl; "Status: " << status << std::endl;
} }
} }
@ -21,16 +23,19 @@ PeriodicTask::~PeriodicTask(void) {
} }
void PeriodicTask::taskEntryPoint(void* argument) { void PeriodicTask::taskEntryPoint(void* argument) {
//The argument is re-interpreted as PeriodicTask. The Task object is global, so it is found from any place. // The argument is re-interpreted as PeriodicTask. The Task object is
// global, so it is found from any place.
PeriodicTask *originalTask(reinterpret_cast<PeriodicTask*>(argument)); PeriodicTask *originalTask(reinterpret_cast<PeriodicTask*>(argument));
// Task should not start until explicitly requested /* Task should not start until explicitly requested,
// in FreeRTOS, tasks start as soon as they are created if the scheduler is running * but in FreeRTOS, tasks start as soon as they are created if the scheduler
// but not if the scheduler is not running. * is running but not if the scheduler is not running.
// to be able to accommodate both cases we check a member which is set in #startTask() * To be able to accommodate both cases we check a member which is set in
// if it is not set and we get here, the scheduler was started before #startTask() was called and we need to suspend * #startTask(). If it is not set and we get here, the scheduler was started
// if it is set, the scheduler was not running before #startTask() was called and we can continue * before #startTask() was called and we need to suspend if it is set,
* the scheduler was not running before #startTask() was called and we
* can continue */
if (!originalTask->started) { if (not originalTask->started) {
vTaskSuspend(NULL); vTaskSuspend(NULL);
} }
@ -59,31 +64,73 @@ void PeriodicTask::taskFunctionality() {
TickType_t xLastWakeTime; TickType_t xLastWakeTime;
const TickType_t xPeriod = pdMS_TO_TICKS(this->period * 1000.); const TickType_t xPeriod = pdMS_TO_TICKS(this->period * 1000.);
/* The xLastWakeTime variable needs to be initialized with the current tick /* The xLastWakeTime variable needs to be initialized with the current tick
count. Note that this is the only time the variable is written to explicitly. count. Note that this is the only time the variable is written to
After this assignment, xLastWakeTime is updated automatically internally within explicitly. After this assignment, xLastWakeTime is updated automatically
vTaskDelayUntil(). */ internally within vTaskDelayUntil(). */
xLastWakeTime = xTaskGetTickCount(); xLastWakeTime = xTaskGetTickCount();
/* Enter the loop that defines the task behavior. */ /* Enter the loop that defines the task behavior. */
for (;;) { for (;;) {
for (ObjectList::iterator it = objectList.begin(); for (auto const& object: objectList) {
it != objectList.end(); ++it) { object->performOperation();
(*it)->performOperation();
} }
//TODO deadline missed check
checkMissedDeadline(xLastWakeTime, xPeriod);
vTaskDelayUntil(&xLastWakeTime, xPeriod); vTaskDelayUntil(&xLastWakeTime, xPeriod);
} }
} }
ReturnValue_t PeriodicTask::addComponent(object_id_t object) { ReturnValue_t PeriodicTask::addComponent(object_id_t object, bool setTaskIF) {
ExecutableObjectIF* newObject = objectManager->get<ExecutableObjectIF>( ExecutableObjectIF* newObject = objectManager->get<ExecutableObjectIF>(
object); object);
if (newObject == NULL) { if (newObject == nullptr) {
sif::error << "PeriodicTask::addComponent: Invalid object. Make sure"
"it implements ExecutableObjectIF!" << std::endl;
return HasReturnvaluesIF::RETURN_FAILED; return HasReturnvaluesIF::RETURN_FAILED;
} }
objectList.push_back(newObject); objectList.push_back(newObject);
if(setTaskIF) {
newObject->setTaskIF(this);
}
return HasReturnvaluesIF::RETURN_OK; return HasReturnvaluesIF::RETURN_OK;
} }
uint32_t PeriodicTask::getPeriodMs() const { uint32_t PeriodicTask::getPeriodMs() const {
return period * 1000; return period * 1000;
} }
void PeriodicTask::checkMissedDeadline(const TickType_t xLastWakeTime,
const TickType_t interval) {
/* Check whether deadline was missed while also taking overflows
* into account. Drawing this on paper with a timeline helps to understand
* it. */
TickType_t currentTickCount = xTaskGetTickCount();
TickType_t timeToWake = xLastWakeTime + interval;
// Tick count has overflown
if(currentTickCount < xLastWakeTime) {
// Time to wake has overflown as well. If the tick count
// is larger than the time to wake, a deadline was missed.
if(timeToWake < xLastWakeTime and
currentTickCount > timeToWake) {
handleMissedDeadline();
}
}
// No tick count overflow. If the timeToWake has not overflown
// and the current tick count is larger than the time to wake,
// a deadline was missed.
else if(timeToWake > xLastWakeTime and currentTickCount > timeToWake) {
handleMissedDeadline();
}
}
void PeriodicTask::handleMissedDeadline() {
#ifdef DEBUG
sif::warning << "PeriodicTask: " << pcTaskGetName(NULL) <<
" missed deadline!\n" << std::flush;
#endif
if(deadlineMissedFunc != nullptr) {
this->deadlineMissedFunc();
}
}

View File

@ -1,48 +1,49 @@
#ifndef MULTIOBJECTTASK_H_ #ifndef FRAMEWORK_OSAL_FREERTOS_PERIODICTASK_H_
#define MULTIOBJECTTASK_H_ #define FRAMEWORK_OSAL_FREERTOS_PERIODICTASK_H_
#include <framework/objectmanager/ObjectManagerIF.h> #include <framework/objectmanager/ObjectManagerIF.h>
#include <framework/tasks/PeriodicTaskIF.h> #include <framework/tasks/PeriodicTaskIF.h>
#include <framework/tasks/Typedef.h> #include <framework/tasks/Typedef.h>
#include <FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include "task.h" #include <freertos/task.h>
#include <vector> #include <vector>
class ExecutableObjectIF; class ExecutableObjectIF;
/** /**
* @brief This class represents a specialized task for periodic activities of multiple objects. * @brief This class represents a specialized task for
* * periodic activities of multiple objects.
* @details MultiObjectTask is an extension to ObjectTask in the way that it is able to execute
* multiple objects that implement the ExecutableObjectIF interface. The objects must be
* added prior to starting the task.
*
* @ingroup task_handling * @ingroup task_handling
*/ */
class PeriodicTask: public PeriodicTaskIF { class PeriodicTask: public PeriodicTaskIF {
public: public:
/** /**
* @brief Standard constructor of the class. * Keep in Mind that you need to call before this vTaskStartScheduler()!
* @details The class is initialized without allocated objects. These need to be added * A lot of task parameters are set in "FreeRTOSConfig.h".
* with #addObject. * TODO: why does this need to be called before vTaskStartScheduler?
* In the underlying TaskBase class, a new operating system task is created. * @details
* In addition to the TaskBase parameters, the period, the pointer to the * The class is initialized without allocated objects.
* aforementioned initialization function and an optional "deadline-missed" * These need to be added with #addComponent.
* function pointer is passed. * @param priority
* @param priority Sets the priority of a task. Values range from a low 0 to a high 99. * Sets the priority of a task. Values depend on freeRTOS configuration,
* @param stack_size The stack size reserved by the operating system for the task. * high number means high priority.
* @param setPeriod The length of the period with which the task's functionality will be * @param stack_size
* executed. It is expressed in clock ticks. * The stack size reserved by the operating system for the task.
* @param setDeadlineMissedFunc The function pointer to the deadline missed function * @param setPeriod
* that shall be assigned. * The length of the period with which the task's
* functionality will be executed. It is expressed in clock ticks.
* @param setDeadlineMissedFunc
* The function pointer to the deadline missed function that shall
* be assigned.
*/ */
PeriodicTask(const char *name, TaskPriority setPriority, TaskStackSize setStack, TaskPeriod setPeriod, PeriodicTask(const char *name, TaskPriority setPriority,
TaskStackSize setStack, TaskPeriod setPeriod,
void (*setDeadlineMissedFunc)()); void (*setDeadlineMissedFunc)());
/** /**
* @brief Currently, the executed object's lifetime is not coupled with the task object's * @brief Currently, the executed object's lifetime is not coupled with
* lifetime, so the destructor is empty. * the task object's lifetime, so the destructor is empty.
*/ */
virtual ~PeriodicTask(void); virtual ~PeriodicTask(void);
@ -53,58 +54,72 @@ public:
* The address of the task object is passed as an argument * The address of the task object is passed as an argument
* to the system call. * to the system call.
*/ */
ReturnValue_t startTask(void); ReturnValue_t startTask() override;
/** /**
* Adds an object to the list of objects to be executed. * Adds an object to the list of objects to be executed.
* The objects are executed in the order added. * The objects are executed in the order added.
* @param object Id of the object to add. * @param object Id of the object to add.
* @return RETURN_OK on success, RETURN_FAILED if the object could not be added. * @return
* -@c RETURN_OK on success
* -@c RETURN_FAILED if the object could not be added.
*/ */
ReturnValue_t addComponent(object_id_t object); ReturnValue_t addComponent(object_id_t object,
bool setTaskIF = true) override;
uint32_t getPeriodMs() const; uint32_t getPeriodMs() const override;
ReturnValue_t sleepFor(uint32_t ms); ReturnValue_t sleepFor(uint32_t ms) override;
protected: protected:
bool started; bool started;
TaskHandle_t handle; TaskHandle_t handle;
typedef std::vector<ExecutableObjectIF*> ObjectList; //!< Typedef for the List of objects. //! Typedef for the List of objects.
typedef std::vector<ExecutableObjectIF*> ObjectList;
/** /**
* @brief This attribute holds a list of objects to be executed. * @brief This attribute holds a list of objects to be executed.
*/ */
ObjectList objectList; ObjectList objectList;
/** /**
* @brief The period of the task. * @brief The period of the task.
* @details The period determines the frequency of the task's execution. It is expressed in clock ticks. * @details
* The period determines the frequency of the task's execution.
* It is expressed in clock ticks.
*/ */
TaskPeriod period; TaskPeriod period;
/** /**
* @brief The pointer to the deadline-missed function. * @brief The pointer to the deadline-missed function.
* @details This pointer stores the function that is executed if the task's deadline is missed. * @details
* So, each may react individually on a timing failure. The pointer may be NULL, * This pointer stores the function that is executed if the task's deadline
* then nothing happens on missing the deadline. The deadline is equal to the next execution * is missed so each may react individually on a timing failure.
* of the periodic task. * The pointer may be NULL, then nothing happens on missing the deadline.
* The deadline is equal to the next execution of the periodic task.
*/ */
void (*deadlineMissedFunc)(void); void (*deadlineMissedFunc)(void);
/** /**
* @brief This is the function executed in the new task's context. * @brief This is the function executed in the new task's context.
* @details It converts the argument back to the thread object type and copies the class instance * @details
* to the task context. The taskFunctionality method is called afterwards. * It converts the argument back to the thread object type and copies the
* class instance to the task context. The taskFunctionality method is
* called afterwards.
* @param A pointer to the task object itself is passed as argument. * @param A pointer to the task object itself is passed as argument.
*/ */
static void taskEntryPoint(void* argument); static void taskEntryPoint(void* argument);
/** /**
* @brief The function containing the actual functionality of the task. * @brief The function containing the actual functionality of the task.
* @details The method sets and starts * @details
* the task's period, then enters a loop that is repeated as long as the isRunning * The method sets and starts the task's period, then enters a loop that is
* attribute is true. Within the loop, all performOperation methods of the added * repeated as long as the isRunning attribute is true. Within the loop,
* objects are called. Afterwards the checkAndRestartPeriod system call blocks the task * all performOperation methods of the added objects are called.
* until the next period. * Afterwards the checkAndRestartPeriod system call blocks the task until
* the next period.
* On missing the deadline, the deadlineMissedFunction is executed. * On missing the deadline, the deadlineMissedFunction is executed.
*/ */
void taskFunctionality(void); void taskFunctionality(void);
void checkMissedDeadline(const TickType_t xLastWakeTime,
const TickType_t interval);
void handleMissedDeadline();
}; };
#endif /* MULTIOBJECTTASK_H_ */ #endif /* PERIODICTASK_H_ */

View File

@ -21,13 +21,18 @@ void* PeriodicPosixTask::taskEntryPoint(void* arg) {
return NULL; return NULL;
} }
ReturnValue_t PeriodicPosixTask::addComponent(object_id_t object) { ReturnValue_t PeriodicPosixTask::addComponent(object_id_t object,
bool addTaskIF) {
ExecutableObjectIF* newObject = objectManager->get<ExecutableObjectIF>( ExecutableObjectIF* newObject = objectManager->get<ExecutableObjectIF>(
object); object);
if (newObject == NULL) { if (newObject == nullptr) {
return HasReturnvaluesIF::RETURN_FAILED; return HasReturnvaluesIF::RETURN_FAILED;
} }
objectList.push_back(newObject); objectList.push_back(newObject);
if(setTaskIF) {
newObject->setTaskIF(this);
}
return HasReturnvaluesIF::RETURN_OK; return HasReturnvaluesIF::RETURN_OK;
} }

View File

@ -39,11 +39,12 @@ public:
* @param object Id of the object to add. * @param object Id of the object to add.
* @return RETURN_OK on success, RETURN_FAILED if the object could not be added. * @return RETURN_OK on success, RETURN_FAILED if the object could not be added.
*/ */
ReturnValue_t addComponent(object_id_t object); ReturnValue_t addComponent(object_id_t object,
bool addTaskIF = true) override;
uint32_t getPeriodMs() const; uint32_t getPeriodMs() const override;
ReturnValue_t sleepFor(uint32_t ms); ReturnValue_t sleepFor(uint32_t ms) override;
private: private:
typedef std::vector<ExecutableObjectIF*> ObjectList; //!< Typedef for the List of objects. typedef std::vector<ExecutableObjectIF*> ObjectList; //!< Typedef for the List of objects.

View File

@ -1,9 +1,11 @@
#ifndef PERIODICTASKIF_H_ #ifndef FRAMEWORK_TASK_PERIODICTASKIF_H_
#define PERIODICTASKIF_H_ #define FRAMEWORK_TASK_PERIODICTASKIF_H_
#include <framework/objectmanager/SystemObjectIF.h> #include <framework/objectmanager/SystemObjectIF.h>
#include <framework/timemanager/Clock.h>
#include <cstddef> #include <cstddef>
class ExecutableObjectIF; class ExecutableObjectIF;
/** /**
* New version of TaskIF * New version of TaskIF
* Follows RAII principles, i.e. there's no create or delete method. * Follows RAII principles, i.e. there's no create or delete method.
@ -17,11 +19,27 @@ public:
*/ */
virtual ~PeriodicTaskIF() { } virtual ~PeriodicTaskIF() { }
/** /**
* @brief With the startTask method, a created task can be started for the first time. * @brief With the startTask method, a created task can be started
* for the first time.
*/ */
virtual ReturnValue_t startTask() = 0; virtual ReturnValue_t startTask() = 0;
virtual ReturnValue_t addComponent(object_id_t object) {return HasReturnvaluesIF::RETURN_FAILED;}; /**
* Add a component (object) to a periodic task. The pointer to the
* task can be set optionally
* @param object
* Add an object to the task. The most important case is to add an
* executable object with a function which will be called regularly
* (see ExecutableObjectIF)
* @param setTaskIF
* Can be used to specify whether the task object pointer is passed
* to the component.
* @return
*/
virtual ReturnValue_t addComponent(object_id_t object,
bool setTaskIF = true) {
return HasReturnvaluesIF::RETURN_FAILED;
};
virtual ReturnValue_t sleepFor(uint32_t ms) = 0; virtual ReturnValue_t sleepFor(uint32_t ms) = 0;