Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
38 changes: 36 additions & 2 deletions components/interrupt/example/main/interrupt_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,43 @@ extern "C" void app_main(void) {
logger.info("Minimum queue size over last 4 seconds: {}", min_queue_size);
}

//! [interrupt example]

//! [interrupt task config example]
// The interrupt handler task is started automatically in the constructor, but
// its priority and core affinity (configured here, or via Kconfig in the BSP
// components) can be changed at runtime.
{
espp::Interrupt interrupt({
.interrupts = {io0},
.task_config =
{
.name = "Interrupt task",
.stack_size_bytes = 6192,
.priority = 5,
.core_id = 0,
},
.log_level = espp::Logger::Verbosity::DEBUG,
});

// raise the handler task priority - this is applied to the running task
// immediately (returns true), and is also used if the task is restarted
bool applied = interrupt.set_task_priority(10);
logger.info("Raised interrupt task priority to 10, applied live: {}", applied);

// changing the core affinity only takes effect when the task is (re)started,
// because the default ESP-IDF FreeRTOS port fixes a task's core at creation
interrupt.set_task_core_id(1);
// so stop and start the task to re-create it pinned to the new core
interrupt.stop_task();
interrupt.start_task();
logger.info("Restarted interrupt task on core 1");

std::this_thread::sleep_for(2s);
}
//! [interrupt task config example]

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
esp_intr_dump(stdout);
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)

//! [interrupt example]
}
75 changes: 75 additions & 0 deletions components/interrupt/include/interrupt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ namespace espp {
///
/// \section interrupt_ex0 Interrupt Example
/// \snippet interrupt_example.cpp interrupt example
/// \section interrupt_ex1 Interrupt Task Reconfiguration Example
/// \snippet interrupt_example.cpp interrupt task config example
class Interrupt : public BaseComponent {
public:
/// \brief The event for the interrupt
Expand Down Expand Up @@ -210,6 +212,79 @@ class Interrupt : public BaseComponent {
/// with hardware filtering).
size_t get_min_queue_size() const { return min_queue_size_; }

/// \brief Start the interrupt handler task
/// \return true if the task was started, false if it was already running or
/// there is no task
/// \details The handler task is started automatically in the constructor, so
/// this is only needed after a matching stop_task() - for example to
/// re-create the task so that a new core id set via set_task_core_id()
/// takes effect (the default ESP-IDF FreeRTOS port fixes a task's
/// core affinity when the task is created).
/// \sa Task::start()
bool start_task() {
if (!task_) {
logger_.error("No task to start");
return false;
}
return task_->start();
}

/// \brief Stop the interrupt handler task
/// \return true if the task was stopped, false if it was not running or there
/// is no task
/// \details While the task is stopped, interrupt events are not serviced, but
/// the GPIO ISRs still run and keep enqueueing events. Those events
/// are not dropped: they remain in the queue and are serviced when
/// start_task() is called - up until the queue fills (event_queue_size
/// entries), after which further events are lost until the task drains
/// it. Pair with start_task() to resume. This is primarily useful to
/// apply a new core id from set_task_core_id() by stopping and
/// restarting the task.
/// \sa Task::stop()
bool stop_task() {
if (!task_) {
logger_.error("No task to stop");
return false;
}
return task_->stop();
}

/// \brief Set the priority of the interrupt handler task
/// \param priority New FreeRTOS priority (0 is lowest priority)
/// \return true if the change was applied to the running task, false otherwise
/// \details Forwards to Task::set_priority(). The new priority is always
/// stored and used the next time the task is started; if the task is
/// already running it is also applied immediately. This lets callers
/// override the priority configured at construction (e.g. via Kconfig
/// in the BSP components) at runtime.
/// \sa Task::set_priority()
bool set_task_priority(size_t priority) {
if (!task_) {
logger_.error("No task to set priority on");
return false;
}
return task_->set_priority(priority);
}

/// \brief Set the core affinity (core ID) of the interrupt handler task
/// \param core_id Core to pin the task to (0 or 1), or -1 to leave it unpinned
/// \return true if the change was applied to the running task, false otherwise
/// \details Forwards to Task::set_core_id(). The new core ID is always stored
/// and used the next time the task is started. On the default
/// ESP-IDF FreeRTOS port a running task's core affinity cannot be
/// changed, so for an already-started handler this only takes effect
/// after the task is restarted - call stop_task() then start_task()
/// to apply it. This lets callers override the core configured at
/// construction (e.g. via Kconfig in the BSP components).
/// \sa Task::set_core_id()
bool set_task_core_id(int core_id) {
if (!task_) {
logger_.error("No task to set core id on");
return false;
}
return task_->set_core_id(core_id);
}

/// \brief Add an interrupt to the interrupt handler
/// \param interrupt The interrupt to add
void add_interrupt(const PinConfig &interrupt) {
Expand Down
35 changes: 35 additions & 0 deletions components/task/example/main/task_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,41 @@ extern "C" void app_main(void) {
test_duration = std::chrono::duration<float>(test_end - test_start).count();
logger.debug("Test ran for {:.03f} seconds", test_duration);

/**
* Show an example of changing a task's priority and core affinity at runtime.
*/
{
logger.info("Task priority / core affinity example");
//! [Task Priority and Core example]
auto task_fn = [](std::mutex &m, std::condition_variable &cv) {
std::unique_lock<std::mutex> lk(m);
cv.wait_for(lk, 100ms);
return false; // keep running
};
auto task = espp::Task({.callback = task_fn,
.task_config = {.name = "Reconfig Task", .priority = 5, .core_id = 0},
.log_level = espp::Logger::Verbosity::DEBUG});
task.start();
fmt::println("Task started on core {} at priority {}", espp::Task::get_core_id(task),
espp::Task::get_priority(task));

// priority changes apply to the running task immediately
bool applied = task.set_priority(10);
fmt::println("set_priority(10) applied live: {}, priority now {}", applied,
espp::Task::get_priority(task));

// core affinity changes are stored but only take effect when the task is
// (re)started on the default ESP-IDF FreeRTOS port (a task's core is fixed
// when it is created), so stop() then start() to re-create it on core 1
task.set_core_id(1);
task.stop();
task.start();
fmt::println("After restart, task running on core {}", espp::Task::get_core_id(task));

std::this_thread::sleep_for(500ms);
//! [Task Priority and Core example]
}

/**
* Show an example of a task which is stopped by multiple other tasks
* (ensuring that stop can be called multiple times from multiple other
Expand Down
36 changes: 36 additions & 0 deletions components/task/include/task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ namespace espp {
* \snippet task_example.cpp Task Info example
* \section task_ex7 Task Request Stop Example
* \snippet task_example.cpp Task Request Stop example
* \section task_ex8 Task Priority and Core Affinity Example
* \snippet task_example.cpp Task Priority and Core example
*
* \section run_on_core_ex1 Run on Core Example
* \snippet task_example.cpp run on core example
Expand Down Expand Up @@ -270,6 +272,40 @@ class Task : public espp::BaseComponent {
*/
bool is_running() const;

/**
* @brief Set the priority of the task.
* @details The new priority is always stored in the task's configuration, so
* it will be used the next time the task is started. If the task is
* currently running (ESP only), the change is also applied to the
* live task immediately via vTaskPrioritySet().
* @param priority New FreeRTOS priority (0 is lowest priority on ESP /
* FreeRTOS). It is clamped to [0, configMAX_PRIORITIES - 1] on ESP.
* @return true if the change was applied to the currently-running task; false
* if the task is not running (the new value still takes effect the
* next time the task is started) or the platform does not support
* changing a live task's priority.
*/
bool set_priority(size_t priority);

/**
* @brief Set the core affinity (core ID) of the task.
* @details The new core ID is always stored in the task's configuration, so
* it will be used the next time the task is started. On the default
* ESP-IDF FreeRTOS port a running task's core affinity cannot be
* changed (it is fixed when the task is created), so for an
* already-started task this only takes effect after a stop()/start().
* If the underlying FreeRTOS build does provide a runtime
* core-affinity API (configUSE_CORE_AFFINITY on a multi-core SMP
* build), the change is applied to the live task immediately.
* @param core_id Core to pin the task to (0 or 1), or -1 to leave the task
* unpinned (able to run on any core).
* @return true if the change was applied to the currently-running task; false
* if the task is not running or the platform cannot change a live
* task's core affinity (in which case the new value still takes
* effect the next time the task is started).
*/
bool set_core_id(int core_id);

#if defined(ESP_PLATFORM) || defined(_DOXYGEN_)
/**
* @brief Start the task watchdog for this task.
Expand Down
55 changes: 55 additions & 0 deletions components/task/src/task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,61 @@ bool Task::is_started() const { return started_; }

bool Task::is_running() const { return is_started(); }

bool Task::set_priority(size_t priority) {
#if defined(ESP_PLATFORM)
// clamp to the valid FreeRTOS priority range
if (priority >= configMAX_PRIORITIES) {
logger_.warn("Requested priority ({}) >= configMAX_PRIORITIES ({}), clamping", priority,
configMAX_PRIORITIES);
priority = configMAX_PRIORITIES - 1;
}
#endif
// always store the new priority so it is used on the next start()
config_.priority = priority;
logger_.debug("Set priority to {} for task '{}'", priority, name_);
#if defined(ESP_PLATFORM)
// if the task is running, apply the change to the live task as well
auto handle = static_cast<TaskHandle_t>(get_id());
if (started_ && handle != nullptr) {
vTaskPrioritySet(handle, static_cast<UBaseType_t>(priority));
return true;
}
#endif
return false;
}

bool Task::set_core_id(int core_id) {
// always store the new core id so it is used on the next start()
config_.core_id = core_id;
logger_.debug("Set core id to {} for task '{}'", core_id, name_);
#if defined(ESP_PLATFORM) && defined(configUSE_CORE_AFFINITY) && (configUSE_CORE_AFFINITY == 1) && \
(configNUMBER_OF_CORES > 1)
// this FreeRTOS build supports changing a live task's core affinity
auto handle = static_cast<TaskHandle_t>(get_id());
if (started_ && handle != nullptr) {
UBaseType_t affinity_mask;
if (core_id < 0) {
// not pinned: allow the task to run on any core
affinity_mask = (1u << configNUMBER_OF_CORES) - 1u;
} else {
affinity_mask = 1u << core_id;
}
vTaskCoreAffinitySet(handle, affinity_mask);
return true;
}
return false;
#else
// the default ESP-IDF FreeRTOS port fixes core affinity at task creation, so
// the new core id only takes effect the next time the task is started
if (started_) {
logger_.warn("Cannot change core affinity of running task '{}'; new core id ({}) will take "
"effect on next start()",
name_, core_id);
}
return false;
#endif
}

#if defined(ESP_PLATFORM) || defined(_DOXYGEN_)
std::string Task::get_info() {
return fmt::format("[T] '{}',{},{},{}", pcTaskGetName(nullptr), xPortGetCoreID(),
Expand Down
Loading