From 8282b0f9ea9b3aa7b69a8003fbdef913b30dd7b9 Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Wed, 9 Sep 2020 16:52:03 +0800 Subject: [PATCH] esp_timer: added C++ wrapper for esp_timer Closes IDF-1074 --- .../experimental/esp_timer_cxx/CMakeLists.txt | 8 + .../cxx/experimental/esp_timer_cxx/Makefile | 10 + .../cxx/experimental/esp_timer_cxx/README.md | 48 +++++ .../esp_timer_cxx/main/CMakeLists.txt | 2 + .../esp_timer_cxx/main/component.mk | 4 + .../esp_timer_cxx/main/esp_timer_example.cpp | 44 ++++ .../esp_timer_cxx/sdkconfig.defaults | 3 + .../experimental_cpp_component/CMakeLists.txt | 9 +- .../esp_timer_cxx.cpp | 60 ++++++ .../include/esp_timer_cxx.hpp | 144 +++++++++++++ .../test/test_esp_timer.cpp | 198 ++++++++++++++++++ 11 files changed, 528 insertions(+), 2 deletions(-) create mode 100644 examples/cxx/experimental/esp_timer_cxx/CMakeLists.txt create mode 100644 examples/cxx/experimental/esp_timer_cxx/Makefile create mode 100644 examples/cxx/experimental/esp_timer_cxx/README.md create mode 100644 examples/cxx/experimental/esp_timer_cxx/main/CMakeLists.txt create mode 100644 examples/cxx/experimental/esp_timer_cxx/main/component.mk create mode 100644 examples/cxx/experimental/esp_timer_cxx/main/esp_timer_example.cpp create mode 100644 examples/cxx/experimental/esp_timer_cxx/sdkconfig.defaults create mode 100644 examples/cxx/experimental/experimental_cpp_component/esp_timer_cxx.cpp create mode 100644 examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp create mode 100644 examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp diff --git a/examples/cxx/experimental/esp_timer_cxx/CMakeLists.txt b/examples/cxx/experimental/esp_timer_cxx/CMakeLists.txt new file mode 100644 index 0000000000..24274795b6 --- /dev/null +++ b/examples/cxx/experimental/esp_timer_cxx/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(esp_timer_cxx) diff --git a/examples/cxx/experimental/esp_timer_cxx/Makefile b/examples/cxx/experimental/esp_timer_cxx/Makefile new file mode 100644 index 0000000000..8f26330b5c --- /dev/null +++ b/examples/cxx/experimental/esp_timer_cxx/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +EXTRA_COMPONENT_DIRS += ${IDF_PATH}/examples/cxx/experimental/experimental_cpp_component + +PROJECT_NAME := esp_timer_cxx + +include $(IDF_PATH)/make/project.mk diff --git a/examples/cxx/experimental/esp_timer_cxx/README.md b/examples/cxx/experimental/esp_timer_cxx/README.md new file mode 100644 index 0000000000..e041b2a394 --- /dev/null +++ b/examples/cxx/experimental/esp_timer_cxx/README.md @@ -0,0 +1,48 @@ +# Example: ESPTimer C++ class + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates usage of the ESPTimer c++ class in ESP-IDF. + +In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option. +This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling. +This is necessary for the C++ APIs. + +## How to use example + +### Hardware Required + +Any ESP32 family development board. + +### Configure the project + +``` +idf.py menuconfig +``` + +### Build and Flash + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +Setting up timer to trigger in 500ms +timeout +Setting up timer to periodically every 200ms +periodic timeout +periodic timeout +periodic timeout +periodic timeout +periodic timeout + +``` + diff --git a/examples/cxx/experimental/esp_timer_cxx/main/CMakeLists.txt b/examples/cxx/experimental/esp_timer_cxx/main/CMakeLists.txt new file mode 100644 index 0000000000..6b245fe111 --- /dev/null +++ b/examples/cxx/experimental/esp_timer_cxx/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "esp_timer_example.cpp" + INCLUDE_DIRS ".") diff --git a/examples/cxx/experimental/esp_timer_cxx/main/component.mk b/examples/cxx/experimental/esp_timer_cxx/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/cxx/experimental/esp_timer_cxx/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/cxx/experimental/esp_timer_cxx/main/esp_timer_example.cpp b/examples/cxx/experimental/esp_timer_cxx/main/esp_timer_example.cpp new file mode 100644 index 0000000000..47640f8659 --- /dev/null +++ b/examples/cxx/experimental/esp_timer_cxx/main/esp_timer_example.cpp @@ -0,0 +1,44 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_timer_cxx.hpp" +#include "esp_exception.hpp" + +using namespace std; +using namespace idf; +using namespace idf::esp_timer; + +extern "C" void app_main(void) +{ + try { + cout << "Setting up timer to trigger in 500ms" << endl; + ESPTimer timer([]() { cout << "timeout" << endl; }); + timer.start(chrono::microseconds(200 * 1000)); + + vTaskDelay(550 / portTICK_PERIOD_MS); + + cout << "Setting up timer to trigger periodically every 200ms" << endl; + ESPTimer timer2([]() { cout << "periodic timeout" << endl; }); + timer2.start_periodic(chrono::microseconds(200 * 1000)); + + vTaskDelay(1050 / portTICK_PERIOD_MS); + + } catch (const ESPException &e) { + cout << "Exception with error: " << e.error << endl; + } +} diff --git a/examples/cxx/experimental/esp_timer_cxx/sdkconfig.defaults b/examples/cxx/experimental/esp_timer_cxx/sdkconfig.defaults new file mode 100644 index 0000000000..a365ac6589 --- /dev/null +++ b/examples/cxx/experimental/esp_timer_cxx/sdkconfig.defaults @@ -0,0 +1,3 @@ +# Enable C++ exceptions and set emergency pool size for exception objects +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 diff --git a/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt b/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt index 434ac51173..ec30b2b291 100644 --- a/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt +++ b/examples/cxx/experimental/experimental_cpp_component/CMakeLists.txt @@ -1,3 +1,8 @@ -idf_component_register(SRCS "esp_exception.cpp" "i2c_cxx.cpp" "esp_event_api.cpp" "esp_event_cxx.cpp" +idf_component_register(SRCS + "esp_exception.cpp" + "i2c_cxx.cpp" + "esp_event_api.cpp" + "esp_event_cxx.cpp" + "esp_timer_cxx.cpp" INCLUDE_DIRS "include" - REQUIRES driver esp_event) + REQUIRES driver esp_event esp_timer) diff --git a/examples/cxx/experimental/experimental_cpp_component/esp_timer_cxx.cpp b/examples/cxx/experimental/experimental_cpp_component/esp_timer_cxx.cpp new file mode 100644 index 0000000000..f4b28440ca --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/esp_timer_cxx.cpp @@ -0,0 +1,60 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef __cpp_exceptions + +#include +#include "esp_timer_cxx.hpp" +#include "esp_exception.hpp" + +using namespace std; + +namespace idf { + +namespace esp_timer { + +ESPTimer::ESPTimer(function timeout_cb, const string &timer_name) + : timeout_cb(timeout_cb), name(timer_name) +{ + if (timeout_cb == nullptr) { + throw ESPException(ESP_ERR_INVALID_ARG); + } + + esp_timer_create_args_t timer_args = {}; + timer_args.callback = esp_timer_cb; + timer_args.arg = this; + timer_args.dispatch_method = ESP_TIMER_TASK; + timer_args.name = name.c_str(); + + CHECK_THROW(esp_timer_create(&timer_args, &timer_handle)); +} + +ESPTimer::~ESPTimer() +{ + // Ignore potential ESP_ERR_INVALID_STATE here to not throw exception. + esp_timer_stop(timer_handle); + esp_timer_delete(timer_handle); +} + +void ESPTimer::esp_timer_cb(void *arg) +{ + ESPTimer *timer = static_cast(arg); + timer->timeout_cb(); +} + +} // esp_timer + +} // idf + +#endif // __cpp_exceptions diff --git a/examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp b/examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp new file mode 100644 index 0000000000..b247226d57 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp @@ -0,0 +1,144 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifdef __cpp_exceptions + +#include +#include +#include "esp_exception.hpp" +#include "esp_timer.h" + +namespace idf { + +namespace esp_timer { + +/** + * @brief Get time since boot + * @return time since \c esp_timer_init() was called (this normally happens early during application startup). + */ +static inline std::chrono::microseconds get_time() +{ + return std::chrono::microseconds(esp_timer_get_time()); +} + +/** + * @brief Get the timestamp when the next timeout is expected to occur + * @return Timestamp of the nearest timer event. + * The timebase is the same as for the values returned by \c get_time(). + */ +static inline std::chrono::microseconds get_next_alarm() +{ + return std::chrono::microseconds(esp_timer_get_next_alarm()); +} + + +/** + * @brief + * A timer using the esp_timer component which can be started either as one-shot timer or periodically. + */ +class ESPTimer { +public: + /** + * @param timeout_cb The timeout callback. + * @param timer_name The name of the timer (optional). This is for debugging using \c esp_timer_dump(). + */ + ESPTimer(std::function timeout_cb, const std::string &timer_name = "ESPTimer"); + + /** + * Stop the timer if necessary and delete it. + */ + ~ESPTimer(); + + /** + * Default copy constructor is deleted since one instance of esp_timer_handle_t must not be shared. + */ + ESPTimer(const ESPTimer&) = delete; + + /** + * Default copy assignment is deleted since one instance of esp_timer_handle_t must not be shared. + */ + ESPTimer &operator=(const ESPTimer&) = delete; + + /** + * @brief Start one-shot timer + * + * Timer should not be running (started) when this function is called. + * + * @param timeout timer timeout, in microseconds relative to the current moment. + * + * @throws ESPException with error ESP_ERR_INVALID_STATE if the timer is already running. + */ + inline void start(std::chrono::microseconds timeout) + { + CHECK_THROW(esp_timer_start_once(timer_handle, timeout.count())); + } + + /** + * @brief Start periodic timer + * + * Timer should not be running when this function is called. This function will + * start a timer which will trigger every 'period' microseconds. + * + * Timer should not be running (started) when this function is called. + * + * @param timeout timer timeout, in microseconds relative to the current moment. + * + * @throws ESPException with error ESP_ERR_INVALID_STATE if the timer is already running. + */ + inline void start_periodic(std::chrono::microseconds period) + { + CHECK_THROW(esp_timer_start_periodic(timer_handle, period.count())); + } + + /** + * @brief Stop the previously started timer. + * + * This function stops the timer previously started using \c start() or \c start_periodic(). + * + * @throws ESPException with error ESP_ERR_INVALID_STATE if the timer has not been started yet. + */ + inline void stop() + { + CHECK_THROW(esp_timer_stop(timer_handle)); + } + +private: + /** + * Internal callback to hook into esp_timer component. + */ + static void esp_timer_cb(void *arg); + + /** + * Timer instance of the underlying esp_event component. + */ + esp_timer_handle_t timer_handle; + + /** + * Callback which will be called once the timer triggers. + */ + std::function timeout_cb; + + /** + * Name of the timer, will be passed to the underlying timer framework and is used for debugging. + */ + const std::string name; +}; + +} // esp_timer + +} // idf + +#endif // __cpp_exceptions diff --git a/examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp b/examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp new file mode 100644 index 0000000000..e36ab58ba1 --- /dev/null +++ b/examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp @@ -0,0 +1,198 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef __cpp_exceptions + +#include "unity.h" +#include "unity_cxx.hpp" +#include +#include + +#include +#include "test_utils.h" // ref clock +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "esp_timer_cxx.hpp" +#include "esp_exception.hpp" + +using namespace std; +using namespace idf; +using namespace idf::esp_timer; + +struct RefClock { + RefClock() + { + ref_clock_init(); + }; + + ~RefClock() + { + ref_clock_deinit(); + } +}; + +TEST_CASE("ESPTimer null function", "[ESPTimer]") +{ + TEST_THROW(ESPTimer(nullptr), ESPException); +} + +TEST_CASE("ESPTimer empty std::function", "[ESPTimer]") +{ + function nothing; + TEST_THROW(ESPTimer(nothing, "test"), ESPException); +} + +TEST_CASE("ESPTimer starting twice throws", "[ESPTimer]") +{ + function timer_cb = [&]() { }; + + ESPTimer timer(timer_cb); + + timer.start(chrono::microseconds(5000)); + + TEST_THROW(timer.start(chrono::microseconds(5000)), ESPException); +} + +TEST_CASE("ESPTimer periodically starting twice throws", "[ESPTimer]") +{ + function timer_cb = [&]() { }; + + ESPTimer timer(timer_cb); + + timer.start_periodic(chrono::microseconds(5000)); + + TEST_THROW(timer.start_periodic(chrono::microseconds(5000)), ESPException); +} + +TEST_CASE("ESPTimer stopping non-started timer throws", "[ESPTimer]") +{ + function timer_cb = [&]() { }; + + ESPTimer timer(timer_cb); + + TEST_THROW(timer.stop(), ESPException); +} + +TEST_CASE("ESPTimer calls callback", "[ESPTimer]") +{ + bool called = false; + + function timer_cb = [&]() { + called = true; + }; + + ESPTimer timer(timer_cb); + + timer.start(chrono::microseconds(5000)); + + vTaskDelay(10 / portTICK_PERIOD_MS); + + TEST_ASSERT(called); +} + +TEST_CASE("ESPTimer periodically calls callback", "[ESPTimer]") +{ + size_t called = 0; + + function timer_cb = [&]() { + called++; + }; + + ESPTimer timer(timer_cb); + + timer.start_periodic(chrono::microseconds(2000)); + + vTaskDelay(10 / portTICK_PERIOD_MS); + + TEST_ASSERT(called >= 4u); +} + +TEST_CASE("ESPTimer produces correct delay", "[ESPTimer]") +{ + int64_t t_end; + + RefClock ref_clock; + + function timer_cb = [&t_end]() { + t_end = ref_clock_get(); + }; + + ESPTimer timer(timer_cb, "timer1"); + + const int delays_ms[] = {20, 100, 200, 250}; + const size_t delays_count = sizeof(delays_ms)/sizeof(delays_ms[0]); + + for (size_t i = 0; i < delays_count; ++i) { + t_end = 0; + int64_t t_start = ref_clock_get(); + + timer.start(chrono::microseconds(delays_ms[i] * 1000)); + + vTaskDelay(delays_ms[i] * 2 / portTICK_PERIOD_MS); + TEST_ASSERT(t_end != 0); + int32_t ms_diff = (t_end - t_start) / 1000; + printf("%d %d\n", delays_ms[i], ms_diff); + + TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, delays_ms[i], ms_diff); + } +} + +TEST_CASE("ESPtimer produces correct periodic delays", "[ESPTimer]") +{ + const size_t NUM_INTERVALS = 3u; + + size_t cur_interval = 0; + int intervals[NUM_INTERVALS]; + int64_t t_start; + SemaphoreHandle_t done; + + const int DELAY_MS = 100; + function timer_cb = [&]() { + int64_t t_end = ref_clock_get(); + int32_t ms_diff = (t_end - t_start) / 1000; + printf("timer #%d %dms\n", cur_interval, ms_diff); + if (cur_interval < NUM_INTERVALS) { + intervals[cur_interval++] = ms_diff; + } + // Deliberately make timer handler run longer. + // We check that this doesn't affect the result. + esp_rom_delay_us(10*1000); + if (cur_interval == NUM_INTERVALS) { + printf("done\n"); + xSemaphoreGive(done); + } + }; + + ESPTimer timer(timer_cb, "timer1"); + RefClock ref_clock; + t_start = ref_clock_get(); + done = xSemaphoreCreateBinary(); + timer.start_periodic(chrono::microseconds(DELAY_MS * 1000)); + + TEST_ASSERT(xSemaphoreTake(done, DELAY_MS * NUM_INTERVALS * 2)); + timer.stop(); + + TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, cur_interval); + for (size_t i = 0; i < NUM_INTERVALS; ++i) { + TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, (i + 1) * DELAY_MS, intervals[i]); + } + TEST_ESP_OK(esp_timer_dump(stdout)); + + vSemaphoreDelete(done); +#undef NUM_INTERVALS +} + +#endif // __cpp_exceptions