Create TactilityFreertos subproject (#440)

This commit is contained in:
Ken Van Hoeylandt
2026-01-03 00:19:40 +01:00
committed by GitHub
parent a4dc633063
commit 7283920def
154 changed files with 1926 additions and 2676 deletions
+2 -2
View File
@@ -9,7 +9,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Include/"
REQUIRES mbedtls nvs_flash esp_rom esp_timer
REQUIRES TactilityFreeRtos mbedtls nvs_flash esp_rom
)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
@@ -32,7 +32,7 @@ else()
add_definitions(-D_Nonnull=)
target_link_libraries(TactilityCore
PUBLIC TactilityFreeRtos
PUBLIC mbedtls
PUBLIC freertos_kernel
)
endif()
@@ -1,6 +1,6 @@
#pragma once
#include "RtosCompat.h"
#include <Tactility/freertoscompat/RTOS.h>
namespace tt {
@@ -1,56 +0,0 @@
/**
* @file Dispatcher.h
*
* Dispatcher is a thread-safe code execution queue.
*/
#pragma once
#include "MessageQueue.h"
#include "Mutex.h"
#include "EventFlag.h"
#include <memory>
#include <queue>
namespace tt {
/**
* A thread-safe way to defer code execution.
* Generally, one task would dispatch the execution,
* while the other thread consumes and executes the work.
*/
class Dispatcher final {
public:
typedef std::function<void()> Function;
private:
Mutex mutex;
std::queue<Function> queue = {};
EventFlag eventFlag;
public:
explicit Dispatcher() = default;
~Dispatcher();
/**
* Queue a function to be consumed elsewhere.
* @param[in] function the function to execute elsewhere
* @param[in] timeout lock acquisition timeout
* @return true if dispatching was successful (timeout not reached)
*/
bool dispatch(Function function, TickType_t timeout = portMAX_DELAY);
/**
* Consume 1 or more dispatched function (if any) until the queue is empty.
* @warning The timeout is only the wait time before consuming the message! It is not a limit to the total execution time when calling this method.
* @param[in] timeout the ticks to wait for a message
* @return the amount of messages that were consumed
*/
uint32_t consume(TickType_t timeout = portMAX_DELAY);
};
} // namespace
@@ -1,36 +0,0 @@
#pragma once
#include "Dispatcher.h"
namespace tt {
/** Starts a Thread to process dispatched messages */
class DispatcherThread final {
Dispatcher dispatcher;
std::unique_ptr<Thread> thread;
bool interruptThread = true;
int32_t threadMain();
public:
explicit DispatcherThread(const std::string& threadName, size_t threadStackSize = 4096);
~DispatcherThread();
/**
* Dispatch a message.
*/
bool dispatch(Dispatcher::Function function, TickType_t timeout = portMAX_DELAY);
/** Start the thread (blocking). */
void start();
/** Stop the thread (blocking). */
void stop();
/** @return true of the thread is started */
bool isStarted() const { return thread != nullptr && !interruptThread; }
};
}
@@ -1,60 +0,0 @@
#pragma once
#include "RtosCompatEventGroups.h"
#include <memory>
namespace tt {
/**
* Wrapper for FreeRTOS xEventGroup.
*/
class EventFlag final {
struct EventGroupHandleDeleter {
void operator()(EventGroupHandle_t handleToDelete) {
vEventGroupDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<EventGroupHandle_t>, EventGroupHandleDeleter> handle;
public:
EventFlag();
~EventFlag();
enum Flag {
WaitAny = 0x00000000U, ///< Wait for any flag (default).
WaitAll = 0x00000001U, ///< Wait for all flags.
NoClear = 0x00000002U, ///< Do not clear flags which have been specified to wait for.
Error = 0x80000000U, ///< Error indicator.
ErrorUnknown = 0xFFFFFFFFU, ///< TtStatusError (-1).
ErrorTimeout = 0xFFFFFFFEU, ///< TtStatusErrorTimeout (-2).
ErrorResource = 0xFFFFFFFDU, ///< TtStatusErrorResource (-3).
ErrorParameter = 0xFFFFFFFCU, ///< TtStatusErrorParameter (-4).
ErrorISR = 0xFFFFFFFAU, ///< TtStatusErrorISR (-6).
};
/** Set the bitmask for 1 or more flags that we might be waiting for */
uint32_t set(uint32_t flags) const;
/** Clear the specified flags */
uint32_t clear(uint32_t flags) const;
/** Get the currently set flags */
uint32_t get() const;
/** Await for flags to be set
* @param[in] flags the bitmask of the flags that we want to wait for
* @param[in] options the trigger behaviour: WaitAny, WaitAll, NoClear (NoClear can be combined with either WaitAny or WaitAll)
* @param[in] timeoutTicks the maximum amount of ticks to wait
*/
uint32_t wait(
uint32_t flags,
uint32_t options = WaitAny,
uint32_t timeoutTicks = (uint32_t)portMAX_DELAY
) const;
};
} // namespace
-78
View File
@@ -1,78 +0,0 @@
#pragma once
#include "Check.h"
#include "RtosCompat.h"
#include <memory>
#include <functional>
namespace tt {
class ScopedLock;
/** Represents a lock/mutex */
class Lock {
public:
virtual ~Lock() = default;
virtual bool lock(TickType_t timeout) const = 0;
bool lock() const { return lock(portMAX_DELAY); }
virtual bool unlock() const = 0;
void withLock(TickType_t timeout, const std::function<void()>& onLockAcquired) const {
if (lock(timeout)) {
onLockAcquired();
unlock();
}
}
void withLock(TickType_t timeout, const std::function<void()>& onLockAcquired, const std::function<void()>& onLockFailure) const {
if (lock(timeout)) {
onLockAcquired();
unlock();
} else {
onLockFailure();
}
}
void withLock(const std::function<void()>& onLockAcquired) const { withLock(portMAX_DELAY, onLockAcquired); }
void withLock(const std::function<void()>& onLockAcquired, const std::function<void()>& onLockFailed) const { withLock(portMAX_DELAY, onLockAcquired, onLockFailed); }
ScopedLock asScopedLock() const;
};
/**
* Represents a lockable instance that is scoped to a specific lifecycle.
* Once the ScopedLock is destroyed, unlock() is called automatically.
*
* In other words:
* You have to lock() this object manually, but unlock() happens automatically on destruction.
*/
class ScopedLock final : public Lock {
const Lock& lockable;
public:
using Lock::lock;
explicit ScopedLock(const Lock& lockable) : lockable(lockable) {}
~ScopedLock() override {
lockable.unlock(); // We don't care whether it succeeded or not
}
bool lock(TickType_t timeout) const override {
return lockable.lock(timeout);
}
bool unlock() const override {
return lockable.unlock();
}
};
}
@@ -1,86 +0,0 @@
/**
* @file MessageQueue.h
*
* MessageQueue is a wrapper for FreeRTOS xQueue functionality.
* There is no additional thread-safety on top of the xQueue functionality,
* so make sure you create a lock if needed.
*/
#pragma once
#include <memory>
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#else
#include "FreeRTOS.h"
#include "queue.h"
#endif
namespace tt {
/**
* Message Queue implementation.
* Calls can be done from ISR/IRQ mode unless otherwise specified.
*/
class MessageQueue {
struct QueueHandleDeleter {
void operator()(QueueHandle_t handleToDelete) {
vQueueDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<QueueHandle_t>, QueueHandleDeleter> handle;
public:
/** Allocate message queue
* @param[in] capacity Maximum messages in queue
* @param[in] messageSize The size in bytes of a single message
*/
MessageQueue(uint32_t capacity, uint32_t messageSize);
~MessageQueue();
/** Post a message to the queue.
* The message is queued by copy, not by reference.
* @param[in] message A pointer to a message. The message will be copied into a buffer.
* @param[in] timeout
* @return success result
*/
bool put(const void* message, TickType_t timeout);
/** Get message from queue
* @param[out] message A pointer to an already allocated message object
* @param[in] timeout
* @return success result
*/
bool get(void* message, TickType_t timeout);
/**
* @return The maximum amount of messages that can be in the queue at any given time.
*/
uint32_t getCapacity() const;
/**
* @return The size of a single message in bytes
*/
uint32_t getMessageSize() const;
/**
* @return How many messages are currently in the queue.
*/
uint32_t getCount() const;
/**
* @return How many messages can be added to the queue before the put() method starts blocking.
*/
uint32_t getSpace() const;
/** Reset queue (cannot be called in ISR/IRQ mode)
* @return success result
*/
bool reset();
};
} // namespace
-68
View File
@@ -1,68 +0,0 @@
/**
* @file Mutex.h
* Mutex
*/
#pragma once
#include "Lock.h"
#include "RtosCompatSemaphore.h"
#include "Thread.h"
#include "kernel/Kernel.h"
#include <memory>
#include <cassert>
namespace tt {
/**
* Wrapper for FreeRTOS xSemaphoreCreateMutex
* Cannot be used in IRQ mode (within ISR context)
*/
class Mutex final : public Lock {
private:
struct SemaphoreHandleDeleter {
void operator()(QueueHandle_t handleToDelete) {
assert(!kernel::isIsr());
vSemaphoreDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<QueueHandle_t>, SemaphoreHandleDeleter> handle = std::unique_ptr<std::remove_pointer_t<QueueHandle_t>, SemaphoreHandleDeleter>(xSemaphoreCreateMutex());
public:
using Lock::lock;
explicit Mutex() {
assert(handle != nullptr);
}
~Mutex() override = default;
/** Attempt to lock the mutex. Blocks until timeout passes or lock is acquired.
* @param[in] timeout
* @return success result
*/
bool lock(TickType_t timeout) const override {
assert(!kernel::isIsr());
return xSemaphoreTake(handle.get(), timeout) == pdPASS;
}
/** Attempt to unlock the mutex.
* @return success result
*/
bool unlock() const override {
assert(!kernel::isIsr());
return xSemaphoreGive(handle.get()) == pdPASS;
}
/** @return the owner of the thread */
ThreadId getOwner() const {
assert(!kernel::isIsr());
return xSemaphoreGetMutexHolder(handle.get());
}
};
} // namespace tt
-88
View File
@@ -1,88 +0,0 @@
#pragma once
#include "Mutex.h"
#include <list>
namespace tt {
/** Publish and subscribe to messages in a thread-safe manner. */
template<typename DataType>
class PubSub final {
struct Subscription {
uint64_t id;
std::function<void(DataType)> callback;
};
typedef std::list<Subscription> Subscriptions;
uint64_t lastId = 0;
Subscriptions items;
Mutex mutex;
public:
typedef void* SubscriptionHandle;
PubSub() = default;
~PubSub() {
if (!items.empty()) {
TT_LOG_W("Loader", "Destroying PubSub with %d active subscriptions", items.size());
}
}
/** Start receiving messages at the specified handle (Threadsafe, Re-entrable)
* @param[in] callback
* @return subscription instance
*/
SubscriptionHandle subscribe(std::function<void(DataType)> callback) {
mutex.lock();
items.push_back({
.id = (++lastId),
.callback = std::move(callback)
});
mutex.unlock();
return reinterpret_cast<SubscriptionHandle>(lastId);
}
/** Stop receiving messages at the specified handle (Threadsafe, Re-entrable.)
* No use of `tt_pubsub_subscription` allowed after call of this method
* @param[in] subscription
*/
void unsubscribe(SubscriptionHandle subscription) {
assert(subscription);
mutex.lock();
bool result = false;
auto id = reinterpret_cast<uint64_t>(subscription);
for (auto it = items.begin(); it != items.end(); ++it) {
if (it->id == id) {
items.erase(it);
result = true;
break;
}
}
mutex.unlock();
tt_check(result);
}
/** Publish something to all subscribers (Threadsafe, Re-entrable.)
* @param[in] data the data to publish
*/
void publish(DataType data) {
mutex.lock();
// Iterate over subscribers
for (auto& it : items) {
it.callback(data);
}
mutex.unlock();
}
};
} // namespace
@@ -1,67 +0,0 @@
/**
* @file RecursiveMutex.h
* RecursiveMutex
*/
#pragma once
#include "Lock.h"
#include "RtosCompatSemaphore.h"
#include "Thread.h"
#include "kernel/Kernel.h"
#include <memory>
#include <cassert>
namespace tt {
/**
* Wrapper for FreeRTOS xSemaphoreCreateRecursiveMutex
* Cannot be used in IRQ mode (within ISR context)
*/
class RecursiveMutex final : public Lock {
private:
struct SemaphoreHandleDeleter {
void operator()(QueueHandle_t handleToDelete) {
assert(!kernel::isIsr());
vSemaphoreDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<QueueHandle_t>, SemaphoreHandleDeleter> handle = std::unique_ptr<std::remove_pointer_t<QueueHandle_t>, SemaphoreHandleDeleter>(xSemaphoreCreateRecursiveMutex());
public:
using Lock::lock;
explicit RecursiveMutex() {
assert(handle != nullptr);
}
~RecursiveMutex() override = default;
/** Attempt to lock the mutex. Blocks until timeout passes or lock is acquired.
* @param[in] timeout
* @return success result
*/
bool lock(TickType_t timeout) const override {
assert(!kernel::isIsr());
return xSemaphoreTakeRecursive(handle.get(), timeout) == pdPASS;
}
/** Attempt to unlock the mutex.
* @return success result
*/
bool unlock() const override {
assert(!kernel::isIsr());
return xSemaphoreGiveRecursive(handle.get()) == pdPASS;
}
/** @return the owner of the thread */
ThreadId getOwner() const {
assert(!kernel::isIsr());
return xSemaphoreGetMutexHolder(handle.get());
}
};
} // namespace
@@ -1,13 +0,0 @@
#pragma once
/**
* Compatibility includes for FreeRTOS.
* Custom FreeRTOS from ESP-IDF prefixes paths with "freertos/",
* but this isn't the normal behaviour for the regular FreeRTOS project.
*/
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#else
#include "FreeRTOS.h"
#endif
@@ -1,14 +0,0 @@
#pragma once
/**
* See explanation in RtosCompat.h
*/
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#else
#include "FreeRTOS.h"
#include "event_groups.h"
#endif
@@ -1,14 +0,0 @@
#pragma once
/**
* See explanation in RtosCompat.h
*/
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#else
#include "FreeRTOS.h"
#include "semphr.h"
#endif
@@ -1,14 +0,0 @@
#pragma once
/**
* See explanation in RtosCompat.h
*/
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#else
#include "FreeRTOS.h"
#include "task.h"
#endif
@@ -1,13 +0,0 @@
#pragma once
/**
* See explanation in RtosCompat.h
*/
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
#else
#include "FreeRTOS.h"
#include "timers.h"
#endif
@@ -1,69 +0,0 @@
#pragma once
#include "Lock.h"
#include "kernel/Kernel.h"
#include <cassert>
#include <memory>
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#else
#include "FreeRTOS.h"
#include "semphr.h"
#endif
namespace tt {
/**
* Wrapper for xSemaphoreCreateBinary (max count == 1) and xSemaphoreCreateCounting (max count > 1)
* Can be used from IRQ/ISR mode, but cannot be created/destroyed from such a context.
*/
class Semaphore final : public Lock {
struct SemaphoreHandleDeleter {
void operator()(QueueHandle_t handleToDelete) {
assert(!kernel::isIsr());
vSemaphoreDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<QueueHandle_t>, SemaphoreHandleDeleter> handle;
public:
using Lock::lock;
/**
* Cannot be called from IRQ/ISR mode.
* @param[in] maxAvailable The maximum count
* @param[in] initialAvailable The initial count
*/
Semaphore(uint32_t maxAvailable, uint32_t initialAvailable);
/**
* Cannot be called from IRQ/ISR mode.
* @param[in] maxAvailable The maximum count
*/
explicit Semaphore(uint32_t maxAvailable) : Semaphore(maxAvailable, maxAvailable) {};
/** Cannot be called from IRQ/ISR mode. */
~Semaphore() override;
Semaphore(Semaphore& other) : handle(std::move(other.handle)) {}
/** Acquire semaphore */
bool acquire(TickType_t timeout) const;
/** Release semaphore */
bool release() const;
bool lock(TickType_t timeout) const override { return acquire(timeout); }
bool unlock() const override { return release(); }
/** @return return the amount of times this semaphore can be acquired/locked */
uint32_t getAvailable() const;
};
} // namespace
@@ -1,152 +0,0 @@
#pragma once
#include <memory>
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/stream_buffer.h"
#else
#include "FreeRTOS.h"
#include "stream_buffer.h"
#endif
namespace tt {
/**
* Stream buffers are used to send a continuous stream of data from one task or
* interrupt to another. Their implementation is light weight, making them
* particularly suited for interrupt to task and core to core communication
* scenarios.
*
* **NOTE**: Stream buffer implementation assumes there is only one task or
* interrupt that will write to the buffer (the writer), and only one task or
* interrupt that will read from the buffer (the reader).
*/
class StreamBuffer final {
struct StreamBufferHandleDeleter {
void operator()(StreamBufferHandle_t handleToDelete) {
vStreamBufferDelete(handleToDelete);
}
};
std::unique_ptr<std::remove_pointer_t<StreamBufferHandle_t>, StreamBufferHandleDeleter> handle;
public:
/**
* Stream buffer implementation assumes there is only one task or
* interrupt that will write to the buffer (the writer), and only one task or
* interrupt that will read from the buffer (the reader).
*
* @param[in] size The total number of bytes the stream buffer will be able to hold at any one time.
* @param[in] triggerLevel The number of bytes that must be in the stream buffer
* before a task that is blocked on the stream buffer to wait for data is moved out of the blocked state.
* @return The stream buffer instance.
*/
StreamBuffer(size_t size, size_t triggerLevel);
~StreamBuffer() = default;
/**
* @brief Set trigger level for stream buffer.
* A stream buffer's trigger level is the number of bytes that must be in the
* stream buffer before a task that is blocked on the stream buffer to
* wait for data is moved out of the blocked state.
*
* @param[in] triggerLevel The new trigger level for the stream buffer.
* @return true if trigger level can be be updated (new trigger level was less than or equal to the stream buffer's length).
* @return false if trigger level can't be be updated (new trigger level was greater than the stream buffer's length).
*/
bool setTriggerLevel(size_t triggerLevel) const;
/**
* @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer.
* Wakes up task waiting for data to become available if called from ISR.
*
* @param[in] data A pointer to the data that is to be copied into the stream buffer.
* @param[in] length The maximum number of bytes to copy from data into the stream buffer.
* @param[in] timeout The maximum amount of time the task should remain in the
* Blocked state to wait for space to become available if the stream buffer is full.
* Will return immediately if timeout is zero.
* Setting timeout to portMAX_DELAY will cause the task to wait indefinitely.
* Ignored if called from ISR.
* @return The number of bytes actually written to the stream buffer.
*/
size_t send(
const void* data,
size_t length,
uint32_t timeout
) const;
/**
* @brief Receives bytes from a stream buffer.
* Wakes up task waiting for space to become available if called from ISR.
*
* @param[in] data A pointer to the buffer into which the received bytes will be
* copied.
* @param[in] length The length of the buffer pointed to by the data parameter.
* @param[in] timeout The maximum amount of time the task should remain in the
* Blocked state to wait for data to become available if the stream buffer is empty.
* Will return immediately if timeout is zero.
* Setting timeout to portMAX_DELAY will cause the task to wait indefinitely.
* Ignored if called from ISR.
* @return The number of bytes read from the stream buffer, if any.
*/
size_t receive(
void* data,
size_t length,
uint32_t timeout
) const;
/**
* @brief Queries a stream buffer to see how much data it contains, which is equal to
* the number of bytes that can be read from the stream buffer before the stream
* buffer would be empty.
*
* @return The number of bytes that can be read from the stream buffer before
* the stream buffer would be empty.
*/
size_t getAvailableReadBytes() const;
/**
* @brief Queries a stream buffer to see how much free space it contains, which is
* equal to the amount of data that can be sent to the stream buffer before it
* is full.
*
* @return The number of bytes that can be written to the stream buffer before
* the stream buffer would be full.
*/
size_t getAvailableWriteBytes() const;
/**
* @brief Queries a stream buffer to see if it is full.
*
* @return true if the stream buffer is full.
* @return false if the stream buffer is not full.
*/
bool isFull() const;
/**
* @brief Queries a stream buffer to see if it is empty.
*
* @param stream_buffer The stream buffer instance.
* @return true if the stream buffer is empty.
* @return false if the stream buffer is not empty.
*/
bool isEmpty() const;
/**
* @brief Resets a stream buffer to its initial, empty, state. Any data that was
* in the stream buffer is discarded. A stream buffer can only be reset if there
* are no tasks blocked waiting to either send to or receive from the stream buffer.
*
* @return TtStatusOk if the stream buffer is reset.
* @return TtStatusError if there was a task blocked waiting to send to or read
* from the stream buffer then the stream buffer is not reset.
*/
bool reset() const;
};
} // namespace
@@ -2,11 +2,9 @@
#include <cstdio>
#include "Tactility/Thread.h"
#include "Check.h"
#include "CoreDefines.h"
#include "EventFlag.h"
#include "kernel/Kernel.h"
#include "kernel/critical/Critical.h"
#include "Log.h"
#include "Mutex.h"
#include "Thread.h"
#include <Tactility/kernel/Kernel.h>
@@ -1,3 +0,0 @@
#pragma once
#define TT_CONFIG_THREAD_MAX_PRIORITIES 10
-195
View File
@@ -1,195 +0,0 @@
#pragma once
#include "RtosCompatTask.h"
#include <functional>
#include <memory>
#include <string>
namespace tt {
typedef TaskHandle_t ThreadId;
class Thread final {
public:
enum class State{
Stopped,
Starting,
Running,
};
/** ThreadPriority */
enum class Priority : UBaseType_t {
None = 0U, /**< Uninitialized, choose system default */
Idle = 1U,
Lower = 2U,
Low = 3U,
Normal = 4U,
High = 5U,
Higher = 6U,
Critical = 7U
};
/** ThreadCallback Your callback to run in new thread
* @warning never use osThreadExit in Thread
*/
typedef int32_t (*Callback)(void* context);
typedef std::function<int32_t()> MainFunction;
/** Write to stdout callback
* @param[in] data pointer to data
* @param[in] size data size @warning your handler must consume everything
*/
typedef void (*StdoutWriteCallback)(const char* data, size_t size);
/** Thread state change callback called upon thread state change
* @param[in] state new thread state
* @param[in] context callback context
*/
typedef void (*StateCallback)(State state, void* context);
private:
static void mainBody(void* context);
TaskHandle_t taskHandle = nullptr;
State state = State::Stopped;
MainFunction mainFunction;
int32_t callbackResult = 0;
StateCallback stateCallback = nullptr;
void* stateCallbackContext = nullptr;
std::string name = {};
Priority priority = Priority::Normal;
configSTACK_DEPTH_TYPE stackSize = 0;
portBASE_TYPE affinity = -1;
void setState(Thread::State state);
public:
Thread() = default;
/** Allocate Thread, shortcut version
* @param[in] name the name of the thread
* @param[in] stackSize in bytes
* @param[in] function
* @param[in] affinity Which CPU core to pin this task to, -1 means unpinned (only works on ESP32)
*/
Thread(
std::string name,
configSTACK_DEPTH_TYPE stackSize,
MainFunction function,
portBASE_TYPE affinity = -1
);
~Thread();
/** Set Thread name
* @param[in] name string
*/
void setName(std::string name);
/** Set Thread stack size
* @param[in] stackSize stack size in bytes
*/
void setStackSize(size_t stackSize);
/** Set CPU core pinning for this thread.
* @param[in] affinity -1 means not pinned, otherwise it's the core id (e.g. 0 or 1 on ESP32)
*/
void setAffinity(portBASE_TYPE affinity);
/** Set Thread callback
* @param[in] callback ThreadCallback, called upon thread run
* @param[in] callbackContext what to pass to the callback
*/
[[deprecated("use setMainFunction()")]]
void setCallback(Callback callback, _Nullable void* callbackContext = nullptr);
/** Set Thread callback
* @param[in] function called upon thread run
*/
void setMainFunction(MainFunction function);
/** Set Thread priority
* @param[in] priority ThreadPriority value
*/
void setPriority(Priority priority);
/** Set Thread state change callback
* @param[in] callback state change callback
* @param[in] callbackContext pointer to context
*/
void setStateCallback(StateCallback callback, _Nullable void* callbackContext = nullptr);
/** Get Thread state
* @return thread state from ThreadState
*/
State getState() const;
/** Start Thread */
void start();
/** Join Thread
* @warning make sure you manually interrupt any logic in your thread (e.g. by an EventFlag or boolean+Mutex)
* @param[in] timeout the maximum amount of time to wait
* @param[in] pollInterval the amount of ticks to wait before we check again if the thread is finished
* @return success result
*/
bool join(TickType_t timeout = portMAX_DELAY, TickType_t pollInterval = 10);
/** Get FreeRTOS ThreadId for Thread instance
* @return ThreadId or nullptr
*/
ThreadId getId() const;
/**
* @warning crashes when state is not "stopped"
* @return thread return code
*/
int32_t getReturnCode() const;
/** Suspend thread
* @param[in] threadId thread id
*/
static void suspend(ThreadId threadId);
/** Resume thread
* @param[in] threadId thread id
*/
static void resume(ThreadId threadId);
/** Get thread suspended state
* @param[in] threadId thread id
* @return true if thread is suspended
*/
static bool isSuspended(ThreadId threadId);
/**
* @brief Get thread stack watermark
* @param[in] threadId
* @return uint32_t
*/
static uint32_t getStackSpace(ThreadId threadId);
/** @return pointer to Thread instance or nullptr if this thread doesn't belong to Tactility */
static Thread* getCurrent();
static uint32_t setFlags(ThreadId threadId, uint32_t flags);
static uint32_t clearFlags(uint32_t flags);
static uint32_t getFlags();
static uint32_t awaitFlags(uint32_t flags, uint32_t options, uint32_t timeout);
};
constexpr auto THREAD_PRIORITY_SERVICE = Thread::Priority::High;
constexpr auto THREAD_PRIORITY_RENDER = Thread::Priority::Higher;
constexpr auto THREAD_PRIORITY_ISR = Thread::Priority::Critical;
} // namespace
-98
View File
@@ -1,98 +0,0 @@
#pragma once
#include "RtosCompatTimers.h"
#include "Thread.h"
#include <memory>
#include <functional>
namespace tt {
class Timer {
public:
typedef std::function<void()> Callback;
typedef void (*PendingCallback)(void* context, uint32_t arg);
private:
struct TimerHandleDeleter {
void operator()(TimerHandle_t handleToDelete) const {
xTimerDelete(handleToDelete, portMAX_DELAY);
}
};
Callback callback;
std::unique_ptr<std::remove_pointer_t<TimerHandle_t>, TimerHandleDeleter> handle;
static void onCallback(TimerHandle_t hTimer);
public:
enum class Type {
Once = 0, ///< One-shot timer.
Periodic = 1 ///< Repeating timer.
};
enum class Priority{
Normal, /**< Lower then other threads */
Elevated, /**< Same as other threads */
};
/**
* @param[in] type The timer type
* @param[in] callback The callback function
*/
Timer(Type type, Callback callback);
~Timer();
/** Start timer
* @warning This is asynchronous call, real operation will happen as soon as timer service process this request.
* @param[in] interval The interval in ticks
* @return success result
*/
bool start(TickType_t interval);
/** Restart timer with previous timeout value
* @warning This is asynchronous call, real operation will happen as soon as timer service process this request.
* @param[in] interval The interval in ticks
* @return success result
*/
bool restart(TickType_t interval);
/** Stop timer
* @warning This is asynchronous call, real operation will happen as soon as timer service process this request.
* @return success result
*/
bool stop();
/** Is timer running
* @warning This cal may and will return obsolete timer state if timer commands are still in the queue. Please read FreeRTOS timer documentation first.
* @return true when running
*/
bool isRunning();
/** Get timer expire time
* @return expire tick
*/
TickType_t getExpireTime();
/**
* Calls xTimerPendFunctionCall internally.
* @param[in] callback the function to call
* @param[in] callbackContext the first function argument
* @param[in] callbackArg the second function argument
* @param[in] timeout the function timeout (must set to 0 in ISR mode)
* @return true on success
*/
bool setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeout);
/** Set Timer thread priority
* @param[in] priority The priority
*/
void setThreadPriority(Thread::Priority priority);
};
} // namespace
+2 -1
View File
@@ -4,7 +4,8 @@
*/
#pragma once
#include "Tactility/TactilityCore.h"
#include <Tactility/TactilityCore.h>
#include <Tactility/Lock.h>
#include <cstdio>
#include <dirent.h>
@@ -1,117 +0,0 @@
#pragma once
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include <esp_timer.h>
#else
#include "FreeRTOS.h"
#include <sys/time.h>
#endif
namespace tt::kernel {
/** Recognized platform types */
typedef enum {
PlatformEsp,
PlatformSimulator
} Platform;
/** Return true when called from an Interrupt Service Routine (~IRQ mode) */
#ifdef ESP_PLATFORM
constexpr bool isIsr() { return (xPortInIsrContext() == pdTRUE); }
#else
constexpr bool isIsr() { return false; }
#endif
/** Check if kernel is running
* @return true if the FreeRTOS kernel is running, false otherwise
*/
bool isRunning();
/** Lock kernel, pause process scheduling
* @warning don't call from ISR context
* @return true on success
*/
bool lock();
/** Unlock kernel, resume process scheduling
* @warning don't call from ISR context
* @return true on success
*/
bool unlock();
/** Restore kernel lock state
* @warning don't call from ISR context
* @param[in] lock The lock state
* @return true on success
*/
bool restoreLock(bool lock);
/** Get kernel systick frequency
* @return systick counts per second
*/
uint32_t getTickFrequency();
TickType_t getTicks();
constexpr size_t getMillis() { return getTicks() / portTICK_PERIOD_MS; }
constexpr long int getMicros() {
#ifdef ESP_PLATFORM
return static_cast<unsigned long>(esp_timer_get_time());
#else
timeval tv;
gettimeofday(&tv, nullptr);
return 1000000 * tv.tv_sec + tv.tv_usec;
#endif
}
/** Delay execution
* @warning don't call from ISR context
* Also keep in mind delay is aliased to scheduler timer intervals.
* @param[in] ticks The ticks count to pause
*/
void delayTicks(TickType_t ticks);
/** Delay until tick
* @warning don't call from ISR context
* @param[in] ticks The tick until which kerel should delay task execution
* @return true on success
*/
bool delayUntilTick(TickType_t tick);
constexpr TickType_t secondsToTicks(uint32_t seconds) {
return static_cast<TickType_t>(seconds) * 1000U / portTICK_PERIOD_MS;
}
constexpr TickType_t minutesToTicks(uint32_t minutes) {
return secondsToTicks(minutes * 60U);
}
/** Convert milliseconds to ticks
*
* @param[in] milliSeconds time in milliseconds
* @return time in ticks
*/
TickType_t millisToTicks(uint32_t milliSeconds);
/** Delay in milliseconds
* This method uses kernel ticks on the inside, which causes delay to be aliased to scheduler timer intervals.
* Real wait time will be between X+ milliseconds.
* Special value: 0, will cause task yield.
* Also if used when kernel is not running will fall back to delayMicros()
* @warning don't call from ISR context
* @param[in] milliSeconds milliseconds to wait
*/
void delayMillis(uint32_t milliSeconds);
/** Delay in microseconds
* Implemented using Cortex DWT counter. Blocking and non aliased.
* @param[in] microSeconds microseconds to wait
*/
void delayMicros(uint32_t microSeconds);
/** @return the platform that Tactility currently is running on. */
Platform getPlatform();
} // namespace
@@ -0,0 +1,14 @@
#pragma once
namespace tt::kernel {
/** Recognized platform types */
typedef enum {
PlatformEsp,
PlatformSimulator
} Platform;
/** @return the platform that Tactility currently is running on. */
Platform getPlatform();
} // namespace
+4 -4
View File
@@ -1,9 +1,9 @@
#include "Tactility/Check.h"
#include <Tactility/Check.h>
#include "Tactility/Log.h"
#include "Tactility/RtosCompatTask.h"
#include <Tactility/Log.h>
#include <Tactility/freertoscompat/Task.h>
#define TAG "kernel"
constexpr auto TAG = "kernel";
static void logMemoryInfo() {
#ifdef ESP_PLATFORM
-70
View File
@@ -1,70 +0,0 @@
#include "Tactility/Dispatcher.h"
#include "Tactility/Check.h"
#include "Tactility/kernel/Kernel.h"
namespace tt {
#define TAG "dispatcher"
#define BACKPRESSURE_WARNING_COUNT ((EventBits_t)100)
#define WAIT_FLAG ((EventBits_t)1U)
Dispatcher::~Dispatcher() {
// Wait for Mutex usage
mutex.lock();
mutex.unlock();
}
bool Dispatcher::dispatch(Function function, TickType_t timeout) {
// Mutate
if (mutex.lock(timeout)) {
queue.push(std::move(function));
if (queue.size() == BACKPRESSURE_WARNING_COUNT) {
TT_LOG_W(TAG, "Backpressure: You're not consuming fast enough (100 queued)");
}
tt_check(mutex.unlock());
// Signal
eventFlag.set(WAIT_FLAG);
return true;
} else {
TT_LOG_E(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
return false;
}
}
uint32_t Dispatcher::consume(TickType_t timeout) {
// Wait for signal
uint32_t result = eventFlag.wait(WAIT_FLAG, EventFlag::WaitAny, timeout);
if (result & EventFlag::Error) {
return 0;
}
eventFlag.clear(WAIT_FLAG);
// Mutate
bool processing = true;
uint32_t consumed = 0;
do {
if (mutex.lock(10)) {
if (!queue.empty()) {
auto function = queue.front();
queue.pop();
consumed++;
processing = !queue.empty();
// Don't keep lock as callback might be slow
mutex.unlock();
function();
} else {
processing = false;
mutex.unlock();
}
} else {
TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
}
} while (processing);
return consumed;
}
} // namespace
-47
View File
@@ -1,47 +0,0 @@
#include "Tactility/DispatcherThread.h"
namespace tt {
DispatcherThread::DispatcherThread(const std::string& threadName, size_t threadStackSize) {
thread = std::make_unique<Thread>(
threadName,
threadStackSize,
[this] {
return threadMain();
}
);
}
DispatcherThread::~DispatcherThread() {
if (thread->getState() != Thread::State::Stopped) {
stop();
}
}
int32_t DispatcherThread::threadMain() {
do {
/**
* If this value is too high (e.g. 1 second) then the dispatcher destroys too slowly when the simulator exits.
* This causes the problems with other services doing an update (e.g. Statusbar) and calling into destroyed mutex in the global scope.
*/
dispatcher.consume(100 / portTICK_PERIOD_MS);
} while (!interruptThread);
return 0;
}
bool DispatcherThread::dispatch(Dispatcher::Function function, TickType_t timeout) {
return dispatcher.dispatch(function, timeout);
}
void DispatcherThread::start() {
interruptThread = false;
thread->start();
}
void DispatcherThread::stop() {
interruptThread = true;
thread->join();
}
}
-135
View File
@@ -1,135 +0,0 @@
#include "Tactility/EventFlag.h"
#include "Tactility/Check.h"
#include "Tactility/kernel/Kernel.h"
#define TT_EVENT_FLAG_MAX_BITS_EVENT_GROUPS 24U
#define TT_EVENT_FLAG_INVALID_BITS (~((1UL << TT_EVENT_FLAG_MAX_BITS_EVENT_GROUPS) - 1U))
namespace tt {
EventFlag::EventFlag() :
handle(xEventGroupCreate())
{
assert(!kernel::isIsr());
tt_check(handle);
}
EventFlag::~EventFlag() {
assert(!kernel::isIsr());
}
uint32_t EventFlag::set(uint32_t flags) const {
assert(handle);
assert((flags & TT_EVENT_FLAG_INVALID_BITS) == 0U);
uint32_t rflags;
BaseType_t yield;
if (kernel::isIsr()) {
yield = pdFALSE;
if (xEventGroupSetBitsFromISR(handle.get(), (EventBits_t)flags, &yield) == pdFAIL) {
rflags = (uint32_t)ErrorResource;
} else {
rflags = flags;
portYIELD_FROM_ISR(yield);
}
} else {
rflags = xEventGroupSetBits(handle.get(), (EventBits_t)flags);
}
/* Return event flags after setting */
return rflags;
}
uint32_t EventFlag::clear(uint32_t flags) const {
assert((flags & TT_EVENT_FLAG_INVALID_BITS) == 0U);
uint32_t rflags;
if (kernel::isIsr()) {
rflags = xEventGroupGetBitsFromISR(handle.get());
if (xEventGroupClearBitsFromISR(handle.get(), (EventBits_t)flags) == pdFAIL) {
rflags = (uint32_t)ErrorResource;
} else {
/* xEventGroupClearBitsFromISR only registers clear operation in the timer command queue. */
/* Yield is required here otherwise clear operation might not execute in the right order. */
/* See https://github.com/FreeRTOS/FreeRTOS-Kernel/issues/93 for more info. */
portYIELD_FROM_ISR(pdTRUE);
}
} else {
rflags = xEventGroupClearBits(handle.get(), (EventBits_t)flags);
}
/* Return event flags before clearing */
return rflags;
}
uint32_t EventFlag::get() const {
uint32_t rflags;
if (kernel::isIsr()) {
rflags = xEventGroupGetBitsFromISR(handle.get());
} else {
rflags = xEventGroupGetBits(handle.get());
}
/* Return current event flags */
return (rflags);
}
uint32_t EventFlag::wait(
uint32_t flags,
uint32_t options,
uint32_t timeoutTicksw
) const {
assert(!kernel::isIsr());
assert((flags & TT_EVENT_FLAG_INVALID_BITS) == 0U);
BaseType_t wait_all;
BaseType_t exit_clear;
uint32_t rflags;
if (options & WaitAll) {
wait_all = pdTRUE;
} else {
wait_all = pdFALSE;
}
if (options & NoClear) {
exit_clear = pdFALSE;
} else {
exit_clear = pdTRUE;
}
rflags = xEventGroupWaitBits(
handle.get(),
(EventBits_t)flags,
exit_clear,
wait_all,
(TickType_t)timeoutTicksw
);
if (options & WaitAll) {
if ((flags & rflags) != flags) {
if (timeoutTicksw > 0U) {
rflags = (uint32_t)ErrorTimeout;
} else {
rflags = (uint32_t)ErrorResource;
}
}
} else {
if ((flags & rflags) == 0U) {
if (timeoutTicksw > 0U) {
rflags = (uint32_t)ErrorTimeout;
} else {
rflags = (uint32_t)ErrorResource;
}
}
}
return rflags;
}
} // namespace
-9
View File
@@ -1,9 +0,0 @@
#include "Tactility/Lock.h"
namespace tt {
ScopedLock Lock::asScopedLock() const {
return ScopedLock(*this);
}
}
-136
View File
@@ -1,136 +0,0 @@
#include "Tactility/MessageQueue.h"
#include "Tactility/Check.h"
#include "Tactility/kernel/Kernel.h"
namespace tt {
static inline QueueHandle_t createQueue(uint32_t capacity, uint32_t messageSize) {
assert(!kernel::isIsr() && (capacity > 0U) && (messageSize > 0U));
return xQueueCreate(capacity, messageSize);
}
MessageQueue::MessageQueue(uint32_t capacity, uint32_t messageSize) : handle(createQueue(capacity, messageSize)) {
tt_check(handle != nullptr);
}
MessageQueue::~MessageQueue() {
assert(!kernel::isIsr());
}
bool MessageQueue::put(const void* message, TickType_t timeout) {
bool result = true;
BaseType_t yield;
if (kernel::isIsr()) {
if ((handle == nullptr) || (message == nullptr) || (timeout != 0U)) {
result = false;
} else {
yield = pdFALSE;
if (xQueueSendToBackFromISR(handle.get(), message, &yield) != pdTRUE) {
result = false;
} else {
portYIELD_FROM_ISR(yield);
}
}
} else if ((handle == nullptr) || (message == nullptr)) {
result = false;
} else if (xQueueSendToBack(handle.get(), message, (TickType_t)timeout) != pdPASS) {
result = false;
}
return result;
}
bool MessageQueue::get(void* msg_ptr, TickType_t timeout) {
bool result = true;
BaseType_t yield;
if (kernel::isIsr()) {
if ((handle == nullptr) || (msg_ptr == nullptr) || (timeout != 0U)) {
result = false;
} else {
yield = pdFALSE;
if (xQueueReceiveFromISR(handle.get(), msg_ptr, &yield) != pdPASS) {
result = false;
} else {
portYIELD_FROM_ISR(yield);
}
}
} else {
if ((handle == nullptr) || (msg_ptr == nullptr)) {
result = false;
} else if (xQueueReceive(handle.get(), msg_ptr, (TickType_t)timeout) != pdPASS) {
result = false;
}
}
return result;
}
uint32_t MessageQueue::getCapacity() const {
auto* mq = (StaticQueue_t*)(handle.get());
if (mq == nullptr) {
return 0U;
} else {
return mq->uxDummy4[1];
}
}
uint32_t MessageQueue::getMessageSize() const {
auto* mq = (StaticQueue_t*)(handle.get());
if (mq == nullptr) {
return 0U;
} else {
return mq->uxDummy4[2];
}
}
uint32_t MessageQueue::getCount() const {
UBaseType_t count;
if (handle == nullptr) {
count = 0U;
} else if (kernel::isIsr()) {
count = uxQueueMessagesWaitingFromISR(handle.get());
} else {
count = uxQueueMessagesWaiting(handle.get());
}
/* Return number of queued messages */
return (uint32_t)count;
}
uint32_t MessageQueue::getSpace() const {
auto* mq = (StaticQueue_t*)(handle.get());
uint32_t space;
uint32_t isrm;
if (mq == nullptr) {
space = 0U;
} else if (kernel::isIsr()) {
isrm = taskENTER_CRITICAL_FROM_ISR();
/* space = pxQueue->uxLength - pxQueue->uxMessagesWaiting; */
space = mq->uxDummy4[1] - mq->uxDummy4[0];
taskEXIT_CRITICAL_FROM_ISR(isrm);
} else {
space = (uint32_t)uxQueueSpacesAvailable((QueueHandle_t)mq);
}
return space;
}
bool MessageQueue::reset() {
tt_check(!kernel::isIsr());
if (handle == nullptr) {
return false;
} else {
xQueueReset(handle.get());
return true;
}
}
} // namespace
-79
View File
@@ -1,79 +0,0 @@
#include "Tactility/Semaphore.h"
#include "Tactility/Check.h"
#include "Tactility/CoreDefines.h"
namespace tt {
static inline QueueHandle_t createHandle(uint32_t maxCount, uint32_t initialCount) {
assert((maxCount > 0U) && (initialCount <= maxCount));
if (maxCount == 1U) {
auto handle = xSemaphoreCreateBinary();
if ((handle != nullptr) && (initialCount != 0U)) {
if (xSemaphoreGive(handle) != pdPASS) {
vSemaphoreDelete(handle);
handle = nullptr;
}
}
return handle;
} else {
return xSemaphoreCreateCounting(maxCount, initialCount);
}
}
Semaphore::Semaphore(uint32_t maxAvailable, uint32_t initialAvailable) : handle(createHandle(maxAvailable, initialAvailable)) {
assert(!kernel::isIsr());
tt_check(handle != nullptr);
}
Semaphore::~Semaphore() {
assert(!kernel::isIsr());
}
bool Semaphore::acquire(TickType_t timeout) const {
if (kernel::isIsr()) {
if (timeout != 0U) {
return false;
} else {
BaseType_t yield = pdFALSE;
if (xSemaphoreTakeFromISR(handle.get(), &yield) != pdPASS) {
return false;
} else {
portYIELD_FROM_ISR(yield);
return true;
}
}
} else {
return xSemaphoreTake(handle.get(), timeout) == pdPASS;
}
}
bool Semaphore::release() const {
if (kernel::isIsr()) {
BaseType_t yield = pdFALSE;
if (xSemaphoreGiveFromISR(handle.get(), &yield) != pdTRUE) {
return false;
} else {
portYIELD_FROM_ISR(yield);
return true;
}
} else {
return xSemaphoreGive(handle.get()) == pdPASS;
}
}
uint32_t Semaphore::getAvailable() const {
if (kernel::isIsr()) {
// TODO: uxSemaphoreGetCountFromISR is not supported on esp-idf 5.1.2 - perhaps later on?
#ifdef uxSemaphoreGetCountFromISR
return uxSemaphoreGetCountFromISR(handle.get());
#else
return uxQueueMessagesWaitingFromISR(handle.get());
#endif
} else {
return uxSemaphoreGetCount(handle.get());
}
}
} // namespace
-71
View File
@@ -1,71 +0,0 @@
#include "Tactility/StreamBuffer.h"
#include "Tactility/Check.h"
#include "Tactility/kernel/Kernel.h"
namespace tt {
static StreamBufferHandle_t createStreamBuffer(size_t size, size_t triggerLevel) {
assert(size != 0);
return xStreamBufferCreate(size, triggerLevel);
}
StreamBuffer::StreamBuffer(size_t size, size_t triggerLevel) : handle(createStreamBuffer(size, triggerLevel)) {
tt_check(handle);
};
bool StreamBuffer::setTriggerLevel(size_t triggerLevel) const {
return xStreamBufferSetTriggerLevel(handle.get(), triggerLevel) == pdTRUE;
};
size_t StreamBuffer::send(
const void* data,
size_t length,
uint32_t timeout
) const {
if (kernel::isIsr()) {
BaseType_t yield;
size_t result = xStreamBufferSendFromISR(handle.get(), data, length, &yield);
portYIELD_FROM_ISR(yield);
return result;
} else {
return xStreamBufferSend(handle.get(), data, length, timeout);
}
};
size_t StreamBuffer::receive(
void* data,
size_t length,
uint32_t timeout
) const {
if (kernel::isIsr()) {
BaseType_t yield;
size_t result = xStreamBufferReceiveFromISR(handle.get(), data, length, &yield);
portYIELD_FROM_ISR(yield);
return result;
} else {
return xStreamBufferReceive(handle.get(), data, length, timeout);
}
}
size_t StreamBuffer::getAvailableReadBytes() const {
return xStreamBufferBytesAvailable(handle.get());
};
size_t StreamBuffer::getAvailableWriteBytes() const {
return xStreamBufferSpacesAvailable(handle.get());
};
bool StreamBuffer::isFull() const {
return xStreamBufferIsFull(handle.get()) == pdTRUE;
};
bool StreamBuffer::isEmpty() const {
return xStreamBufferIsEmpty(handle.get()) == pdTRUE;
};
bool StreamBuffer::reset() const {
return xStreamBufferReset(handle.get()) == pdPASS;
}
} // namespace
-386
View File
@@ -1,386 +0,0 @@
#include "Tactility/Thread.h"
#include "Tactility/Check.h"
#include "Tactility/CoreDefines.h"
#include "Tactility/EventFlag.h"
#include "Tactility/kernel/Kernel.h"
#include "Tactility/Log.h"
#include "Tactility/TactilityCoreConfig.h"
#include <string>
namespace tt {
#define TAG "Thread"
#define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers
// Limits
#define MAX_BITS_TASK_NOTIFY 31U
#define MAX_BITS_EVENT_GROUPS 24U
#define THREAD_FLAGS_INVALID_BITS (~((1UL << MAX_BITS_TASK_NOTIFY) - 1U))
#define EVENT_FLAGS_INVALID_BITS (~((1UL << MAX_BITS_EVENT_GROUPS) - 1U))
static_assert(static_cast<UBaseType_t>(Thread::Priority::Critical) <= TT_CONFIG_THREAD_MAX_PRIORITIES, "highest thread priority is higher than max priority");
static_assert(TT_CONFIG_THREAD_MAX_PRIORITIES <= configMAX_PRIORITIES, "highest tactility priority is higher than max FreeRTOS priority");
void Thread::setState(Thread::State newState) {
state = newState;
if (stateCallback) {
stateCallback(state, stateCallbackContext);
}
}
static_assert(configSUPPORT_DYNAMIC_ALLOCATION == 1);
/** Catch threads that are trying to exit wrong way */
__attribute__((__noreturn__)) void threadCatch() { //-V1082
// If you're here it means you're probably doing something wrong with critical sections or with scheduler state
asm volatile("nop");
tt_crash();
__builtin_unreachable();
}
void Thread::mainBody(void* context) {
assert(context != nullptr);
auto* thread = static_cast<Thread*>(context);
// Store thread data instance to thread local storage
assert(pvTaskGetThreadLocalStoragePointer(nullptr, 0) == nullptr);
vTaskSetThreadLocalStoragePointer(nullptr, 0, thread);
TT_LOG_I(TAG, "Starting %s", thread->name.c_str());
assert(thread->state == Thread::State::Starting);
thread->setState(Thread::State::Running);
thread->callbackResult = thread->mainFunction();
assert(thread->state == Thread::State::Running);
thread->setState(Thread::State::Stopped);
TT_LOG_I(TAG, "Stopped %s", thread->name.c_str());
vTaskSetThreadLocalStoragePointer(nullptr, 0, nullptr);
thread->taskHandle = nullptr;
vTaskDelete(nullptr);
threadCatch();
}
Thread::Thread(
std::string name,
configSTACK_DEPTH_TYPE stackSize,
MainFunction function,
portBASE_TYPE affinity
) :
mainFunction(function),
name(std::move(name)),
stackSize(stackSize),
affinity(affinity)
{}
Thread::~Thread() {
// Ensure that use join before free
assert(state == State::Stopped);
assert(taskHandle == nullptr);
}
void Thread::setName(std::string newName) {
assert(state == State::Stopped);
name = std::move(newName);
}
void Thread::setStackSize(size_t newStackSize) {
assert(state == State::Stopped);
assert(stackSize % 4 == 0);
stackSize = newStackSize;
}
void Thread::setAffinity(portBASE_TYPE newAffinity) {
assert(state == State::Stopped);
affinity = newAffinity;
}
void Thread::setCallback(Callback callback, _Nullable void* callbackContext) {
assert(state == State::Stopped);
mainFunction = [callback, callbackContext] {
return callback(callbackContext);
};
}
void Thread::setMainFunction(MainFunction function) {
assert(state == State::Stopped);
mainFunction = function;
}
void Thread::setPriority(Priority newPriority) {
assert(state == State::Stopped);
priority = newPriority;
}
void Thread::setStateCallback(StateCallback callback, _Nullable void* callbackContext) {
assert(state == State::Stopped);
stateCallback = callback;
stateCallbackContext = callbackContext;
}
Thread::State Thread::getState() const {
return state;
}
void Thread::start() {
assert(mainFunction);
assert(state == State::Stopped);
assert(stackSize > 0U && stackSize < (UINT16_MAX * sizeof(StackType_t)));
setState(State::Starting);
uint32_t stack_depth = stackSize / sizeof(StackType_t);
BaseType_t result;
if (affinity != -1) {
#ifdef ESP_PLATFORM
result = xTaskCreatePinnedToCore(
mainBody,
name.c_str(),
stack_depth,
this,
static_cast<UBaseType_t>(priority),
&taskHandle,
affinity
);
#else
TT_LOG_W(TAG, "Pinned tasks are not supported by current FreeRTOS platform - creating regular one");
result = xTaskCreate(
mainBody,
name.c_str(),
stack_depth,
this,
static_cast<UBaseType_t>(priority),
&taskHandle
);
#endif
} else {
result = xTaskCreate(
mainBody,
name.c_str(),
stack_depth,
this,
static_cast<UBaseType_t>(priority),
&taskHandle
);
}
tt_check(result == pdPASS);
tt_check(state == State::Stopped || taskHandle);
}
bool Thread::join(TickType_t timeout, TickType_t pollInterval) {
tt_check(getCurrent() != this);
// !!! IMPORTANT NOTICE !!!
//
// If your thread exited, but your app stuck here: some other thread uses
// all cpu time, which delays kernel from releasing task handle
TickType_t start_ticks = kernel::getTicks();
while (taskHandle) {
kernel::delayTicks(pollInterval);
if ((kernel::getTicks() - start_ticks) > timeout) {
return false;
}
}
return true;
}
ThreadId Thread::getId() const {
return taskHandle;
}
int32_t Thread::getReturnCode() const {
assert(state == State::Stopped);
return callbackResult;
}
Thread* Thread::getCurrent() {
return static_cast<Thread*>(pvTaskGetThreadLocalStoragePointer(nullptr, 0));
}
uint32_t Thread::setFlags(ThreadId threadId, uint32_t flags) {
auto hTask = (TaskHandle_t)threadId;
uint32_t rflags;
BaseType_t yield;
if ((hTask == nullptr) || ((flags & THREAD_FLAGS_INVALID_BITS) != 0U)) {
rflags = (uint32_t)EventFlag::ErrorParameter;
} else {
rflags = (uint32_t)EventFlag::Error;
if (kernel::isIsr()) {
yield = pdFALSE;
(void)xTaskNotifyIndexedFromISR(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits, &yield);
(void)xTaskNotifyAndQueryIndexedFromISR(
hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags, nullptr
);
portYIELD_FROM_ISR(yield);
} else {
(void)xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits);
(void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags);
}
}
/* Return flags after setting */
return (rflags);
}
uint32_t Thread::clearFlags(uint32_t flags) {
TaskHandle_t hTask;
uint32_t rflags, cflags;
if (kernel::isIsr()) {
rflags = (uint32_t)EventFlag::ErrorISR;
} else if ((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
rflags = (uint32_t)EventFlag::ErrorParameter;
} else {
hTask = xTaskGetCurrentTaskHandle();
if (xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &cflags) ==
pdPASS) {
rflags = cflags;
cflags &= ~flags;
if (xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, cflags, eSetValueWithOverwrite) !=
pdPASS) {
rflags = (uint32_t)EventFlag::Error;
}
} else {
rflags = (uint32_t)EventFlag::Error;
}
}
/* Return flags before clearing */
return (rflags);
}
uint32_t Thread::getFlags() {
TaskHandle_t hTask;
uint32_t rflags;
if (kernel::isIsr()) {
rflags = (uint32_t)EventFlag::ErrorISR;
} else {
hTask = xTaskGetCurrentTaskHandle();
if (xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags) !=
pdPASS) {
rflags = (uint32_t)EventFlag::Error;
}
}
return (rflags);
}
uint32_t Thread::awaitFlags(uint32_t flags, uint32_t options, uint32_t timeout) {
uint32_t rflags, nval;
uint32_t clear;
TickType_t t0, td, tout;
BaseType_t rval;
if (kernel::isIsr()) {
rflags = (uint32_t)EventFlag::ErrorISR;
} else if ((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
rflags = (uint32_t)EventFlag::ErrorParameter;
} else {
if ((options & EventFlag::NoClear) == EventFlag::NoClear) {
clear = 0U;
} else {
clear = flags;
}
rflags = 0U;
tout = timeout;
t0 = xTaskGetTickCount();
do {
rval = xTaskNotifyWaitIndexed(THREAD_NOTIFY_INDEX, 0, clear, &nval, tout);
if (rval == pdPASS) {
rflags &= flags;
rflags |= nval;
if ((options & EventFlag::WaitAll) == EventFlag::WaitAll) {
if ((flags & rflags) == flags) {
break;
} else {
if (timeout == 0U) {
rflags = (uint32_t)EventFlag::ErrorResource;
break;
}
}
} else {
if ((flags & rflags) != 0) {
break;
} else {
if (timeout == 0U) {
rflags = (uint32_t)EventFlag::ErrorResource;
break;
}
}
}
/* Update timeout */
td = xTaskGetTickCount() - t0;
if (td > tout) {
tout = 0;
} else {
tout -= td;
}
} else {
if (timeout == 0) {
rflags = (uint32_t)EventFlag::ErrorResource;
} else {
rflags = (uint32_t)EventFlag::ErrorTimeout;
}
}
} while (rval != pdFAIL);
}
/* Return flags before clearing */
return (rflags);
}
uint32_t Thread::getStackSpace(ThreadId threadId) {
auto hTask = (TaskHandle_t)threadId;
uint32_t sz;
if (kernel::isIsr() || (hTask == nullptr)) {
sz = 0U;
} else {
sz = (uint32_t)(uxTaskGetStackHighWaterMark(hTask) * sizeof(StackType_t));
}
return (sz);
}
void Thread::suspend(ThreadId threadId) {
auto hTask = (TaskHandle_t)threadId;
vTaskSuspend(hTask);
}
void Thread::resume(ThreadId threadId) {
auto hTask = (TaskHandle_t)threadId;
if (kernel::isIsr()) {
xTaskResumeFromISR(hTask);
} else {
vTaskResume(hTask);
}
}
bool Thread::isSuspended(ThreadId threadId) {
auto hTask = (TaskHandle_t)threadId;
return eTaskGetState(hTask) == eSuspended;
}
} // namespace
-88
View File
@@ -1,88 +0,0 @@
#include "Tactility/Timer.h"
#include "Tactility/Check.h"
#include "Tactility/RtosCompat.h"
#include "Tactility/kernel/Kernel.h"
namespace tt {
void Timer::onCallback(TimerHandle_t hTimer) {
auto* timer = static_cast<Timer*>(pvTimerGetTimerID(hTimer));
if (timer != nullptr) {
timer->callback();
}
}
static TimerHandle_t createTimer(Timer::Type type, void* timerId, TimerCallbackFunction_t callback) {
assert(timerId != nullptr);
assert(callback != nullptr);
UBaseType_t reload;
if (type == Timer::Type::Once) {
reload = pdFALSE;
} else {
reload = pdTRUE;
}
return xTimerCreate(nullptr, portMAX_DELAY, (BaseType_t)reload, timerId, callback);
}
Timer::Timer(Type type, Callback callback) :
callback(callback),
handle(createTimer(type, this, onCallback))
{
assert(!kernel::isIsr());
assert(handle != nullptr);
}
Timer::~Timer() {
assert(!kernel::isIsr());
}
bool Timer::start(TickType_t interval) {
assert(!kernel::isIsr());
assert(interval < portMAX_DELAY);
return xTimerChangePeriod(handle.get(), interval, portMAX_DELAY) == pdPASS;
}
bool Timer::restart(TickType_t interval) {
assert(!kernel::isIsr());
assert(interval < portMAX_DELAY);
return xTimerChangePeriod(handle.get(), interval, portMAX_DELAY) == pdPASS &&
xTimerReset(handle.get(), portMAX_DELAY) == pdPASS;
}
bool Timer::stop() {
assert(!kernel::isIsr());
return xTimerStop(handle.get(), portMAX_DELAY) == pdPASS;
}
bool Timer::isRunning() {
assert(!kernel::isIsr());
return xTimerIsTimerActive(handle.get()) == pdTRUE;
}
TickType_t Timer::getExpireTime() {
assert(!kernel::isIsr());
return xTimerGetExpiryTime(handle.get());
}
bool Timer::setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeout) {
if (kernel::isIsr()) {
assert(timeout == 0);
return xTimerPendFunctionCallFromISR(callback, callbackContext, callbackArg, nullptr) == pdPASS;
} else {
return xTimerPendFunctionCall(callback, callbackContext, callbackArg, timeout) == pdPASS;
}
}
void Timer::setThreadPriority(Thread::Priority priority) {
assert(!kernel::isIsr());
TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle();
assert(task_handle); // Don't call this method before timer task start
vTaskPrioritySet(task_handle, static_cast<UBaseType_t>(priority));
}
} // namespace
+1 -1
View File
@@ -15,7 +15,7 @@ constexpr auto* TAG = "file";
class NoLock final : public Lock {
bool lock(TickType_t timeout) const override { return true; }
bool unlock() const override { return true; }
void unlock() const override { /* NO-OP */ }
};
static std::shared_ptr<Lock> noLock = std::make_shared<NoLock>();
-167
View File
@@ -1,167 +0,0 @@
#include "Tactility/kernel/Kernel.h"
#include "Tactility/CoreDefines.h"
#include "Tactility/RtosCompatTask.h"
#ifdef ESP_PLATFORM
#include "rom/ets_sys.h"
#else
#include <cassert>
#include <unistd.h>
#endif
namespace tt::kernel {
bool isRunning() {
return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING;
}
bool lock() {
assert(!kernel::isIsr());
int32_t lock;
switch (xTaskGetSchedulerState()) {
// Already suspended
case taskSCHEDULER_SUSPENDED:
return true;
case taskSCHEDULER_RUNNING:
vTaskSuspendAll();
return true;
case taskSCHEDULER_NOT_STARTED:
default:
return false;
}
/* Return previous lock state */
return (lock);
}
bool unlock() {
assert(!kernel::isIsr());
switch (xTaskGetSchedulerState()) {
case taskSCHEDULER_SUSPENDED:
if (xTaskResumeAll() != pdTRUE) {
if (xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) {
return false;
}
}
return true;
case taskSCHEDULER_RUNNING:
return true;
case taskSCHEDULER_NOT_STARTED:
default:
return false;
}
}
bool restoreLock(bool lock) {
assert(!kernel::isIsr());
switch (xTaskGetSchedulerState()) {
case taskSCHEDULER_SUSPENDED:
case taskSCHEDULER_RUNNING:
if (lock) {
vTaskSuspendAll();
return true;
} else {
if (xTaskResumeAll() != pdTRUE) {
if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) {
return false;
}
}
return true;
}
case taskSCHEDULER_NOT_STARTED:
default:
return false;
}
}
uint32_t getTickFrequency() {
/* Return frequency in hertz */
return (configTICK_RATE_HZ);
}
void delayTicks(TickType_t ticks) {
assert(!kernel::isIsr());
if (ticks == 0U) {
taskYIELD();
} else {
vTaskDelay(ticks);
}
}
bool delayUntilTick(TickType_t tick) {
assert(!kernel::isIsr());
TickType_t tcnt, delay;
tcnt = xTaskGetTickCount();
/* Determine remaining number of tick to delay */
delay = (TickType_t)tick - tcnt;
/* Check if target tick has not expired */
if ((delay != 0U) && (0 == (delay >> (8 * sizeof(TickType_t) - 1)))) {
if (xTaskDelayUntil(&tcnt, delay) == pdPASS) {
return true;
}
}
return false;
}
TickType_t getTicks() {
if (kernel::isIsr() != 0U) {
return xTaskGetTickCountFromISR();
} else {
return xTaskGetTickCount();
}
}
TickType_t millisToTicks(uint32_t milliseconds) {
#if configTICK_RATE_HZ == 1000
return (TickType_t)milliseconds;
#else
return (TickType_t)((float)configTICK_RATE_HZ) / 1000.0f * (float)milliseconds;
#endif
}
void delayMillis(uint32_t milliseconds) {
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
if (milliseconds > 0 && milliseconds < portMAX_DELAY - 1) {
milliseconds += 1;
}
#if configTICK_RATE_HZ_RAW == 1000
tt_delay_tick(milliseconds);
#else
delayTicks(kernel::millisToTicks(milliseconds));
#endif
} else if (milliseconds > 0) {
kernel::delayMicros(milliseconds * 1000);
}
}
void delayMicros(uint32_t microseconds) {
#ifdef ESP_PLATFORM
ets_delay_us(microseconds);
#else
usleep(microseconds);
#endif
}
Platform getPlatform() {
#ifdef ESP_PLATFORM
return PlatformEsp;
#else
return PlatformSimulator;
#endif
}
} // namespace
+13
View File
@@ -0,0 +1,13 @@
#include "Tactility/kernel/Platform.h"
namespace tt::kernel {
Platform getPlatform() {
#ifdef ESP_PLATFORM
return PlatformEsp;
#else
return PlatformSimulator;
#endif
}
} // namespace
@@ -1,13 +1,15 @@
#include "Tactility/kernel/critical/Critical.h"
#include <Tactility/kernel/critical/Critical.h>
#include "Tactility/RtosCompatTask.h"
#include "Tactility/kernel/Kernel.h"
#include <Tactility/freertoscompat/Task.h>
#include <Tactility/kernel/Kernel.h>
#ifdef ESP_PLATFORM
static portMUX_TYPE critical_mutex;
#define TT_ENTER_CRITICAL() taskENTER_CRITICAL(&critical_mutex)
#define TT_EXIT_CRITICAL() taskEXIT_CRITICAL(&critical_mutex)
#else
#define TT_ENTER_CRITICAL() taskENTER_CRITICAL()
#define TT_EXIT_CRITICAL() taskEXIT_CRITICAL()
#endif
namespace tt::kernel::critical {
@@ -15,7 +17,7 @@ namespace tt::kernel::critical {
CriticalInfo enter() {
CriticalInfo info = {
.isrm = 0,
.fromIsr = kernel::isIsr(),
.fromIsr = (xPortInIsrContext() == pdTRUE),
.kernelRunning = (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)
};
@@ -34,7 +36,7 @@ void exit(CriticalInfo info) {
if (info.fromIsr) {
taskEXIT_CRITICAL_FROM_ISR(info.isrm);
} else if (info.kernelRunning) {
TT_ENTER_CRITICAL();
TT_EXIT_CRITICAL();
} else {
portENABLE_INTERRUPTS();
}