diff --git a/.github/workflows/cmake-test-generic-stm32.yml b/.github/workflows/cmake-test-generic-stm32.yml index bf38284f..b27cbf1c 100644 --- a/.github/workflows/cmake-test-generic-stm32.yml +++ b/.github/workflows/cmake-test-generic-stm32.yml @@ -15,7 +15,7 @@ jobs: matrix: include: - name: "Configure CMake (Board: STM32F407DISC1, CPU: Arm Cortex-M4)" - cmake_flags: -DVENDOR_STM32=ON -DTARGET_CORTEX_M4=ON -DTARGET_CPU_FAMILY=STM32F407xx + cmake_flags: -DVENDOR_STM32=ON -DTARGET_CORTEX_M4=ON -DTARGET_CPU_FAMILY=STM32F407xx -DENABLE_TLS=ON cpu: cortex-m4 board: STM32F4-Discovery diff --git a/.github/workflows/cmake-test-generic.yml b/.github/workflows/cmake-test-generic.yml index 4ecf8eb4..34b672b8 100644 --- a/.github/workflows/cmake-test-generic.yml +++ b/.github/workflows/cmake-test-generic.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v6 - name: Configure CMake - run: cmake -G "Unix Makefiles" -B ${{github.workspace}}/build -DBUILD_LIB=ON -DBUILD_TESTS=ON -DTEST_GENERIC=ON + run: cmake -G "Unix Makefiles" -B ${{github.workspace}}/build -DBUILD_LIB=ON -DBUILD_TESTS=ON -DTEST_GENERIC=ON -DENABLE_TLS=ON - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel 4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c9e326f..70d6a560 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,11 +85,17 @@ if (BUILD_DOC) endif() # Declare tickles mode if enabled (see stk_config.h.in) -if (MODE_TICKLESS) +if (ENABLE_TICKLESS) message(STATUS "* tickless mode") set(STK_TICKLESS_IDLE 1) endif() +# Declare tickles mode if enabled (see stk_config.h.in) +if (ENABLE_TLS) + message(STATUS "* enable TLS") + set(STK_TLS 1) +endif() + # Create stk_config.h (note: must be defined after inclusion of the projects to catch value of _STK_DEVICE_INC) if (WIN32) set(_STK_ARCH_X86_WIN32 TRUE) diff --git a/README.md b/README.md index 796fa440..e28fdb1a 100644 --- a/README.md +++ b/README.md @@ -571,55 +571,54 @@ Compatible with: Below example toggles RGB LEDs on a development board. Each LED is controlled by its own thread, switching at 1s intervals: ```cpp -#include #include -#include "example.h" +#include -static volatile uint8_t g_TaskSwitch = 0; +using namespace bsp; + +enum { TASK_STACK_SIZE = 256 }; + +static const uint32_t FLAGS_ALL[] = { + (1U << LED_RED), + (1U << LED_ORANGE), + (1U << LED_GREEN), + (1U << LED_BLUE) +}; +static stk::sync::EventFlags g_TaskFlags(FLAGS_ALL[LED_RED]); +static stk::Ticks g_Timeline = 0; template -class MyTask : public stk::Task<256, _AccessMode> +class MyTask : public stk::Task { - uint8_t m_taskId; + uint8_t m_task_id; + uint32_t m_my_flag; + uint32_t m_next_flag; public: - MyTask(uint8_t taskId) : m_taskId(taskId) + MyTask(uint8_t task_id) : m_task_id(task_id), m_my_flag(FLAGS_ALL[task_id]), + m_next_flag(FLAGS_ALL[(task_id + 1) % LED_MAX]) {} private: - void Run() + void Run() override { - uint8_t task_id = m_taskId; + const stk::Timeout period = stk::GetTicksFromMs(1000); + g_Timeline = stk::GetTicks(); while (true) { - if (g_TaskSwitch != task_id) - { - stk::Sleep(10); + uint32_t result = g_TaskFlags.Wait(m_my_flag, stk::sync::EventFlags::OPT_WAIT_ANY); + if (stk::sync::EventFlags::IsError(result)) continue; - } - switch (task_id) { - case 0: - LED_SET_STATE(LED_RED, true); - LED_SET_STATE(LED_GREEN, false); - LED_SET_STATE(LED_BLUE, false); - break; - case 1: - LED_SET_STATE(LED_RED, false); - LED_SET_STATE(LED_GREEN, true); - LED_SET_STATE(LED_BLUE, false); - break; - case 2: - LED_SET_STATE(LED_RED, false); - LED_SET_STATE(LED_GREEN, false); - LED_SET_STATE(LED_BLUE, true); - break; + stk::hw::CriticalSection::ScopedLock __guard; + Led::SwitchOnExclusive(static_cast(m_task_id)); } - stk::Sleep(1000); - g_TaskSwitch = (task_id + 1) % 3; + stk::SleepUntil(g_Timeline += period); + + g_TaskFlags.Set(m_next_flag); } } }; @@ -628,23 +627,27 @@ void RunExample() { using namespace stk; - LED_INIT(LED_RED, false); - LED_INIT(LED_GREEN, false); - LED_INIT(LED_BLUE, false); + Led::InitAll(false); + + const uint8_t KernelMode = KERNEL_STATIC | KERNEL_SYNC | (STK_TICKLESS_IDLE ? KERNEL_TICKLESS : 0); + + static Kernel kernel; - static Kernel kernel; - static MyTask task1(0), task2(1), task3(2); + static MyTask task1(LED_RED); + static MyTask task2(LED_ORANGE); + static MyTask task3(LED_GREEN); + static MyTask task4(LED_BLUE); - kernel.Initialize(PERIODICITY_DEFAULT); + kernel.Initialize(); kernel.AddTask(&task1); kernel.AddTask(&task2); kernel.AddTask(&task3); + kernel.AddTask(&task4); kernel.Start(); - assert(false); - while (true); + STK_ASSERT(false); } ``` diff --git a/build/example/blinky/example.cpp b/build/example/blinky/example.cpp index a0b20fa1..7c18b81b 100644 --- a/build/example/blinky/example.cpp +++ b/build/example/blinky/example.cpp @@ -1,5 +1,5 @@ /* - * SuperTinyKernel™ (STK): Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems. + * SuperTinyKernel(TM) RTOS: Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems. * * Source: https://github.com/SuperTinyKernel-RTOS * @@ -9,8 +9,11 @@ #include #include +#include #include "example.h" +using namespace bsp; + // R2350 requires larger stack due to stack-memory heavy SDK API #ifdef _PICO_H enum { TASK_STACK_SIZE = 1024 }; @@ -18,103 +21,85 @@ enum { TASK_STACK_SIZE = 1024 }; enum { TASK_STACK_SIZE = 256 }; #endif -static volatile uint8_t g_TaskSwitch = 0; +// One flag bit per LED task; task 0 (RED) goes first +static const uint32_t FLAGS_ALL[] = { + (1U << LED_RED), + (1U << LED_ORANGE), + (1U << LED_GREEN), + (1U << LED_BLUE) +}; + +// Start with the RED task's flag set so it runs first +static stk::sync::EventFlags g_TaskFlags(FLAGS_ALL[LED_RED]); + +// Timeline for a precise LED switching +static stk::Ticks g_Timeline = 0; // Task's core (thread) template class MyTask : public stk::Task { - uint8_t m_task_id; - const char *m_name; + uint8_t m_task_id; + uint32_t m_my_flag; + uint32_t m_next_flag; public: - MyTask(uint8_t task_id, const char *name) : m_task_id(task_id), m_name(name) + MyTask(uint8_t task_id) : m_task_id(task_id), m_my_flag(FLAGS_ALL[task_id]), + m_next_flag(FLAGS_ALL[(task_id + 1) % LED_MAX]) {} - size_t GetId() const { return m_task_id; } - const char *GetName() const { return m_name; } - private: void Run() override { - uint8_t task_id = m_task_id; - stk::Ticks ts = 0; + // we switch LEDs with 1s period + const stk::Timeout period = stk::GetTicksFromMs(1000); + + // get a start of the timeline + g_Timeline = stk::GetTicks(); while (true) { - if (g_TaskSwitch != task_id) - { - // to avoid hot loop and excessive CPU usage sleep 10ms while waiting for the own turn, - // if scheduler does not have active threads then it will fall into a sleep mode which is - // saving the consumed power - stk::Sleep(100); + // block until this task's flag is set; auto-cleared on return + uint32_t result = g_TaskFlags.Wait(m_my_flag, stk::sync::EventFlags::OPT_WAIT_ANY); + if (stk::sync::EventFlags::IsError(result)) continue; - } + // change active LED { stk::hw::CriticalSection::ScopedLock __guard; - - SwitchOnLED(task_id); + Led::SwitchOnExclusive(static_cast(m_task_id)); } - ts = stk::hw::HiResClock::GetTimeUs(); - - // sleep 1s and delegate work to another task switching another LED - stk::Sleep(1000); + // sleep 1s drift-free and then delegate work to the next task + // we could use simple stk::Sleep() but due to other work around Sleep call we + // will get a time drift, STK allows to sleep until exact timestamp making it + // possible precise sleeping with 1 tick precision, you could also use + // time::TimerHost for timer-related tasks (see related 'timer' example) + stk::SleepUntil(g_Timeline += period); - stk::Cycles diff = stk::hw::HiResClock::GetTimeUs() - ts; - (void)diff; - - g_TaskSwitch = (task_id + 1) % 3; - } - } - - static void SwitchOnLED(uint8_t task_id) - { - switch (task_id) - { - case 0: - Led::Set(Led::RED, true); - Led::Set(Led::GREEN, false); - Led::Set(Led::BLUE, false); - break; - case 1: - Led::Set(Led::RED, false); - Led::Set(Led::GREEN, true); - Led::Set(Led::BLUE, false); - break; - case 2: - Led::Set(Led::RED, false); - Led::Set(Led::GREEN, false); - Led::Set(Led::BLUE, true); - break; + // hand off to the next task + g_TaskFlags.Set(m_next_flag); } } }; -static void InitLeds() -{ - Led::Init(Led::RED, false); - Led::Init(Led::GREEN, false); - Led::Init(Led::BLUE, false); -} - void RunExample() { using namespace stk; - InitLeds(); + Led::InitAll(false); - // operating in Static mode (tasks never exit) and if config requested also in tickless (STK_TICKLESS_IDLE=1) mode - const uint8_t KernelMode = KERNEL_STATIC | (STK_TICKLESS_IDLE ? KERNEL_TICKLESS : 0); + // operating in Static + Sync mode (EventFlags requires KERNEL_SYNC) and optionally tickless + const uint8_t KernelMode = KERNEL_STATIC | KERNEL_SYNC | (STK_TICKLESS_IDLE ? KERNEL_TICKLESS : 0); // allocate scheduling kernel for 3 threads (tasks) with Round-Robin scheduling strategy - static Kernel kernel; + static Kernel kernel; // note: using ACCESS_PRIVILEGED as Cortex-M3+ may not allow writing to GPIO from a less secure user thread - static MyTask task1(0, "LED-red"); - static MyTask task2(1, "LED-grn"); - static MyTask task3(2, "LED-blu"); + static MyTask task1(LED_RED); + static MyTask task2(LED_ORANGE); + static MyTask task3(LED_GREEN); + static MyTask task4(LED_BLUE); // init scheduling kernel kernel.Initialize(); @@ -123,6 +108,7 @@ void RunExample() kernel.AddTask(&task1); kernel.AddTask(&task2); kernel.AddTask(&task3); + kernel.AddTask(&task4); // start scheduler (it will start threads added by AddTask), execution in main() will be blocked on this line kernel.Start(); diff --git a/build/example/blinky_c/example.c b/build/example/blinky_c/example.c index 6dab9a1d..c815cee6 100644 --- a/build/example/blinky_c/example.c +++ b/build/example/blinky_c/example.c @@ -15,96 +15,89 @@ #define STACK_SIZE 256 -static volatile uint8_t g_TaskSwitch = 0; +// One flag bit per LED task; task 0 (RED) goes first +static const uint32_t FLAGS_ALL[] = { + (1U << LED_RED), + (1U << LED_ORANGE), + (1U << LED_GREEN), + (1U << LED_BLUE) +}; + +// EventFlags object and its backing memory +static stk_ef_mem_t g_TaskFlagsMem; +static stk_ef_t *g_TaskFlags; + +// Stack of the tasks static uint32_t g_Stack[STK_C_KERNEL_MAX_TASKS][STACK_SIZE] __stk_c_stack; -static void InitLeds() -{ - Led_Init(LED_RED, false); - Led_Init(LED_GREEN, false); - Led_Init(LED_BLUE, false); -} +// Per-task argument passed through the void* arg +typedef struct { + uint8_t task_id; + uint32_t my_flag; + uint32_t next_flag; +} TaskArg; -// Task function switching the LED -static void SwitchOnLED(uint8_t task_id) -{ - switch (task_id) - { - case 0: - Led_Set(LED_RED, true); - Led_Set(LED_GREEN, false); - Led_Set(LED_BLUE, false); - break; - case 1: - Led_Set(LED_RED, false); - Led_Set(LED_GREEN, true); - Led_Set(LED_BLUE, false); - break; - case 2: - Led_Set(LED_RED, false); - Led_Set(LED_GREEN, false); - Led_Set(LED_BLUE, true); - break; - } -} +static TaskArg g_TaskArgs[LED_MAX]; void TaskFunc(void *arg) { - uint8_t task_id = (uint8_t)((uintptr_t)arg); - - // just fake counters to demonstrate that scheduler is saving/restoring context correctly - // preserving values of floating-point and 64 bit variables - volatile float count = 0; - volatile uint64_t count_skip = 0; + const TaskArg *a = (const TaskArg *)arg; while (true) { - if (g_TaskSwitch != task_id) - { - // to avoid hot loop and excessive CPU usage sleep 10ms while waiting for the own turn, - // if scheduler does not have active threads then it will fall into a sleep mode which is - // saving the consumed power - stk_sleep_ms(10); - - ++count_skip; + // block until this task's flag is set; auto-cleared on return + uint32_t result = stk_ef_wait(g_TaskFlags, a->my_flag, STK_EF_OPT_WAIT_ANY, STK_WAIT_INFINITE); + if (stk_ef_is_error(result)) continue; - } - - ++count; // change LED state { stk_critical_section_enter(); - - SwitchOnLED(task_id); - + Led_SwitchOnExclusive((LedId)a->task_id); stk_critical_section_exit(); } - // sleep 1s and delegate work to another task switching another LED + // sleep 1s and delegate work to the next task stk_sleep_ms(1000); - g_TaskSwitch = (task_id + 1) % 3; + + // hand off to the next task + stk_ef_set(g_TaskFlags, a->next_flag); } } void RunExample() { - InitLeds(); + Led_InitAll(false); + + // initialize per-task argument structs + for (uint8_t i = 0; i < LED_MAX; i++) + { + g_TaskArgs[i].task_id = i; + g_TaskArgs[i].my_flag = FLAGS_ALL[i]; + g_TaskArgs[i].next_flag = FLAGS_ALL[(i + 1) % LED_MAX]; + } + + // create EventFlags with the RED task's flag pre-set so it runs first + g_TaskFlags = stk_ef_create(&g_TaskFlagsMem, sizeof(g_TaskFlagsMem), FLAGS_ALL[LED_RED]); + STK_C_ASSERT(g_TaskFlags != NULL); - // allocate scheduling kernel + // allocate scheduling kernel (KERNEL_SYNC required for EventFlags) stk_kernel_t *k = stk_kernel_create(0); + STK_C_ASSERT(k != NULL); // init kernel with default periodicity - 1ms tick stk_kernel_init(k, STK_PERIODICITY_DEFAULT); // using privileged tasks as some MCUs may not allow writing to GPIO from a user thread, such ARM Cortex-M7/M33/... - stk_task_t *t1 = stk_task_create_privileged(TaskFunc, (void *)0, g_Stack[0], STACK_SIZE); - stk_task_t *t2 = stk_task_create_privileged(TaskFunc, (void *)1, g_Stack[1], STACK_SIZE); - stk_task_t *t3 = stk_task_create_privileged(TaskFunc, (void *)2, g_Stack[2], STACK_SIZE); + stk_task_t *t1 = stk_task_create_privileged(TaskFunc, &g_TaskArgs[LED_RED], g_Stack[0], STACK_SIZE); + stk_task_t *t2 = stk_task_create_privileged(TaskFunc, &g_TaskArgs[LED_ORANGE], g_Stack[1], STACK_SIZE); + stk_task_t *t3 = stk_task_create_privileged(TaskFunc, &g_TaskArgs[LED_GREEN], g_Stack[2], STACK_SIZE); + stk_task_t *t4 = stk_task_create_privileged(TaskFunc, &g_TaskArgs[LED_BLUE], g_Stack[3], STACK_SIZE); stk_kernel_add_task(k, t1); stk_kernel_add_task(k, t2); stk_kernel_add_task(k, t3); + stk_kernel_add_task(k, t4); // start scheduler (it will start threads added by stk_kernel_add_task), execution in main() will be blocked on this line stk_kernel_start(k); diff --git a/build/example/blinky_fp/example.cpp b/build/example/blinky_fp/example.cpp index 2c9f43eb..f5030034 100644 --- a/build/example/blinky_fp/example.cpp +++ b/build/example/blinky_fp/example.cpp @@ -11,12 +11,7 @@ #include #include "example.h" -static void InitLeds() -{ - Led::Init(Led::RED, false); - Led::Init(Led::GREEN, false); - Led::Init(Led::BLUE, false); -} +using namespace bsp; // R2350 requires larger stack due to stack-memory heavy SDK API #ifdef _PICO_H @@ -71,23 +66,27 @@ void RunExample() { using namespace stk; - InitLeds(); + Led::InitAll(false); // 3 tasks kernel with 3 priorities - static Kernel kernel; + static Kernel kernel; // RED has lowest priority, and BLUE has highest: // - BLUE gets more CPU time and will blink very often - // - GRREN blinks less often than BLUE + // - GREEN blinks less often than BLUE + // - ORANGE is blinking less often than GREEN // - RED is least blinking as it gets gets the least CPU time // note: if you set the same priority for tasks LEDs of these tasks will blink equally static LedTask task_red(Led::RED); + static LedTask task_org(Led::ORANGE); static LedTask task_green(Led::GREEN); static LedTask task_blue(Led::BLUE); kernel.Initialize(); kernel.AddTask(&task_red); + kernel.AddTask(&task_org); kernel.AddTask(&task_green); kernel.AddTask(&task_blue); diff --git a/build/example/blinky_mc/example.cpp b/build/example/blinky_mc/example.cpp index e16fc9dd..369bf5ae 100644 --- a/build/example/blinky_mc/example.cpp +++ b/build/example/blinky_mc/example.cpp @@ -11,56 +11,63 @@ #include #include "example.h" -enum LedState +// Sync primitives used: +// sync::PipeT - typed single-slot FIFO per LedTask; CtrlTask writes a command, +// the matching LedTask blocks in Read() instead of spin-sleeping. +// sync::Event - CtrlTask waits on this event for the 1-second interval; a future +// caller (e.g. a button ISR) can call Set() to interrupt the delay +// early without any code change to CtrlTask. +#include +#include + +using namespace bsp; + +enum LedState : uint8_t { - LED_OFF, LED_ON, LED_NEXT + LED_OFF = 0, + LED_ON = 1 }; -static volatile LedState g_TaskSwitch = LED_NEXT; -static volatile LedState g_Task = LED_OFF; +// One single-slot pipe per LED task. CtrlTask writes the command; the +// corresponding LedTask blocks in Read() until its turn arrives. +// Capacity = 1: only one pending command is ever needed per task. +static stk::sync::PipeT g_PipeOff; // commands for LED_OFF task +static stk::sync::PipeT g_PipeOn; // commands for LED_ON task -static void InitLeds() -{ - Led::Init(Led::GREEN, false); -} +// CtrlTask uses this event to sleep for around 1 s. Keeping it as an Event (rather +// than a plain stk::Sleep) means an external caller, e.g. a button ISR, can +// call g_WakeCtrl.Set() to shorten or cancel the delay at any time. +static stk::sync::Event g_WakeCtrl; // Task's core (thread) template class LedTask : public stk::Task<2048, _AccessMode> { - LedState m_task_id; + LedState m_task_id; + stk::sync::PipeT &m_pipe; // reference to this task's command pipe public: - LedTask(LedState task_id) : m_task_id(task_id) + LedTask(LedState task_id, stk::sync::PipeT &pipe) + : m_task_id(task_id), m_pipe(pipe) {} private: - void Run() + void Run() override { - LedState task_id = m_task_id; - while (true) { - if (g_TaskSwitch != task_id) - { - // to avoid hot loop and excessive CPU usage sleep 10ms while waiting for the own turn, - // if scheduler does not have active threads then it will fall into a sleep mode which is - // saving the consumed power - stk::Sleep(10); + // block here until CtrlTask sends a command on our pipe + LedState cmd; + if (!m_pipe.Read(cmd)) continue; - } // switch LED on/off { // we do not want preemption during IO with hardware stk::hw::CriticalSection::ScopedLock __cs; - Led::Set(Led::GREEN, (task_id == LED_OFF ? false : true)); + Led::Set(Led::GREEN, (m_task_id == LED_ON)); } - - // wait for a next switch - g_Task = task_id; - g_TaskSwitch = LED_NEXT; } } }; @@ -77,41 +84,30 @@ template class CtrlTask : public stk::Task { private: - void Run() + void Run() override { - int64_t task_start = stk::GetTimeNowMs(); + LedState next = LED_ON; // first command sent after startup while (true) { - if (g_TaskSwitch != LED_NEXT) + // sleep 1s and delegate work to another task switching another LED; + // Wait(1000) returns false on timeout (normal tick) or true if woken + // early by Set() - either way proceed to the next toggle + g_WakeCtrl.Wait(1000); + g_WakeCtrl.Reset(); // re-arm for the next iteration + + // TryWrite is used because each pipe has capacity 1 and CtrlTask is + // the sole producer; the pipe is always empty here by design + if (next == LED_ON) { - // to avoid hot loop and excessive CPU usage sleep 10ms while waiting for the own turn, - // if scheduler does not have active threads then it will fall into a sleep mode which is - // saving the consumed power - stk::Sleep(10); - continue; + g_PipeOn.TryWrite(LED_ON); + next = LED_OFF; } - - // sleep 1s and delegate work to another task switching another LED, hw thread could have - // some latency, thus account for it - int32_t sleep = 1000 + (int32_t)(task_start - stk::GetTimeNowMs()); - if (sleep > 0) - stk::Sleep(sleep); - - switch (g_Task) + else { - case LED_OFF: - g_TaskSwitch = LED_ON; - break; - case LED_ON: - g_TaskSwitch = LED_OFF; - break; - default: - STK_ASSERT(false); - break; + g_PipeOff.TryWrite(LED_OFF); + next = LED_ON; } - - task_start = stk::GetTimeNowMs(); } } }; @@ -121,10 +117,11 @@ void StartCore0() using namespace stk; // allocate scheduling kernel for 1 thread (tasks) with Round-robin scheduling strategy - static Kernel kernel; + static Kernel kernel; // these are secure/trusted tasks which are allowed to access hardware safely - static LedTask secure_hw_task0(LED_OFF), secure_hw_task1(LED_ON); + static LedTask secure_hw_task0(LED_OFF, g_PipeOff); + static LedTask secure_hw_task1(LED_ON, g_PipeOn); // init scheduling kernel kernel.Initialize(); @@ -145,7 +142,8 @@ void StartCore1() using namespace stk; // allocate scheduling kernel for 1 thread (tasks) with Round-robin scheduling strategy - static Kernel kernel; + static Kernel kernel; // if MCU supports (for example Cortex-M7/M33), ACCESS_USER does not allow an access to a hardware directly, // therefore you can process in this thread/task an insecure context or data @@ -166,7 +164,7 @@ void StartCore1() void RunExample() { - InitLeds(); + Led::InitAll(false); // start on the main core (0) in the last step as it will be the last blocking call of RunExample Cpu::Start(1, StartCore1); diff --git a/build/example/blinky_swrr/example.cpp b/build/example/blinky_swrr/example.cpp index f25e2e2d..152075d5 100644 --- a/build/example/blinky_swrr/example.cpp +++ b/build/example/blinky_swrr/example.cpp @@ -11,12 +11,7 @@ #include #include "example.h" -static void InitLeds() -{ - Led::Init(Led::RED, false); - Led::Init(Led::GREEN, false); - Led::Init(Led::BLUE, false); -} +using namespace bsp; // R2350 requires larger stack due to stack-memory heavy SDK API #ifdef _PICO_H @@ -26,8 +21,8 @@ enum { TASK_STACK_SIZE = 256 }; #endif // Generic LED-blink task -template -class LedTask : public stk::TaskW<_Priority, TASK_STACK_SIZE, _AccessMode> +template +class LedTask : public stk::TaskW<_Weight, TASK_STACK_SIZE, _AccessMode> { uint8_t m_led_id; public: @@ -35,7 +30,7 @@ class LedTask : public stk::TaskW<_Priority, TASK_STACK_SIZE, _AccessMode> {} private: - void Run() + void Run() override { bool led_state = false; @@ -51,13 +46,13 @@ class LedTask : public stk::TaskW<_Priority, TASK_STACK_SIZE, _AccessMode> // protect from preemption during hardware IO stk::hw::CriticalSection::ScopedLock __cs; - Led::Set((Led::Id)m_led_id, led_state); + Led::Set(static_cast(m_led_id), led_state); } led_state = !led_state; - // SWRR, unlike Fixed-Priority (FP), does not require tasks cooperation with Sleep() or Yield(), all - // tasks will get their CPU time slice, even tasks with lowest priority (see task_red in this example). + // SWRR, unlike with fixed-priority SwitchStrategyFP32, does not require tasks cooperation with + // Sleep() or Yield(), all tasks will get their CPU time slice, even tasks with lowest priority //stk::Sleep(10); } } @@ -67,23 +62,27 @@ void RunExample() { using namespace stk; - InitLeds(); + Led::InitAll(false); // 3 tasks kernel with Smooth Weighted Round-Robin scheduling strategy - static Kernel kernel; + static Kernel kernel; - // RED is lowest priority (1), and BLUE is highest (87): + // RED is lowest priority (1), and BLUE is highest (69): // - BLUE gets more CPU time and will blink very often // - GRREN blinks less often than BLUE + // - ORANGE is blinking less often than GREEN // - RED is least blinking as it gets gets the least CPU time // note: if you set the same priority for tasks LEDs of these tasks will blink equally - static LedTask<5, ACCESS_PRIVILEGED> task_red(Led::RED); + static LedTask<1, ACCESS_PRIVILEGED> task_red(Led::RED); + static LedTask<10, ACCESS_PRIVILEGED> task_org(Led::ORANGE); static LedTask<20, ACCESS_PRIVILEGED> task_green(Led::GREEN); - static LedTask<75, ACCESS_PRIVILEGED> task_blue(Led::BLUE); + static LedTask<69, ACCESS_PRIVILEGED> task_blue(Led::BLUE); kernel.Initialize(); kernel.AddTask(&task_red); + kernel.AddTask(&task_org); kernel.AddTask(&task_green); kernel.AddTask(&task_blue); diff --git a/build/example/critical_section/example.cpp b/build/example/critical_section/example.cpp index c3a0a40f..9438af62 100644 --- a/build/example/critical_section/example.cpp +++ b/build/example/critical_section/example.cpp @@ -15,6 +15,8 @@ #include // for system Sleep() #endif +using namespace bsp; + enum { THREADS_MAX = 8, @@ -33,8 +35,8 @@ static void UnsafeIncrement() // we are making thread-race with this delay inevitable, we can't invoke stk::Sleep() or stk::Delay() // here as it will deadlock program execution because critical section temporarily frozen // our scheduler, thus use critical section with care and ONLY if REALLY necessary - volatile int32_t delay = 1000000; - while (--delay); + for (volatile uint32_t delay = 0; delay < 100000; delay++) + {} // it will overwrite the result which was most likely set by another thread // therefore counter will not be incremented ITERATIONS_MAX * THREADS_MAX times @@ -58,17 +60,12 @@ static void IncrementWithCS(void *) stk::hw::CriticalSection::ScopedLock __cs1; stk::hw::CriticalSection::ScopedLock __cs2; // STK supports nesting of critical sections + // you can also use sync::ScopedCriticalSection instead of hw::CriticalSection + UnsafeIncrement(); } } -static void InitLEDs() -{ - Led::Init(Led::RED, false); - Led::Init(Led::GREEN, false); - Led::Init(Led::BLUE, false); -} - // Task's thread object template class Thread : public stk::Task<256, _AccessMode> @@ -77,14 +74,14 @@ class Thread : public stk::Task<256, _AccessMode> void (* m_func) (void *user_data); private: - void Run() { m_func(nullptr); } + void Run() override { m_func(nullptr); } }; void RunExample() { using namespace stk; - InitLEDs(); + Led::InitAll(false); // allocate scheduling kernel for THREADS_MAX threads (tasks) with Round-robin scheduling strategy static Kernel kernel; @@ -95,6 +92,10 @@ void RunExample() // init scheduling kernel kernel.Initialize(); + // RED and GREEN leds will blink one by one, in case of RED led g_Counter variable got incorrect + // count due to race, in case of GREEN led a critical section was used which protected + // variable from the race + static bool toogle = false; while (true) { diff --git a/build/example/driver/c_wrapper.cpp b/build/example/driver/c_wrapper.cpp index cbac0a09..92a04a18 100644 --- a/build/example/driver/c_wrapper.cpp +++ b/build/example/driver/c_wrapper.cpp @@ -9,6 +9,8 @@ #include "led.h" +using namespace bsp; + // C interface extern "C" { @@ -22,4 +24,14 @@ void Led_Set(LedId led, bool state) Led::Set(led, state); } +void Led_InitAll(bool state) +{ + Led::InitAll(state); +} + +void Led_SwitchOnExclusive(LedId led) +{ + Led::SwitchOnExclusive(led); +} + } // extern "C" diff --git a/build/example/driver/led.h b/build/example/driver/led.h index a39d63ac..62d1eb3e 100644 --- a/build/example/driver/led.h +++ b/build/example/driver/led.h @@ -11,32 +11,54 @@ #define DRIVER_LED_H_ #include +#include -typedef enum LedId { - LED_RED, +typedef enum LedId +{ LED_GREEN, - LED_BLUE + LED_ORANGE, + LED_RED, + LED_BLUE, + + LED_MAX } LedId; #ifdef __cplusplus +namespace bsp { + struct Led { using Id = LedId; - static constexpr Id RED = LED_RED; - static constexpr Id GREEN = LED_GREEN; - static constexpr Id BLUE = LED_BLUE; + static constexpr Id GREEN = LED_GREEN; + static constexpr Id ORANGE = LED_ORANGE; + static constexpr Id RED = LED_RED; + static constexpr Id BLUE = LED_BLUE; static void Init(Id led, bool init_state); + static inline void InitAll(bool init_state) + { + for (uint8_t led = 0; led < LED_MAX; ++led) + Led::Init(static_cast(led), init_state); + } static void Set(Id led, bool state); + static void SwitchOnExclusive(Id led) + { + for (uint8_t i = 0; i < LED_MAX; ++i) + Led::Set(static_cast(i), (i == led)); + } }; -#else +} // namespace bsp + +#else // __cplusplus void Led_Init(LedId led, bool init_state); +void Led_InitAll(bool init_state); void Led_Set(LedId led, bool state); +void Led_SwitchOnExclusive(LedId led); #endif diff --git a/build/example/driver/portable/led.cpp b/build/example/driver/portable/led.cpp index 171bcce0..627096b8 100644 --- a/build/example/driver/portable/led.cpp +++ b/build/example/driver/portable/led.cpp @@ -13,20 +13,24 @@ #include #include "../led.h" +using namespace bsp; + +static bool g_LedState[LED_MAX] = {}; + static const char *Led_GetPin(Led::Id led) { switch (led) { case LED_RED: return "RED"; + case LED_ORANGE: return "ORANGE"; case LED_GREEN: return "GREEN"; case LED_BLUE: return "BLUE"; default: - assert(false); - return NULL; + return "UNK"; } } -void Log(const char *label, Led::Id led, bool state) +static void PrintMsg(const char *label, Led::Id led, bool state) { static const time_t g_SecNow = time(NULL); time_t now = time(NULL); @@ -36,15 +40,31 @@ void Log(const char *label, Led::Id led, bool state) void Led::Init(Id led, bool init_state) { - Log("LED_INIT", led, init_state); - // required to show log in Eclipse IDE Console for 64-bit binary #if defined(_WIN32) && !defined(_MSC_VER) - setbuf(stdout, NULL); + static bool s_init = false; + if (!s_init) + { + setbuf(stdout, NULL); + s_init = true; + } #endif + + if (static_cast(led) < LED_MAX) + g_LedState[led] = init_state; + + PrintMsg("LED_INIT", led, init_state); } void Led::Set(Id led, bool state) { - Log("LED_SET_STATE", led, state); + if (static_cast(led) < LED_MAX) + { + if (g_LedState[led] == state) + return; + + g_LedState[led] = state; + } + + PrintMsg("LED_SET_STATE", led, state); } diff --git a/build/example/driver/rp2350w/led.cpp b/build/example/driver/rp2350w/led.cpp index d04e9052..478421c6 100644 --- a/build/example/driver/rp2350w/led.cpp +++ b/build/example/driver/rp2350w/led.cpp @@ -17,7 +17,9 @@ #include "../led.h" -#define NO_LED ~0 +using namespace bsp; + +#define NO_LED (~0) static void Led_InitGpio(uint16_t pin) { @@ -28,11 +30,11 @@ static void Led_InitGpio(uint16_t pin) gpio_init(pin); gpio_set_dir(pin, GPIO_OUT); #elif defined(CYW43_WL_GPIO_LED_PIN) - static bool init = false; - if (!init) + static bool s_init = false; + if (!s_init) { cyw43_arch_init(); - init = true; + s_init = true; } #endif } @@ -41,17 +43,12 @@ static uint16_t Led_GetPin(Led::Id led) { switch (led) { - case Led::RED: - return NO_LED; - case Led::GREEN: { + case Led::GREEN: #if defined(PICO_DEFAULT_LED_PIN) return PICO_DEFAULT_LED_PIN; #elif defined(CYW43_WL_GPIO_LED_PIN) return CYW43_WL_GPIO_LED_PIN; #endif - } - case Led::BLUE: - return NO_LED; default: return NO_LED; } diff --git a/build/example/driver/stm32f4xx/led.cpp b/build/example/driver/stm32f4xx/led.cpp index 9e776c71..1c435ee7 100644 --- a/build/example/driver/stm32f4xx/led.cpp +++ b/build/example/driver/stm32f4xx/led.cpp @@ -12,6 +12,8 @@ #include "stm32f4xx_hal_gpio.h" #include "../led.h" +using namespace bsp; + #define LED_PORT GPIOD static void Led_InitGpio(int32_t pin) @@ -19,21 +21,23 @@ static void Led_InitGpio(int32_t pin) __HAL_RCC_GPIOD_CLK_ENABLE(); GPIO_InitTypeDef gpio = {}; - gpio.Mode = GPIO_MODE_OUTPUT_PP; - gpio.Pin = pin; - gpio.Pull = GPIO_NOPULL; - gpio.Speed = GPIO_SPEED_FREQ_HIGH; + gpio.Mode = GPIO_MODE_OUTPUT_PP; + gpio.Pin = pin; + gpio.Pull = GPIO_NOPULL; + gpio.Speed = GPIO_SPEED_FREQ_LOW; gpio.Alternate = 0; HAL_GPIO_Init(LED_PORT, &gpio); } static int32_t Led_GetPin(Led::Id led) { + // STM32F407G-DISC1 LEDs are on PD12-PD15 switch (led) { - case Led::RED: return GPIO_PIN_14; - case Led::GREEN: return GPIO_PIN_12; - case Led::BLUE: return GPIO_PIN_15; + case Led::GREEN: return GPIO_PIN_12; + case Led::ORANGE: return GPIO_PIN_13; + case Led::RED: return GPIO_PIN_14; + case Led::BLUE: return GPIO_PIN_15; default: return GPIO_PIN_MASK; } diff --git a/build/example/hrt/example.cpp b/build/example/hrt/example.cpp index 3ad7a895..68be86cc 100644 --- a/build/example/hrt/example.cpp +++ b/build/example/hrt/example.cpp @@ -12,6 +12,12 @@ #include