From b659d5b9407dbfa8872ebfe8dc55d399900acc26 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 24 Aug 2024 19:21:22 +0200 Subject: [PATCH] Added Dispatcher and fix sim (#54) - Add dispatcher mechanism (a queue for function calls) and tests - Added tests for MessageQueue - Fix FreeRTOS config for simulator - Explicit dependencies for touch-related libs, because minor version changes caused broken builds on CI. --- app-esp/idf_component.yml | 8 +- app-sim/src/FreeRTOSConfig.h | 28 +------ tactility-core/src/dispatcher.c | 48 ++++++++++++ tactility-core/src/dispatcher.h | 36 +++++++++ tactility-core/src/message_queue.h | 5 +- tactility/src/tactility.c | 2 +- tests/tactility-core/dispatcher_test.cpp | 38 ++++++++++ tests/tactility-core/message_queue_test.cpp | 81 +++++++++++++++++++++ 8 files changed, 215 insertions(+), 31 deletions(-) create mode 100644 tactility-core/src/dispatcher.c create mode 100644 tactility-core/src/dispatcher.h create mode 100644 tests/tactility-core/dispatcher_test.cpp create mode 100644 tests/tactility-core/message_queue_test.cpp diff --git a/app-esp/idf_component.yml b/app-esp/idf_component.yml index b72a73d8..7a174bbc 100644 --- a/app-esp/idf_component.yml +++ b/app-esp/idf_component.yml @@ -1,6 +1,6 @@ dependencies: - espressif/esp_lcd_ili9341: "^2.0.0" - espressif/esp_lcd_touch_cst816s: "^1.0.3" - espressif/esp_lcd_touch_gt911: "^1.0.0" - espressif/esp_lcd_touch: "1.1.1" + espressif/esp_lcd_ili9341: "2.0.0" + espressif/esp_lcd_touch_cst816s: "1.0.3" + espressif/esp_lcd_touch_gt911: "1.1.1" + espressif/esp_lcd_touch: "1.1.2" idf: '~5.2' diff --git a/app-sim/src/FreeRTOSConfig.h b/app-sim/src/FreeRTOSConfig.h index 0271baac..7d1e36ef 100644 --- a/app-sim/src/FreeRTOSConfig.h +++ b/app-sim/src/FreeRTOSConfig.h @@ -1,5 +1,7 @@ #pragma once +extern void vAssertCalled(unsigned long line, const char* const file); + #define configUSE_PREEMPTION 1 #define configUSE_PORT_OPTIMISED_TASK_SELECTION 0 #define configUSE_TICKLESS_IDLE 0 @@ -8,7 +10,6 @@ #define configMINIMAL_STACK_SIZE 128 #define configMAX_TASK_NAME_LEN 16 #define configUSE_16_BIT_TICKS 0 -#define configTICK_TYPE_WIDTH_IN_BITS TICK_TYPE_WIDTH_16_BITS #define configIDLE_SHOULD_YIELD 1 #define configUSE_TASK_NOTIFICATIONS 1 #define configTASK_NOTIFICATION_ARRAY_ENTRIES 2 // Must be the same as ESP32! @@ -27,16 +28,13 @@ #define configMESSAGE_BUFFER_LENGTH_TYPE size_t #define configHEAP_CLEAR_MEMORY_ON_FREE 1 #define configUSE_APPLICATION_TASK_TAG 0 -#define configSTATS_BUFFER_MAX_LENGTH 0xFFFF /* Memory allocation related definitions. */ #define configSUPPORT_STATIC_ALLOCATION 0 #define configSUPPORT_DYNAMIC_ALLOCATION 1 -#define configKERNEL_PROVIDED_STATIC_MEMORY 1 #define configTOTAL_HEAP_SIZE (1024 * 1024) #define configAPPLICATION_ALLOCATED_HEAP 0 #define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 0 // TODO: Compare with ESP defaults -#define configENABLE_HEAP_PROTECTOR 1 /* Hook function related definitions. */ #define configUSE_IDLE_HOOK 0 @@ -61,29 +59,11 @@ #define configTIMER_QUEUE_LENGTH 10 #define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE -/* Interrupt nesting behaviour configuration. */ -#define configKERNEL_INTERRUPT_PRIORITY 1 -#define configMAX_SYSCALL_INTERRUPT_PRIORITY 4 -#define configMAX_API_CALL_INTERRUPT_PRIORITY 10 - /* Define to trap errors during development. */ -#define configASSERT(x) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ ) +#define configASSERT(x) if( ( x ) == 0 ) vAssertCalled(__LINE__, __FILE__) /* FreeRTOS MPU specific definitions. */ #define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0 -#define configTOTAL_MPU_REGIONS 8 /* Default value. */ -#define configTEX_S_C_B_FLASH 0x07UL /* Default value. */ -#define configTEX_S_C_B_SRAM 0x07UL /* Default value. */ -#define configENFORCE_SYSTEM_CALLS_FROM_KERNEL_ONLY 1 -#define configALLOW_UNPRIVILEGED_CRITICAL_SECTIONS 1 -#define configENABLE_ERRATA_837070_WORKAROUND 1 -#define configUSE_MPU_WRAPPERS_V1 0 -#define configPROTECTED_KERNEL_OBJECT_POOL_SIZE 10 -#define configSYSTEM_CALL_STACK_SIZE 128 -#define configENABLE_ACCESS_CONTROL_LIST 1 - -/* ARMv8-M secure side port related definitions. */ -#define secureconfigMAX_SECURE_CONTEXTS 5 /* Optional functions - most linkers will remove unused functions anyway. */ /* Ensure these are closely match ESP32: you can activate more features, but not less. */ @@ -91,7 +71,6 @@ #define INCLUDE_uxTaskPriorityGet 1 #define INCLUDE_vTaskDelete 1 #define INCLUDE_vTaskSuspend 1 -#define INCLUDE_xResumeFromISR 1 #define INCLUDE_vTaskDelayUntil 1 #define INCLUDE_vTaskDelay 1 #define INCLUDE_xTaskGetSchedulerState 1 @@ -100,7 +79,6 @@ #define INCLUDE_uxTaskGetStackHighWaterMark2 1 #define INCLUDE_xTaskGetIdleTaskHandle 1 #define INCLUDE_eTaskGetState 1 -#define INCLUDE_xEventGroupSetBitFromISR 1 #define INCLUDE_xTimerPendFunctionCall 1 #define INCLUDE_xTaskAbortDelay 1 #define INCLUDE_xTaskGetHandle 1 diff --git a/tactility-core/src/dispatcher.c b/tactility-core/src/dispatcher.c new file mode 100644 index 00000000..a7d272d3 --- /dev/null +++ b/tactility-core/src/dispatcher.c @@ -0,0 +1,48 @@ +#include "dispatcher.h" + +#include "tactility_core.h" + +Dispatcher* tt_dispatcher_alloc(uint32_t message_count) { + Dispatcher* dispatcher = malloc(sizeof(Dispatcher)); + *dispatcher = (Dispatcher) { + .queue = tt_message_queue_alloc(message_count, sizeof(DispatcherMessage)), + .mutex = tt_mutex_alloc(MutexTypeNormal), + .buffer = { + .callback = NULL, + .context = NULL + } + }; + return dispatcher; +} + +void tt_dispatcher_free(Dispatcher* dispatcher) { + tt_mutex_acquire(dispatcher->mutex, TtWaitForever); + tt_message_queue_reset(dispatcher->queue); + tt_message_queue_free(dispatcher->queue); + tt_mutex_release(dispatcher->mutex); + tt_mutex_free(dispatcher->mutex); + free(dispatcher); +} + +void tt_dispatcher_dispatch(Dispatcher* dispatcher, Callback callback, void* context) { + DispatcherMessage message = { + .callback = callback, + .context = context + }; + tt_mutex_acquire(dispatcher->mutex, TtWaitForever); + tt_message_queue_put(dispatcher->queue, &message, TtWaitForever); + tt_mutex_release(dispatcher->mutex); +} + +bool tt_dispatcher_consume(Dispatcher* dispatcher, uint32_t timeout_ticks) { + tt_mutex_acquire(dispatcher->mutex, TtWaitForever); + if (tt_message_queue_get(dispatcher->queue, &(dispatcher->buffer), timeout_ticks) == TtStatusOk) { + DispatcherMessage* message = &(dispatcher->buffer); + message->callback(message->context); + tt_mutex_release(dispatcher->mutex); + return true; + } else { + tt_mutex_release(dispatcher->mutex); + return false; + } +} diff --git a/tactility-core/src/dispatcher.h b/tactility-core/src/dispatcher.h new file mode 100644 index 00000000..1c1f6318 --- /dev/null +++ b/tactility-core/src/dispatcher.h @@ -0,0 +1,36 @@ +/** +* @file message_queue.h +* +* Dispatcher is a thread-safe message queue implementation for callbacks. +*/ +#pragma once + +#include "message_queue.h" +#include "mutex.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*Callback)(void* data); + +typedef struct { + Callback callback; + void* context; +} DispatcherMessage; + +typedef struct { + MessageQueue* queue; + Mutex* mutex; + DispatcherMessage buffer; // Buffer for consuming a message +} Dispatcher; + +Dispatcher* tt_dispatcher_alloc(uint32_t message_count); +void tt_dispatcher_free(Dispatcher* dispatcher); +void tt_dispatcher_dispatch(Dispatcher* dispatcher, Callback callback, void* context); +bool tt_dispatcher_consume(Dispatcher* dispatcher, uint32_t timeout_ticks); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/tactility-core/src/message_queue.h b/tactility-core/src/message_queue.h index d66be7fb..b9145627 100644 --- a/tactility-core/src/message_queue.h +++ b/tactility-core/src/message_queue.h @@ -1,6 +1,9 @@ /** * @file message_queue.h - * MessageQueue + * + * 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 diff --git a/tactility/src/tactility.c b/tactility/src/tactility.c index 6a4f18a6..c1076f02 100644 --- a/tactility/src/tactility.c +++ b/tactility/src/tactility.c @@ -72,7 +72,7 @@ static void register_user_apps(const AppManifest* const apps[TT_CONFIG_APPS_LIMI static void register_and_start_system_services() { TT_LOG_I(TAG, "Registering and starting system services"); - int app_count = sizeof(system_services) / sizeof(ServiceManifest *); + int app_count = sizeof(system_services) / sizeof(ServiceManifest*); for (int i = 0; i < app_count; ++i) { tt_service_registry_add(system_services[i]); tt_check(tt_service_registry_start(system_services[i]->id)); diff --git a/tests/tactility-core/dispatcher_test.cpp b/tests/tactility-core/dispatcher_test.cpp new file mode 100644 index 00000000..d5f5ccd6 --- /dev/null +++ b/tests/tactility-core/dispatcher_test.cpp @@ -0,0 +1,38 @@ +#include "doctest.h" +#include "tactility_core.h" +#include "dispatcher.h" + +void increment_callback(void* context) { + auto* counter = (uint32_t*)context; + (*counter)++; +} + +TEST_CASE("dispatcher should not call callback if consume isn't called") { + Dispatcher* dispatcher = tt_dispatcher_alloc(10); + + uint32_t counter = 0; + tt_dispatcher_dispatch(dispatcher, &increment_callback, &counter); + tt_delay_tick(10); + CHECK_EQ(counter, 0); + + tt_dispatcher_free(dispatcher); +} + + +TEST_CASE("dispatcher should be able to dealloc when message is not consumed") { + Dispatcher* dispatcher = tt_dispatcher_alloc(10); + uint32_t counter = 0; + tt_dispatcher_dispatch(dispatcher, increment_callback, &counter); + tt_dispatcher_free(dispatcher); +} + +TEST_CASE("dispatcher should call callback when consume is called") { + Dispatcher* dispatcher = tt_dispatcher_alloc(10); + + uint32_t counter = 0; + tt_dispatcher_dispatch(dispatcher, increment_callback, &counter); + tt_dispatcher_consume(dispatcher, 100); + CHECK_EQ(counter, 1); + + tt_dispatcher_free(dispatcher); +} diff --git a/tests/tactility-core/message_queue_test.cpp b/tests/tactility-core/message_queue_test.cpp new file mode 100644 index 00000000..d31cdbf4 --- /dev/null +++ b/tests/tactility-core/message_queue_test.cpp @@ -0,0 +1,81 @@ +#include "doctest.h" +#include "message_queue.h" + +TEST_CASE("message queue capacity should be correct") { + MessageQueue* queue = tt_message_queue_alloc(10, 1); + + uint32_t capacity = tt_message_queue_get_capacity(queue); + CHECK_EQ(capacity, 10); + + tt_message_queue_free(queue); +} + +TEST_CASE("message queue initial count should be 0") { + MessageQueue* queue = tt_message_queue_alloc(10, 1); + + uint32_t count = tt_message_queue_get_count(queue); + CHECK_EQ(count, 0); + + tt_message_queue_free(queue); +} + +TEST_CASE("message queue size should be correct") { + MessageQueue* queue = tt_message_queue_alloc(1, 123); + + uint32_t size = tt_message_queue_get_message_size(queue); + CHECK_EQ(size, 123); + + tt_message_queue_free(queue); +} + +TEST_CASE("message queue count should increase when message is added") { + MessageQueue* queue = tt_message_queue_alloc(10, sizeof(uint32_t)); + + uint32_t message = 123; + + tt_message_queue_put(queue, &message, 100); + uint32_t count = tt_message_queue_get_count(queue); + CHECK_EQ(count, 1); + + tt_message_queue_free(queue); +} + +TEST_CASE("message queue count should be 0 when message is added and queue is reset") { + MessageQueue* queue = tt_message_queue_alloc(10, sizeof(uint32_t)); + + uint32_t message = 123; + + tt_message_queue_put(queue, &message, 100); + tt_message_queue_reset(queue); + uint32_t count = tt_message_queue_get_count(queue); + CHECK_EQ(count, 0); + + tt_message_queue_free(queue); +} + +TEST_CASE("message queue consumption should work") { + MessageQueue* queue = tt_message_queue_alloc(10, sizeof(uint32_t)); + + uint32_t out_message = 123; + tt_message_queue_put(queue, &out_message, 100); + + uint32_t in_message = 0; + tt_message_queue_get(queue, &in_message, 100); + CHECK_EQ(in_message, 123); + + tt_message_queue_free(queue); +} + +TEST_CASE("message queue count should decrease when message is consumed") { + MessageQueue* queue = tt_message_queue_alloc(10, sizeof(uint32_t)); + + uint32_t out_message = 123; + tt_message_queue_put(queue, &out_message, 100); + + uint32_t in_message = 0; + tt_message_queue_get(queue, &in_message, 100); + uint32_t count = tt_message_queue_get_count(queue); + CHECK_EQ(count, 0); + + tt_message_queue_free(queue); +}