diff --git a/src/shared/scheduler/TaskScheduler.cpp b/src/shared/scheduler/TaskScheduler.cpp
index eb63590d3ac89ade8d832fc9b10fdf179f6d6f25..b7e9645016c002163c410a0d3cbabe2cdcd82459 100644
--- a/src/shared/scheduler/TaskScheduler.cpp
+++ b/src/shared/scheduler/TaskScheduler.cpp
@@ -23,10 +23,13 @@
#include "TaskScheduler.h"
#include <diagnostic/SkywardStack.h>
+#include <utils/TimeUtils.h>
#include <algorithm>
+#include <mutex>
using namespace std;
+using namespace std::chrono;
using namespace miosix;
namespace Boardcore
@@ -51,52 +54,47 @@ TaskScheduler::TaskScheduler(miosix::Priority priority)
tasks.emplace_back();
}
-size_t TaskScheduler::addTask(function_t function, uint32_t period,
- Policy policy, int64_t startTick)
+size_t TaskScheduler::addTask(function_t function, nanoseconds period,
+ Policy policy, time_point<steady_clock> startTime)
{
- // In the case of early returns, using RAII mutex wrappers to unlock the
- // mutex would cause it to be locked and unlocked one more time before
- // returning, because of the destructor being called on the Unlock object
- // first and then on the Lock object. To avoid this, we don't use RAII
- // wrappers and manually lock and unlock the mutex instead.
- mutex.lock();
+ std::unique_lock<miosix::FastMutex> lock{mutex};
if (tasks.size() >= MAX_TASKS)
{
// Unlock the mutex to release the scheduler resources before logging
- mutex.unlock();
+ lock.unlock();
LOG_ERR(logger, "Full task scheduler");
return 0;
}
if (policy == Policy::ONE_SHOT)
{
- startTick += period;
+ startTime += period;
}
// Insert a new task with the given parameters
- tasks.emplace_back(function, period, policy, startTick);
+ tasks.emplace_back(function, period.count(), policy,
+ startTime.time_since_epoch().count());
size_t id = tasks.size() - 1;
// Only add the task to the agenda if the scheduler is running
// Otherwise, the agenda will be populated when the scheduler is started
if (isRunning())
{
- agenda.emplace(id, startTick);
+ agenda.emplace(id, startTime.time_since_epoch().count());
}
condvar.broadcast(); // Signals the run thread
- mutex.unlock();
return id;
}
void TaskScheduler::enableTask(size_t id)
{
- mutex.lock();
+ std::unique_lock<miosix::FastMutex> lock{mutex};
if (id > tasks.size() - 1)
{
- mutex.unlock();
+ lock.unlock();
LOG_ERR(logger, "Tried to enable an out-of-range task, id = {}", id);
return;
}
@@ -108,29 +106,30 @@ void TaskScheduler::enableTask(size_t id)
// exception
if (task.empty())
{
- mutex.unlock();
+ lock.unlock();
LOG_WARN(logger, "Tried to enable an empty task, id = {}", id);
return;
}
task.enabled = true;
- agenda.emplace(id, Kernel::getOldTick() + task.period);
- mutex.unlock();
+ agenda.emplace(id, miosix::getTime() + task.period);
}
void TaskScheduler::disableTask(size_t id)
{
- mutex.lock();
+ std::unique_lock<miosix::FastMutex> lock{mutex};
if (id > tasks.size() - 1)
{
- mutex.unlock();
+ lock.unlock();
LOG_ERR(logger, "Tried to disable an out-of-range task, id = {}", id);
return;
}
- tasks[id].enabled = false;
- mutex.unlock();
+ Task& task = tasks[id];
+ task.enabled = false;
+ // Reset the last call time to avoid incorrect period statistics
+ task.lastCall = -1;
}
bool TaskScheduler::start()
@@ -178,21 +177,24 @@ vector<TaskStatsResult> TaskScheduler::getTaskStats()
void TaskScheduler::populateAgenda()
{
- int64_t currentTick = Kernel::getOldTick();
+ int64_t currentTime = miosix::getTime();
for (size_t id = 1; id < tasks.size(); id++)
{
- Task& task = tasks[id];
+ Task& task = tasks[id];
+ int64_t startTime = task.startTime;
- int64_t nextTick = task.startTick;
- // Normalize the tasks start time if they precede the current tick
- if (nextTick < currentTick)
+ // Shift the task's start time if it precedes the current time
+ // to avoid clumping all tasks at the beginning (see issue #91)
+ if (startTime < currentTime)
{
- nextTick +=
- ((currentTick - nextTick) / task.period + 1) * task.period;
+ int64_t timeSinceStart = currentTime - startTime;
+ int64_t periodsMissed = timeSinceStart / task.period;
+ int64_t periodsToSkip = periodsMissed + 1;
+ startTime += periodsToSkip * task.period;
}
- agenda.emplace(id, nextTick);
+ agenda.emplace(id, startTime);
}
}
@@ -213,19 +215,12 @@ void TaskScheduler::run()
return;
}
- int64_t startTick = Kernel::getOldTick();
+ int64_t startTime = miosix::getTime();
Event nextEvent = agenda.top();
- Task& nextTask = tasks[nextEvent.taskId];
- // If the task has the SKIP policy and its execution was missed, we need
- // to move it forward to match the period
- if (nextEvent.nextTick < startTick && nextTask.policy == Policy::SKIP)
- {
- agenda.pop();
- enqueue(nextEvent, startTick);
- }
- else if (nextEvent.nextTick <= startTick)
+ if (nextEvent.nextTime <= startTime)
{
+ Task& nextTask = tasks[nextEvent.taskId];
agenda.pop();
// Execute the task function
@@ -246,42 +241,42 @@ void TaskScheduler::run()
}
// Enqueue only on a valid task
- updateStats(nextEvent, startTick, Kernel::getOldTick());
- enqueue(nextEvent, startTick);
+ updateStats(nextEvent, startTime, miosix::getTime());
+ enqueue(nextEvent, startTime);
}
}
else
{
Unlock<FastMutex> unlock(lock);
- Kernel::Thread::sleepUntil(nextEvent.nextTick);
+ Thread::nanoSleepUntil(nextEvent.nextTime);
}
}
}
-void TaskScheduler::updateStats(const Event& event, int64_t startTick,
- int64_t endTick)
+void TaskScheduler::updateStats(const Event& event, int64_t startTime,
+ int64_t endTime)
{
Task& task = tasks[event.taskId];
- // Activation stats
- float activationError = startTick - event.nextTick;
- task.activationStats.add(activationError);
+ float activationTime = startTime - event.nextTime;
+ task.activationStats.add(activationTime / Constants::NS_IN_MS);
- // Period stats
int64_t lastCall = task.lastCall;
if (lastCall >= 0)
- task.periodStats.add((startTick - lastCall));
-
- // Update the last call tick to the current start tick for the next
+ {
+ float periodTime = startTime - lastCall;
+ task.periodStats.add(periodTime / Constants::NS_IN_MS);
+ }
+ // Update the last call time to the current start time for the next
// iteration
- task.lastCall = startTick;
+ task.lastCall = startTime;
- // Workload stats
- task.workloadStats.add(endTick - startTick);
+ float workloadTime = endTime - startTime;
+ task.workloadStats.add(workloadTime / Constants::NS_IN_MS);
}
-void TaskScheduler::enqueue(Event event, int64_t startTick)
+void TaskScheduler::enqueue(Event event, int64_t startTime)
{
Task& task = tasks[event.taskId];
switch (task.policy)
@@ -292,21 +287,26 @@ void TaskScheduler::enqueue(Event event, int64_t startTick)
task.enabled = false;
return;
case Policy::SKIP:
+ {
+ // Compute the number of missed periods since the last execution
+ int64_t timeSinceLastExec = startTime - event.nextTime;
+ int64_t periodsMissed = timeSinceLastExec / task.period;
+
+ // Schedule the task executon to the next aligned period, by
+ // skipping over the missed ones
+ // E.g. 3 periods have passed since last execution, the next viable
+ // schedule time is after 4 periods
+ int64_t periodsToSkip = periodsMissed + 1;
+ // Update the task to run at the next viable timeslot, while still
+ // being aligned to the original one
+ event.nextTime += periodsToSkip * task.period;
+
// Updated the missed events count
- task.missedEvents += (startTick - event.nextTick) / task.period;
-
- // Compute the number of periods between the tick the event should
- // have been run and the tick it actually run. Than adds 1 and
- // multiply the period to get the next execution tick still aligned
- // to the original one.
- // E.g. If a task has to run once every 2 ticks and start at tick 0
- // but for whatever reason the first execution is at tick 3, then
- // the next execution will be at tick 4.
- event.nextTick +=
- ((startTick - event.nextTick) / task.period + 1) * task.period;
+ task.missedEvents += static_cast<uint32_t>(periodsMissed);
break;
+ }
case Policy::RECOVER:
- event.nextTick += task.period;
+ event.nextTime += task.period;
break;
}
@@ -316,15 +316,15 @@ void TaskScheduler::enqueue(Event event, int64_t startTick)
}
TaskScheduler::Task::Task()
- : function(nullptr), period(0), startTick(0), enabled(false),
+ : function(nullptr), period(0), startTime(0), enabled(false),
policy(Policy::SKIP), lastCall(-1), activationStats(), periodStats(),
workloadStats(), missedEvents(0), failedEvents(0)
{
}
-TaskScheduler::Task::Task(function_t function, uint32_t period, Policy policy,
- int64_t startTick)
- : function(function), period(period), startTick(startTick), enabled(true),
+TaskScheduler::Task::Task(function_t function, int64_t period, Policy policy,
+ int64_t startTime)
+ : function(function), period(period), startTime(startTime), enabled(true),
policy(policy), lastCall(-1), activationStats(), periodStats(),
workloadStats(), missedEvents(0), failedEvents(0)
{
diff --git a/src/shared/scheduler/TaskScheduler.h b/src/shared/scheduler/TaskScheduler.h
index 84f5c97d7777846582618ab19645f95e55e5ce5c..3b506c22fcff49e4dc110fe1441d683086bac856 100644
--- a/src/shared/scheduler/TaskScheduler.h
+++ b/src/shared/scheduler/TaskScheduler.h
@@ -26,9 +26,11 @@
#include <Singleton.h>
#include <debug/debug.h>
#include <diagnostic/PrintLogger.h>
+#include <units/Frequency.h>
#include <utils/KernelTime.h>
#include <utils/Stats/Stats.h>
+#include <chrono>
#include <cstdint>
#include <functional>
#include <list>
@@ -70,29 +72,32 @@ public:
/**
* @brief Task behavior policy.
+ * Determines the behavior of the scheduler for a specific task.
*
- * This policies allows to change the behavior of the scheduler for the
- * specific task:
- * - ONE_SHOT: Allows to run the task once. When it is executed, it is
- * removed from the tasks list.
- * - SKIP: If for whatever reason a task can't be executed when
- * it is supposed to (e.g. another thread occupies the CPU), the scheduler
- * doesn't recover the missed executions but instead skips those and
- * continues normally. This ensures that all the events are aligned with
- * the original start tick. In other words, the period and the start tick of
- * a task specifies the time slots the task has to be executed. If one of
- * this time slots can't be used, that specific execution won't be
- * recovered.
- * - RECOVER: On the other hand, the RECOVER policy ensures that the missed
- * executions are run. However, this will cause the period to not be
- * respected and the task will run consecutively for some time (See issue
- * #91).
+ * - ONE_SHOT: Runs the task once and subsequently removes it from the task
+ * list. This is useful for one-off tasks.
+ *
+ * - SKIP: Skips missed executions. This is useful for tasks that need to
+ * execute periodically but can skip some executions.
+ * If the task misses one or more executions, the scheduler will skip the
+ * missed executions, run the task once and re-schedule the task for
+ * future execution. The scheduler will try to align the task execution time
+ * with the original start time, but actual execution time is not guaranteed
+ * to be aligned with the period.
+ *
+ * - RECOVER: Recovers missed executions. This is useful for
+ * tasks that need to reach an overall number of iterations, but don't care
+ * too much about timing.
+ * Missed executions are recovered immediately, so this may cause one or
+ * more tasks to clump at the beginning of the task queue until all missed
+ * executions are recovered, causing the period to not be respected (see
+ * issue #91).
*/
enum class Policy : uint8_t
{
ONE_SHOT, ///< Run the task one single timer.
SKIP, // Skips lost executions and stays aligned with the original
- // start tick.
+ // start time.
RECOVER ///< Prioritize the number of executions over the period.
};
@@ -100,7 +105,8 @@ public:
1);
/**
- * @brief Add a task function to the scheduler with an auto generated ID.
+ * @brief Add a millisecond-period task function to the scheduler with an
+ * auto generated ID.
*
* Note that each task has it's own unique ID, even one shot tasks!
*
@@ -108,14 +114,70 @@ public:
* executed immediately, otherwise after the given period.
*
* @param function Function to be called periodically.
- * @param period Inter call period [ms].
- * @param policy Task policy, default is SKIP.
- * @param startTick First activation time, useful for synchronizing tasks.
+ * @param periodMs Inter call period [ms].
+ * @param policy Task policy, default is RECOVER.
+ * @param startTick Absolute system tick of the first activation, useful
+ * for synchronizing tasks [ms]
* @return The ID of the task if it was added successfully, 0 otherwise.
*/
- size_t addTask(function_t function, uint32_t period,
+ size_t addTask(function_t function, uint32_t periodMs,
Policy policy = Policy::RECOVER,
- int64_t startTick = Kernel::getOldTick());
+ int64_t startTick = Kernel::getOldTick())
+ {
+ auto period = std::chrono::milliseconds{periodMs};
+ auto startTime = std::chrono::time_point<std::chrono::steady_clock>{
+ std::chrono::milliseconds{startTick}};
+
+ return addTask(function, period, policy, startTime);
+ }
+
+ /**
+ * @brief Add a task function with the given frequency to the scheduler with
+ * an auto generated ID.
+ *
+ * Note that each task has it's own unique ID, even one shot tasks!
+ *
+ * For one shot tasks, the period is used as a delay. If 0 the task will be
+ * executed immediately, otherwise after the given period.
+ *
+ * @param function Function to be called periodically.
+ * @param frequency Task frequency [Hz].
+ * @param policy Task policy, default is RECOVER.
+ * @param startTime Absolute system time of the first activation, useful for
+ * synchronizing tasks [ns]
+ * @return The ID of the task if it was added successfully, 0 otherwise.
+ */
+ size_t addTask(function_t function, Units::Frequency::Hertz frequency,
+ Policy policy = Policy::RECOVER,
+ std::chrono::time_point<std::chrono::steady_clock>
+ startTime = std::chrono::steady_clock::now())
+ {
+ auto period = std::chrono::nanoseconds{
+ static_cast<int64_t>(sToNs(1) / frequency.value())};
+
+ return addTask(function, period, policy, startTime);
+ }
+
+ /**
+ * @brief Add a task function with the given period to the scheduler with an
+ * auto generated ID.
+ *
+ * Note that each task has it's own unique ID, even one shot tasks!
+ *
+ * For one shot tasks, the period is used as a delay. If 0 the task will be
+ * executed immediately, otherwise after the given period.
+ *
+ * @param function Function to be called periodically.
+ * @param period Inter call period [ns].
+ * @param policy Task policy, default is RECOVER.
+ * @param startTime Absolute system time of the first activation, useful for
+ * synchronizing tasks [ns]
+ * @return The ID of the task if it was added successfully, 0 otherwise.
+ */
+ size_t addTask(function_t function, std::chrono::nanoseconds period,
+ Policy policy = Policy::RECOVER,
+ std::chrono::time_point<std::chrono::steady_clock>
+ startTime = std::chrono::steady_clock::now());
/**
* @brief Enables the task with the given id.
@@ -137,13 +199,13 @@ private:
struct Task
{
function_t function;
- uint32_t period; // [ms]
- int64_t startTick; ///< First activation time, useful for synchronizing
+ int64_t period; ///< [ns]
+ int64_t startTime; ///< First activation time, useful for synchronizing
///< tasks.
bool enabled; ///< Whether the task should be executed.
Policy policy;
- int64_t lastCall; ///< Last activation tick for statistics computation.
- Stats activationStats; ///< Stats about activation tick error.
+ int64_t lastCall; ///< Last activation time for statistics computation.
+ Stats activationStats; ///< Stats about activation time error.
Stats periodStats; ///< Stats about period error.
Stats workloadStats; ///< Stats about time the task takes to compute.
uint32_t missedEvents; ///< Number of events that could not be run.
@@ -158,12 +220,12 @@ private:
* @brief Creates a task with the given parameters
*
* @param function The std::function to be called
- * @param period The Period in [ms]
+ * @param period The Period in [ns]
* @param policy The task policy in case of a miss
- * @param startTick The first activation time
+ * @param startTime The first activation time
*/
- explicit Task(function_t function, uint32_t period, Policy policy,
- int64_t startTick);
+ explicit Task(function_t function, int64_t period, Policy policy,
+ int64_t startTime);
// Delete copy constructor and copy assignment operator to avoid copying
// and force moving
@@ -184,27 +246,27 @@ private:
struct Event
{
size_t taskId; ///< The task to execute.
- int64_t nextTick; ///< Tick of next activation.
+ int64_t nextTime; ///< Absolute time of next activation.
- Event(size_t taskId, int64_t nextTick)
- : taskId(taskId), nextTick(nextTick)
+ Event(size_t taskId, int64_t nextTime)
+ : taskId(taskId), nextTime(nextTime)
{
}
/**
- * @brief Compare two events based on the next tick.
- * @note This is used to have the event with the lowest tick first in
+ * @brief Compare two events based on the next time.
+ * @note This is used to have the event with the lowest time first in
* the agenda. Newly pushed events are moved up in the queue (see
- * heap bubble-up) until the other tick is lower.
+ * heap bubble-up) until the other time is lower.
*/
bool operator>(const Event& other) const
{
- return this->nextTick > other.nextTick;
+ return this->nextTime > other.nextTime;
}
};
// Use `std::greater` as the comparator to have elements with the lowest
- // tick first. Requires operator `>` to be defined for Event.
+ // time first. Requires operator `>` to be defined for Event.
using EventQueue =
std::priority_queue<Event, std::vector<Event>, std::greater<Event>>;
@@ -228,13 +290,13 @@ private:
/**
* @brief Update task statistics (Intended for when the task is executed).
*
- * This function changes the task last call tick to the startTick.
+ * This function changes the task last call time to the startTime.
*
* \param event Current event.
- * \param startTick Start of execution tick.
- * \param endTick End of execution tick.
+ * \param startTime Start of execution time.
+ * \param endTime End of execution time.
*/
- void updateStats(const Event& event, int64_t startTick, int64_t endTick);
+ void updateStats(const Event& event, int64_t startTime, int64_t endTime);
/**
* @brief (Re)Enqueue an event into the agenda based on the scheduling
@@ -243,17 +305,17 @@ private:
* Requires the mutex to be locked!
*
* \param event Event to be scheduled. Note: this parameter is modified, the
- * nextTick field is updated in order to respect the task interval.
- * \param startTick Activation tick, needed to update the nextTick value of
+ * nextTime field is updated in order to respect the task interval.
+ * \param startTime Activation time, needed to update the nextTime value of
* the event.
*/
- void enqueue(Event event, int64_t startTick);
+ void enqueue(Event event, int64_t startTime);
static TaskStatsResult fromTaskIdPairToStatsResult(const Task& task,
size_t id)
{
return TaskStatsResult{id,
- task.period,
+ std::chrono::nanoseconds{task.period},
task.activationStats.getStats(),
task.periodStats.getStats(),
task.workloadStats.getStats(),
diff --git a/src/shared/scheduler/TaskSchedulerData.h b/src/shared/scheduler/TaskSchedulerData.h
index 8b612714a0f1bda9de9a29914a9f7d71560248c3..916669c65700af218c8f3ae64b62028d0edb780f 100644
--- a/src/shared/scheduler/TaskSchedulerData.h
+++ b/src/shared/scheduler/TaskSchedulerData.h
@@ -24,6 +24,7 @@
#include <utils/Stats/Stats.h>
+#include <chrono>
#include <cstdint>
#include <ostream>
@@ -44,7 +45,7 @@ namespace Boardcore
struct TaskStatsResult
{
size_t id;
- uint32_t period;
+ std::chrono::nanoseconds period;
StatsResult activationStats;
StatsResult periodStats;
StatsResult workloadStats;
@@ -61,15 +62,16 @@ struct TaskStatsResult
void print(std::ostream& os) const
{
- os << (int)id << "," << period << "," << activationStats.minValue << ","
- << activationStats.maxValue << "," << activationStats.mean << ","
- << activationStats.stdDev << "," << activationStats.nSamples << ","
- << periodStats.minValue << "," << periodStats.maxValue << ","
- << periodStats.mean << "," << periodStats.stdDev << ","
- << periodStats.nSamples << "," << workloadStats.minValue << ","
- << workloadStats.maxValue << "," << workloadStats.mean << ","
- << workloadStats.stdDev << "," << workloadStats.nSamples << ","
- << missedEvents << "," << failedEvents << "\n";
+ os << (int)id << "," << period.count() << ","
+ << activationStats.minValue << "," << activationStats.maxValue << ","
+ << activationStats.mean << "," << activationStats.stdDev << ","
+ << activationStats.nSamples << "," << periodStats.minValue << ","
+ << periodStats.maxValue << "," << periodStats.mean << ","
+ << periodStats.stdDev << "," << periodStats.nSamples << ","
+ << workloadStats.minValue << "," << workloadStats.maxValue << ","
+ << workloadStats.mean << "," << workloadStats.stdDev << ","
+ << workloadStats.nSamples << "," << missedEvents << ","
+ << failedEvents << "\n";
}
};
diff --git a/src/shared/units/Frequency.h b/src/shared/units/Frequency.h
new file mode 100644
index 0000000000000000000000000000000000000000..5a92d3728e7f2de29b408fc83df345abfbd8eaad
--- /dev/null
+++ b/src/shared/units/Frequency.h
@@ -0,0 +1,70 @@
+/* Copyright (c) 2024 Skyward Experimental Rocketry
+ * Author: Niccolò Betto
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include "Units.h"
+
+namespace Boardcore
+{
+
+namespace Units
+{
+
+namespace Frequency
+{
+
+template <class Ratio = std::ratio<1>>
+using Frequency = Unit<UnitKind::Frequency, Ratio>;
+
+using Hertz = Frequency<>;
+using Kilohertz = Frequency<std::kilo>;
+
+// Integers
+constexpr auto operator""_hz(unsigned long long n)
+{
+ return Hertz(static_cast<float>(n));
+};
+
+constexpr auto operator""_khz(unsigned long long n)
+{
+ return Kilohertz(static_cast<float>(n));
+};
+
+// Floats
+constexpr auto operator""_hz(long double n)
+{
+ return Hertz(static_cast<float>(n));
+};
+
+constexpr auto operator""_khz(long double n)
+{
+ return Kilohertz(static_cast<float>(n));
+};
+
+} // namespace Frequency
+
+} // namespace Units
+
+} // namespace Boardcore
diff --git a/src/shared/units/Units.h b/src/shared/units/Units.h
index 6e59cbcd052c0cf7835dafe233871f686542b8b7..fd26b2b59c1fe9ee6caa75fb74b3de7f4b5b3c65 100644
--- a/src/shared/units/Units.h
+++ b/src/shared/units/Units.h
@@ -40,6 +40,7 @@ enum class UnitKind
Time,
Speed,
Acceleration,
+ Frequency,
};
// Base class to implement custom measurement units logic.
@@ -68,7 +69,7 @@ public:
}
template <UnitKind TargetKind, class TargetRatio = Ratio>
- constexpr explicit operator Unit<TargetKind, TargetRatio>() const
+ constexpr operator Unit<TargetKind, TargetRatio>() const
{
return Unit<TargetKind, TargetRatio>(value<TargetRatio>());
}
diff --git a/src/tests/scheduler/test-taskscheduler.cpp b/src/tests/scheduler/test-taskscheduler.cpp
index bf842aa1f0db2459bde6148dd5dc667e1950f45e..07ef20a1c19b7d706d8c9bcb124b6f4d412b6c90 100644
--- a/src/tests/scheduler/test-taskscheduler.cpp
+++ b/src/tests/scheduler/test-taskscheduler.cpp
@@ -35,10 +35,20 @@ GpioPin pin5 = GpioPin(GPIOD_BASE, 9);
bool taskLogEnabled; ///< A flag to enable/disable task logging
+// Proxy sleep function to print when the main thread sleeps
+namespace Thread
+{
+void sleep(unsigned int ms)
+{
+ printf("Main thread sleeping for %u ms\n", ms);
+ miosix::Thread::sleep(ms);
+}
+} // namespace Thread
+
void task2Hz()
{
pin1.high();
- delayUs(1);
+ delayUs(100);
pin1.low();
if (taskLogEnabled)
@@ -50,7 +60,7 @@ void task2Hz()
void task5Hz()
{
pin2.high();
- delayUs(1);
+ delayUs(100);
pin2.low();
if (taskLogEnabled)
@@ -62,21 +72,21 @@ void task5Hz()
void task500Hz()
{
pin3.high();
- delayUs(1);
+ delayUs(100);
pin3.low();
}
void task1KHz()
{
pin4.high();
- delayUs(1);
+ delayUs(100);
pin4.low();
}
void signalPin5()
{
pin5.high();
- delayUs(1);
+ delayUs(100);
pin5.low();
}
@@ -110,18 +120,25 @@ void setup()
void printTaskStats(TaskScheduler& scheduler)
{
- printf("Tasks stats:\n");
+ printf("* Tasks stats\n");
for (auto stat : scheduler.getTaskStats())
{
- printf("- %d:\n", stat.id);
- printf("\tActivation: %.2f, %.2f\n", stat.activationStats.mean,
- stat.activationStats.stdDev);
- printf("\tPeriod: %.2f, %.2f\n", stat.periodStats.mean,
- stat.periodStats.stdDev);
- printf("\tWorkload: %.2f, %.2f\n", stat.workloadStats.mean,
- stat.workloadStats.stdDev);
- printf("\tMissed events: %ld\n", stat.missedEvents);
- printf("\tFailed events: %ld\n", stat.failedEvents);
+ float frequency = 1.0f / stat.period.count() * std::nano::den;
+ fmt::print(
+ "| Task ID {} | Frequency {} Hz:\n"
+ "|\t Average[ms] StdDev[ms]\n"
+ "|\tActivation: {:12.3g} {:12.3g}\n"
+ "|\tPeriod: {:12.3g} {:12.3g}\n"
+ "|\tWorkload: {:12.3g} {:12.3g}\n"
+ "|\t------------------------------------------\n"
+ "|\tExecutions: {:12}\n"
+ "|\tMissed events: {:12}\n"
+ "|\tFailed events: {:12}\n|\n",
+ stat.id, frequency, stat.activationStats.mean,
+ stat.activationStats.stdDev, stat.periodStats.mean,
+ stat.periodStats.stdDev, stat.workloadStats.mean,
+ stat.workloadStats.stdDev, stat.activationStats.nSamples,
+ stat.missedEvents, stat.failedEvents);
}
}
@@ -130,13 +147,18 @@ void printTaskStats(TaskScheduler& scheduler)
*/
void test_general_purpose()
{
+ using namespace Boardcore::Units::Frequency;
+ using namespace std::chrono_literals;
+
TaskScheduler scheduler{};
- int task1 = scheduler.addTask(f2Hz, 500);
- scheduler.addTask(f5Hz, 200);
- int task3 = scheduler.addTask(f500Hz, 2, TaskScheduler::Policy::RECOVER);
- scheduler.addTask(f1KHz, 1, TaskScheduler::Policy::RECOVER);
- scheduler.addTask(f1KHz, 1, TaskScheduler::Policy::RECOVER);
+ int task1 = scheduler.addTask([] { delayUs(150); }, 2_hz);
+ scheduler.addTask([] { delayUs(150); }, 5_hz);
+ int task3 = scheduler.addTask([] { delayUs(100); }, 500_hz,
+ TaskScheduler::Policy::RECOVER);
+ scheduler.addTask([] { delayUs(100); }, 1_khz,
+ TaskScheduler::Policy::RECOVER);
+ scheduler.addTask([] { delayUs(100); }, 1ms, TaskScheduler::Policy::SKIP);
printf("4 tasks added (2Hz 5Hz 500Hz 1KHz)\n");
printf("The scheduler will be started in 2 seconds\n");
@@ -187,7 +209,7 @@ void test_fill_scheduler()
TaskScheduler scheduler{};
printf("Adding tasks until the scheduler is full\n");
- size_t taskCount = 0;
+ int taskCount = 0;
// Fill up the scheduler with tasks
do
{
@@ -203,12 +225,12 @@ void test_fill_scheduler()
// Subtract one because the 0-th task is reserved
if (taskCount != TaskScheduler::MAX_TASKS - 1)
{
- printf("Error: couldn't fill the scheduler: taskCount = %zu \n",
+ printf("Error: couldn't fill the scheduler: taskCount = %d \n",
taskCount);
return;
}
- printf("Done adding tasks: taskCount = %zu\n", taskCount);
+ printf("Done adding tasks: taskCount = %d\n", taskCount);
printf("Trying to add another task\n");
// Try to add another task
@@ -218,7 +240,7 @@ void test_fill_scheduler()
return;
}
- printf("Added tasks successfully\n");
+ printf("Adding a tasks failed as expected, all good\n");
printf("Starting the scheduler\n");
scheduler.start();
@@ -314,7 +336,7 @@ void test_edge_cases()
printf("Starting the scheduler\n");
scheduler.start();
- printf("Starting the scheduler again");
+ printf("Starting the scheduler again\n");
if (scheduler.start())
{
printf("Error: started the scheduler twice\n");
@@ -322,13 +344,13 @@ void test_edge_cases()
Thread::sleep(1000);
- printf("Disabling out-of-range tasks with IDs 0 and 256");
+ printf("Disabling out-of-range tasks with IDs 0 and 256\n");
scheduler.disableTask(0);
scheduler.disableTask(256);
Thread::sleep(1000);
- printf("Enabling out-of-range tasks with IDs 0 and 256");
+ printf("Enabling out-of-range tasks with IDs 0 and 256\n");
scheduler.enableTask(0);
scheduler.enableTask(256);
@@ -365,12 +387,40 @@ void test_long_range()
scheduler.stop();
}
+/**
+ * @brief Tests the scheduler with tasks running at a high frequency
+ */
+void test_high_frequency()
+{
+ using namespace Units::Frequency;
+
+ TaskScheduler scheduler{};
+ scheduler.addTask([&] { delayUs(10); }, 1_khz);
+ scheduler.addTask([&] { delayUs(10); }, 1_khz);
+ scheduler.addTask([&] { delayUs(10); }, 2_khz);
+ scheduler.addTask([&] { delayUs(10); }, 2_khz);
+
+ printf("4 tasks added (1KHz 1KHz 2KHz 2KHz)\n");
+
+ printf("Starting the scheduler\n");
+ scheduler.start();
+
+ Thread::sleep(5 * 1000);
+
+ printf("Stopping the scheduler\n");
+ scheduler.stop();
+
+ printTaskStats(scheduler);
+}
+
} // namespace
int main()
{
setup();
+ printf("\n");
+
// Avoid clutter from tasks since this test will add a lot of tasks
taskLogEnabled = false;
printf("=> Running the fill scheduler test\n");
@@ -399,6 +449,11 @@ int main()
printf("\n");
+ printf("=> Running the high frequency task test\n");
+ test_high_frequency();
+
+ printf("\n");
+
// Avoid clutter from tasks since this test will run for a while
taskLogEnabled = false;
printf("=> Running the long range test, this may take a while\n");