Jelajahi Sumber

KBF-5 implement RTOS EventGroup handling

Bence Balint 3 tahun lalu
induk
melakukan
ca638cbaee
6 mengubah file dengan 230 tambahan dan 4 penghapusan
  1. 1 0
      CMakeLists.txt
  2. 77 4
      include/kbf/rtos.h
  3. 1 0
      src/gpio.cpp
  4. 71 0
      src/rtos/event_group.cpp
  5. 2 0
      src/rtos/task.cpp
  6. 78 0
      test/rtos/test_event_group.cpp

+ 1 - 0
CMakeLists.txt

@@ -11,6 +11,7 @@ idf_component_register(
         "src/net.cpp"
         "src/nvs.cpp"
         "src/spiffs.cpp"
+        "src/rtos/event_group.cpp"
         "src/rtos/task.cpp"
         "src/uart.cpp"
         "src/web_service.cpp"

+ 77 - 4
include/kbf/rtos.h

@@ -2,11 +2,11 @@
 #define KBF_RTOS_H
 
 #include <string>
-
-using std::string;
+#include <vector>
 
 #include <freertos/FreeRTOS.h>
 #include <freertos/task.h>
+#include <freertos/event_groups.h>
 
 namespace kbf::rtos {
     /**
@@ -26,7 +26,7 @@ namespace kbf::rtos {
          * @param function function to run
          * @param args arguments to pass to callback; default is nullptr
          */
-        Task(string name, TaskFunc function, void *args = nullptr);
+        Task(std::string name, TaskFunc function, void *args = nullptr);
 
         /**
          * @brief Destructor.
@@ -58,7 +58,7 @@ namespace kbf::rtos {
          * @note RTOS seems to impose a 16-byte limit on task names. If the provided name is longer,
          * it will be silently truncated.
          */
-        const string name;
+        const std::string name;
 
     private:
         bool         running = false;
@@ -72,6 +72,79 @@ namespace kbf::rtos {
          */
         static void task(void *pvParameters);
     };
+
+    /**
+     * RTOS event group.
+     */
+    class EventGroup {
+    public:
+        /** @brief Tag used for logging. */
+        static const constexpr char *const TAG = "kbf::rtos::EventGroup";
+
+        /**
+         * @brief Default constructor.
+         */
+        EventGroup();
+
+        /**
+         * @brief Sets a bit to 1.
+         *
+         * @param bit
+         */
+        void setBit(int bit) const;
+
+        /**
+         * @brief Sets a bit to 0.
+         *
+         * @param bit
+         */
+        void clearBit(int bit) const;
+
+        /**
+         * @brief Sets all bits to 0.
+         */
+        void clear() const;
+
+        /**
+         * @brief Get the value of a bit.
+         *
+         * @param bit
+         * @return true if bit is 1; false otherwise
+         */
+        [[nodiscard]] bool getBit(int bit) const;
+
+        /**
+         * @see getBit()
+         */
+        bool operator[](int bit) const;
+
+        /**
+         * @brief Blocks execution until a bit is set to 1 or timeoutMs milliseconds have passed.
+         *
+         * @param bit bit to wait for
+         * @param timeoutMs timeout in milliseconds
+         * @return true if bit is set to 1; false if timeout is reached
+         */
+        bool waitForBit(int bit, int timeoutMs = portMAX_DELAY) const; // NOLINT(modernize-use-nodiscard)
+
+        /**
+         * @brief Blocks execution until all bits are set to 1 or timeoutMs milliseconds have passed.
+         *
+         * @param bits bits to wait for
+         * @param timeoutMs timeout in milliseconds
+         * @return true if all bits are set to 1; false if timeout is reached
+         */
+        bool waitForAll(const std::vector<int> &bits, // NOLINT(modernize-use-nodiscard)
+                        int timeoutMs = portMAX_DELAY) const;
+
+        bool waitForAny(const std::vector<int> &bits, // NOLINT(modernize-use-nodiscard)
+                        int timeoutMs = portMAX_DELAY) const;
+
+    private:
+        EventGroupHandle_t handle;
+
+        [[nodiscard]] bool wait(const std::vector<int> &bits, int timeoutMs, bool all) const;
+    };
 }
 
 #endif //KBF_RTOS_H

+ 1 - 0
src/gpio.cpp

@@ -8,6 +8,7 @@
 
 using namespace kbf;
 using namespace kbf::rtos;
+using std::string;
 
 gpio::Output::Output(int pin) : pin(static_cast<gpio_num_t>(pin)) {
     ESP_LOGI(TAG, "setting GPIO %d to OUTPUT mode", pin);

+ 71 - 0
src/rtos/event_group.cpp

@@ -0,0 +1,71 @@
+#include "kbf/rtos.h"
+
+#include <esp_log.h>
+
+using namespace kbf;
+using std::vector;
+
+rtos::EventGroup::EventGroup() {
+    handle = xEventGroupCreate();
+}
+
+void rtos::EventGroup::setBit(int bit) const {
+    ESP_LOGD(TAG, "setBit(%d)", bit);
+    xEventGroupSetBits(handle, 1 << bit);
+}
+
+void rtos::EventGroup::clearBit(int bit) const {
+    ESP_LOGD(TAG, "clearBit(%d)", bit);
+    xEventGroupClearBits(handle, 1 << bit);
+}
+
+void rtos::EventGroup::clear() const {
+    ESP_LOGD(TAG, "clear()");
+    xEventGroupClearBits(handle, 0b11111111);
+}
+
+bool rtos::EventGroup::getBit(int bit) const {
+    ESP_LOGD(TAG, "getBit(%d)", bit);
+    return xEventGroupGetBits(handle) & (1 << bit);
+}
+
+bool rtos::EventGroup::operator[](int bit) const {
+    return getBit(bit);
+}
+
+bool rtos::EventGroup::waitForBit(int bit, int timeoutMs) const {
+    auto ticksToWait = timeoutMs == -1 ? -1 : timeoutMs / portTICK_PERIOD_MS;
+    ESP_LOGD(TAG, "waitForBit(%d) start (timeout=%d, ticksToWait=%d)", bit, timeoutMs, ticksToWait);
+    xEventGroupWaitBits(handle, 1 << bit, pdFALSE, pdTRUE, ticksToWait);
+    ESP_LOGD(TAG, "waitForBit(%d) finish", bit);
+    return getBit(bit);
+}
+
+bool rtos::EventGroup::wait(const vector<int> &bits, int timeoutMs, bool all) const {
+    auto ticksToWait = timeoutMs == -1 ? -1 : timeoutMs / portTICK_PERIOD_MS;
+    ESP_LOGD(TAG, "wait(all=%s) start (timeout=%d, ticksToWait=%d)", all ? "true" : "false", timeoutMs, ticksToWait);
+    EventBits_t eventBits{};
+
+    for (int bit : bits) {
+        eventBits |= 1 << bit;
+    }
+    ESP_LOGD(TAG, "eventBits=%d", eventBits);
+    xEventGroupWaitBits(handle, eventBits, pdFALSE, all, ticksToWait);
+
+    bool result = true;
+    eventBits = xEventGroupGetBits(handle);
+    for (int bit : bits) {
+        if (!(eventBits & (1 << bit))) result = false;
+    }
+
+    ESP_LOGD(TAG, "wait(all=%s) finish=%s; eventBits=%d", all ? "true" : "false", result ? "true" : "false", eventBits);
+    return result;
+}
+
+bool rtos::EventGroup::waitForAll(const vector<int> &bits, int timeoutMs) const {
+    return wait(bits, timeoutMs, true);
+}
+
+bool rtos::EventGroup::waitForAny(const vector<int> &bits, int timeoutMs) const {
+    return wait(bits, timeoutMs, false);
+}

+ 2 - 0
src/rtos/task.cpp

@@ -6,6 +6,8 @@
 
 #include <kbf/macros.h>
 
+using std::string;
+
 static constexpr const char *TAG = "kbf::rtos::Task";
 
 kbf::rtos::Task::Task(string name, TaskFunc function, void *data) : function(function),

+ 78 - 0
test/rtos/test_event_group.cpp

@@ -0,0 +1,78 @@
+#include <esp_log.h>
+
+#include <kbf.h>
+#include <kbf/rtos.h>
+
+#include <unity.h>
+
+using namespace kbf;
+
+static const int DELAY = 500;
+enum {
+    TEST_EVENT_QUIT = 0,
+    TEST_EVENT_RESET,
+    TEST_EVENT_TRIGGER_1,
+    TEST_EVENT_FINISHED,
+    TEST_EVENT_TRIGGER_2,
+};
+
+static const constexpr char *const TAG = "test_event_group";
+
+void trigger_1(void *data) {
+    auto eventGroup = static_cast<rtos::EventGroup *>(data);
+    while (!eventGroup->getBit(TEST_EVENT_QUIT)) {
+        kbf::sleep(DELAY);
+        eventGroup->setBit(TEST_EVENT_TRIGGER_1);
+        eventGroup->waitForAny({TEST_EVENT_QUIT, TEST_EVENT_RESET});
+        if (eventGroup->getBit(TEST_EVENT_QUIT)) break;
+        eventGroup->clear();
+    }
+    eventGroup->setBit(TEST_EVENT_FINISHED);
+}
+
+void trigger_2(void *data) {
+    auto eventGroup = static_cast<rtos::EventGroup *>(data);
+    kbf::sleep(DELAY * 2);
+    TEST_ASSERT_FALSE((*eventGroup)[TEST_EVENT_QUIT])
+    eventGroup->setBit(TEST_EVENT_TRIGGER_2);
+    eventGroup->waitForBit(TEST_EVENT_QUIT);
+    eventGroup->clearBit(TEST_EVENT_TRIGGER_2);
+}
+
+TEST_CASE("RTOS EventGroup", "[kbf_rtos]") {
+    auto eventGroup = rtos::EventGroup();
+
+    ESP_LOGI(TAG, "test trigger1");
+    auto task = rtos::Task("trigger1", trigger_1, &eventGroup);
+    TEST_ASSERT_TRUE(eventGroup.waitForBit(TEST_EVENT_TRIGGER_1, DELAY * 2))
+
+    ESP_LOGI(TAG, "test trigger1 timeout");
+    eventGroup.setBit(TEST_EVENT_RESET);
+    kbf::sleep(100);
+    TEST_ASSERT_FALSE(eventGroup.waitForBit(TEST_EVENT_TRIGGER_1, (DELAY / 2) + 100))
+    TEST_ASSERT_FALSE(eventGroup[TEST_EVENT_TRIGGER_1])
+    kbf::sleep((DELAY / 2) + 100);
+    TEST_ASSERT_TRUE(eventGroup[TEST_EVENT_TRIGGER_1])
+
+    ESP_LOGI(TAG, "test double trigger timeout");
+    eventGroup.setBit(TEST_EVENT_RESET);
+    kbf::sleep(100);
+    TEST_ASSERT_FALSE(eventGroup.waitForAll({TEST_EVENT_TRIGGER_1, TEST_EVENT_TRIGGER_2}, DELAY * 3))
+    TEST_ASSERT_TRUE(eventGroup[TEST_EVENT_TRIGGER_1])
+    TEST_ASSERT_FALSE(eventGroup[TEST_EVENT_TRIGGER_2])
+
+    ESP_LOGI(TAG, "test double trigger");
+    eventGroup.setBit(TEST_EVENT_RESET);
+    kbf::sleep(100);
+    auto other = rtos::Task("trigger2", trigger_2, &eventGroup);
+    TEST_ASSERT_TRUE(eventGroup.waitForAll({TEST_EVENT_TRIGGER_1, TEST_EVENT_TRIGGER_2}, DELAY * 3))
+    TEST_ASSERT_TRUE(eventGroup[TEST_EVENT_TRIGGER_1])
+    TEST_ASSERT_TRUE(eventGroup[TEST_EVENT_TRIGGER_2])
+
+    ESP_LOGI(TAG, "test finish");
+    TEST_ASSERT_FALSE(eventGroup[TEST_EVENT_FINISHED])
+    eventGroup.setBit(TEST_EVENT_QUIT);
+    kbf::sleep(100);
+    TEST_ASSERT_TRUE(eventGroup[TEST_EVENT_FINISHED])
+    TEST_ASSERT_FALSE(eventGroup[TEST_EVENT_TRIGGER_2])
+}