add some code

This commit is contained in:
2025-09-05 13:25:11 +08:00
parent 9ff0a99e7a
commit 3cf1229a85
8911 changed files with 2535396 additions and 0 deletions

View File

@@ -0,0 +1 @@
a389d980693ad195b2160de22a72f3391694230188ab16b8f3c7ec4410a7c417

View File

@@ -0,0 +1,53 @@
# ChangeLog
## v1.0.0 - 2024-9-26
* Add ext1_wakeup mode for Knob when define CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y
## v0.1.5 - 2024-7-3
### Enhancements:
* Support power save mode
## v0.1.4 - 2023-11-23
* Fix possible cmake_utilities dependency issue
## v0.1.3 - 2023-6-2
### Enhancements:
* Add power on knob position detection to avoid logical inversion caused by knob position
* Change test to test_apps project
## v0.1.2 - 2023-3-9
### Enhancements:
* Use cu_pkg_define_version to define the version of this component.
## v0.1.1 - 2023-1-18
### Bug Fixes:
* Knob:
* Fix callback return usr_data root pointer, the usr_data of the relevant callback will now be returned.
## v0.1.0 - 2023-1-5
### Enhancements:
* Initial version
* The following types of events are supported
| EVENT | 描述 |
| ---------- | -------------------------------------- |
| KNOB_LEFT | EVENT: Rotate to the left |
| KNOB_RIGHT | EVENT: Rotate to the right |
| KNOB_H_LIM | EVENT: Count reaches maximum limit |
| KNOB_L_LIM | EVENT: Count reaches the minimum limit |
| KNOB_ZERO | EVENT: Count back to 0 |
* Support for defining multiple knobs
* Support binding callback functions for each event and adding user-data

View File

@@ -0,0 +1 @@
{"version": "1.0", "algorithm": "sha256", "created_at": "2025-05-21T16:34:53.822747+00:00", "files": [{"path": "CMakeLists.txt", "size": 263, "hash": "9d95116e9d6ffa9bbb7e35cf63e76321721d4006191e828aace5dce2e4812c01"}, {"path": "knob_gpio.c", "size": 3688, "hash": "b570a9f336a1e122fd92bc359967af269742cc9262dd390badc02597d77f5ac1"}, {"path": "CHANGELOG.md", "size": 1316, "hash": "96fab7881a8240735c4c3d6c0bd4aae0a1dd492fcc65fdf1a24ff0a149999bf2"}, {"path": "idf_component.yml", "size": 525, "hash": "fbc97b0f17013bbc7145aeb3cba79fda80dfecec50eae769b6aec8dd501a4514"}, {"path": "Kconfig", "size": 734, "hash": "18517f9f18070f422db3c0dc338677e303faf0b7da2c9364b30942dea446e0b8"}, {"path": "README.md", "size": 1336, "hash": "58b9ff23c15109fbce8bf4445f3f948fb226ebe5e9e3b10f37510a9cdcfcf770"}, {"path": "iot_knob.c", "size": 15782, "hash": "bfe06d2eaf02cb7751b591c8207b3a470b8dba714f2067522ff10bdc6759dd4d"}, {"path": "license.txt", "size": 11358, "hash": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30"}, {"path": "include/iot_knob.h", "size": 3436, "hash": "b89520ccfaced42ff36ca5bb1c00fde993c4c354a6c21305c97314b224a0a5b8"}, {"path": "include/knob_gpio.h", "size": 4009, "hash": "6cabc7813cc46485ae369338cfd08b1b8604084868dd59b6bcfda85696ec2b3f"}, {"path": "test_apps/CMakeLists.txt", "size": 351, "hash": "61ef5833df38b87c1a4d4d4bc5f9945628403fc41364a1b3efd0033e3cc5a591"}, {"path": "test_apps/sdkconfig.defaults", "size": 213, "hash": "9a34a6cb08c49ec24007587e0c5d492f44b5a862d9c0f583cf9f6f643669b564"}, {"path": "examples/knob_power_save/CMakeLists.txt", "size": 244, "hash": "09db09bc60353c54e7e5d739099b20671e860222725e7abc07d61ff9e4e1f107"}, {"path": "examples/knob_power_save/README.md", "size": 2076, "hash": "a9518bbc25ee747e2c6a2004a86ca0837ee1281ba015c9fe86c33ae835461731"}, {"path": "examples/knob_power_save/sdkconfig.defaults", "size": 287, "hash": "30729327b23211f345543772699d8c80015504f0a009662295d20332512ac2e2"}, {"path": "examples/usb_surface_dial/CMakeLists.txt", "size": 240, "hash": "643fdae937ccf202f641436b047e1df0649d22617aef0e3a7ca294652dc32bc7"}, {"path": "examples/usb_surface_dial/sdkconfig.ci.esp32s3_usb_otg", "size": 54, "hash": "f7d7edde3339e87365f269e1970d163c8c26548d6ef557e18ab6790e1602096e"}, {"path": "examples/usb_surface_dial/README.md", "size": 1401, "hash": "554ea7b91275e6f3ef7d56d984e8ca805bd2c773bd6dc3e005a1a4272cccd6d3"}, {"path": "examples/usb_surface_dial/_static/package.png", "size": 64124, "hash": "13abb2640c3628242a2b475529a76622181df2f3a7c58248dbb711d0325bb3d0"}, {"path": "examples/usb_surface_dial/_static/surface_dial.png", "size": 8069, "hash": "54b79c339566d940799496c7696b1d2a381522914c7067933d56e52fb14524cc"}, {"path": "examples/usb_surface_dial/_static/surface_dial_physical.jpg", "size": 977179, "hash": "908db6404fc8f4a18c2a8a227f29a20512a261a699da654e8753a1540a03b7c8"}, {"path": "examples/usb_surface_dial/_static/schematic.png", "size": 33455, "hash": "42677e75ca417f40657c66ee52ef1019396bce5e944909cc82a0705fd78a9fff"}, {"path": "examples/usb_surface_dial/main/CMakeLists.txt", "size": 247, "hash": "d831351c0e6802a53b05e0ec8ec94cd1f2ec8761394dfd10c1f18d183df105ea"}, {"path": "examples/usb_surface_dial/main/usb_descriptors.c", "size": 8099, "hash": "c1ac9d358c75a774cf752912ea8d2e94f9ada16318602c7d31bb303bad9c784c"}, {"path": "examples/usb_surface_dial/main/idf_component.yml", "size": 505, "hash": "b3d19e6726999912f145d8259b5391c82e628623ed6dd0ed951f5d4f1300e7d9"}, {"path": "examples/usb_surface_dial/main/main.c", "size": 3816, "hash": "ac69ec92d9ddf8358fd89038a5476fad3c082822709aebb8e3ce823528ed3c7b"}, {"path": "examples/usb_surface_dial/main/tusb_config.h", "size": 3667, "hash": "54fe8856126e388586927d3342b071515e9503ea38817bde5b2de6166322685a"}, {"path": "examples/usb_surface_dial/main/Kconfig.projbuild", "size": 677, "hash": "6ea8327630b575d9600884c3e860d4017bb7d83427029ecf2fe28a68d80143ec"}, {"path": "examples/knob_power_save/main/CMakeLists.txt", "size": 86, "hash": "72cf15b48b871684918db1a350f9b3866c88b434e4dd3077d76a1c8bf843e494"}, {"path": "examples/knob_power_save/main/idf_component.yml", "size": 128, "hash": "8729aa86516b7d62eab3611740424cf942b22a8934e7c513f6cc1a01fdf3746d"}, {"path": "examples/knob_power_save/main/knob_power_save.c", "size": 2849, "hash": "3b77988c2ee5b9469645fc850ea799704f2a31565a32d726faeaf7a71324c7a8"}, {"path": "examples/knob_power_save/main/Kconfig.projbuild", "size": 3119, "hash": "002da33c30cbc8c617b449615c551a8ca05368d934437ddbb481c4bc7beb0035"}, {"path": "test_apps/main/CMakeLists.txt", "size": 141, "hash": "56a57e4bc940368a61b7fa466fbf4c5bdd768ee8adbedabedd9560de0203e879"}, {"path": "test_apps/main/knob_test.c", "size": 4813, "hash": "4cdfe50d1f54c7031873f2e36bda97c13019325a32b508296defe804a95c77f6"}]}

View File

@@ -0,0 +1,7 @@
idf_component_register(SRCS "iot_knob.c" "knob_gpio.c"
INCLUDE_DIRS "include"
REQUIRES driver
PRIV_REQUIRES esp_timer)
include(package_manager)
cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR})

View File

@@ -0,0 +1,31 @@
menu "IOT Knob"
config KNOB_PERIOD_TIME_MS
int "BUTTON PERIOD TIME (MS)"
range 2 10
default 3
help
"Knob scan interval"
config KNOB_DEBOUNCE_TICKS
int "KNOB DEBOUNCE TICKS"
range 1 8
default 2
help
"One CONFIG_KNOB_DEBOUNCE_TICKS equal to n CONFIG_KNOB_PERIOD_TIME_MS"
config KNOB_HIGH_LIMIT
int "KNOB HIGH LIMIT"
range 1 10000
default 1000
help
"The highest number that can be counted by the knob"
config KNOB_LOW_LIMIT
int "KNOB LOW LIMIT"
range -10000 -1
default -1000
help
"The lowest number that can be counted by the knob"
endmenu

View File

@@ -0,0 +1,29 @@
[![Component Registry](https://components.espressif.com/components/espressif/knob/badge.svg)](https://components.espressif.com/components/espressif/knob)
## Component Knob
`Knob` is the component that provides the software quadrature decoding, it can be used on chips(esp32c2, esp32c3) that do not have PCNT hardware capabilities. By using this component, you can quickly use a physical encoder, such as the EC11 encoder.
Features:
1. Support multiple knobs
2. Support each event can register its own callback
3. Support setting the upper and lower count limits
List of supported events:
* Knob left
* Knob right
* Knob high limit
* Knob low limit
* Knob back to zero
### Examples
[USB Surface Dial](https://github.com/espressif/esp-iot-solution/tree/master/examples/usb/device/usb_surface_dial)
`Note`: This component is only suitable for decoding low-speed rotary encoders such as EC11, and does not guarantee the complete correctness of the pulse count. For high-speed and accurate calculations, please use hardware [PCNT](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/pcnt.html?highlight=pcnt)
* | Hardware PCNT Supported Targets | ESP32 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
| ------------------------------- | ----- | -------- | -------- | -------- | -------- |

View File

@@ -0,0 +1,6 @@
# The following five 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)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(knob_power_save)

View File

@@ -0,0 +1,47 @@
## Knob Power Save Example
This example demonstrates how to utilize the `knob` component in conjunction with the light sleep low-power mode.
* `knob` [Component Introduction](https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/knob.html)
## Hardware
Any GPIO on any development board can be used in this example, for the default GPIO, please refer to the table below.
| Hardware | GPIO |
| :-----------------: | :---: |
| Encoder A (Phase A) | GPIO1 |
| Encoder B (Phase B) | GPIO2 |
## Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
* Run `. ./export.sh` to set IDF environment
* Run `idf.py set-target esp32xx` to set target chip
* Run `idf.py -p PORT flash monitor` to build, flash and monitor the project
(To exit the serial monitor, type `Ctrl-]`.)
See the Getting Started Guide for all the steps to configure and use the ESP-IDF to build projects.
## Example Output
```
I (316) pm: Frequency switching config: CPU_MAX: 80, APB_MAX: 80, APB_MIN: 10, Light sleep: ENABLED
I (322) sleep: Code start at 0x42000020, total 106515, data start at 0x3c020020, total 46384 Bytes
I (331) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (341) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (350) Knob: Iot Knob Config Succeed, encoder A:1, encoder B:2, direction:0, Version: 0.1.4
I (359) main_task: Returned from app_main()
I (1503) knob_power_save: knob event KNOB_LEFT, -1
I (1503) knob_power_save: Wake up from light sleep, reason 7
I (1691) knob_power_save: knob event KNOB_LEFT, -2
I (1691) knob_power_save: Wake up from light sleep, reason 7
I (1860) knob_power_save: knob event KNOB_LEFT, -3
I (1860) knob_power_save: Wake up from light sleep, reason 7
I (1940) knob_power_save: knob event KNOB_LEFT, -4
I (1940) knob_power_save: Wake up from light sleep, reason 7
I (2017) knob_power_save: knob event KNOB_LEFT, -5
I (2017) knob_power_save: Wake up from light sleep, reason 7
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "knob_power_save.c"
INCLUDE_DIRS ".")

View File

@@ -0,0 +1,74 @@
menu "Example Configuration"
choice EXAMPLE_MAX_CPU_FREQ
prompt "Maximum CPU frequency"
default EXAMPLE_MAX_CPU_FREQ_80 if !IDF_TARGET_ESP32H2
default EXAMPLE_MAX_CPU_FREQ_96 if IDF_TARGET_ESP32H2
depends on PM_ENABLE
help
Maximum CPU frequency to use for dynamic frequency scaling.
config EXAMPLE_MAX_CPU_FREQ_80
bool "80 MHz"
config EXAMPLE_MAX_CPU_FREQ_96
bool "96 MHz"
depends on IDF_TARGET_ESP32H2
config EXAMPLE_MAX_CPU_FREQ_120
bool "120 MHz"
depends on IDF_TARGET_ESP32C2
config EXAMPLE_MAX_CPU_FREQ_160
bool "160 MHz"
depends on !IDF_TARGET_ESP32C2
config EXAMPLE_MAX_CPU_FREQ_240
bool "240 MHz"
depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
endchoice
config EXAMPLE_MAX_CPU_FREQ_MHZ
int
default 80 if EXAMPLE_MAX_CPU_FREQ_80
default 96 if EXAMPLE_MAX_CPU_FREQ_96
default 120 if EXAMPLE_MAX_CPU_FREQ_120
default 160 if EXAMPLE_MAX_CPU_FREQ_160
default 240 if EXAMPLE_MAX_CPU_FREQ_240
choice EXAMPLE_MIN_CPU_FREQ
prompt "Minimum CPU frequency"
default EXAMPLE_MIN_CPU_FREQ_10M if !IDF_TARGET_ESP32H2
default EXAMPLE_MIN_CPU_FREQ_32M if IDF_TARGET_ESP32H2
depends on PM_ENABLE
help
Minimum CPU frequency to use for dynamic frequency scaling.
Should be set to XTAL frequency or XTAL frequency divided by integer.
config EXAMPLE_MIN_CPU_FREQ_40M
bool "40 MHz (use with 40MHz XTAL)"
depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO || !IDF_TARGET_ESP32
config EXAMPLE_MIN_CPU_FREQ_20M
bool "20 MHz (use with 40MHz XTAL)"
depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO || !IDF_TARGET_ESP32
config EXAMPLE_MIN_CPU_FREQ_10M
bool "10 MHz (use with 40MHz XTAL)"
depends on XTAL_FREQ_40 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_40 || ESP32_XTAL_FREQ_AUTO || !IDF_TARGET_ESP32
config EXAMPLE_MIN_CPU_FREQ_26M
bool "26 MHz (use with 26MHz XTAL)"
depends on XTAL_FREQ_26 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_26 || ESP32_XTAL_FREQ_AUTO
config EXAMPLE_MIN_CPU_FREQ_13M
bool "13 MHz (use with 26MHz XTAL)"
depends on XTAL_FREQ_26 || XTAL_FREQ_AUTO || ESP32_XTAL_FREQ_26 || ESP32_XTAL_FREQ_AUTO
config EXAMPLE_MIN_CPU_FREQ_32M
bool "32 MHz (use with 32MHz XTAL)"
depends on IDF_TARGET_ESP32H2
depends on XTAL_FREQ_32 || XTAL_FREQ_AUTO
endchoice
config EXAMPLE_MIN_CPU_FREQ_MHZ
int
default 40 if EXAMPLE_MIN_CPU_FREQ_40M
default 20 if EXAMPLE_MIN_CPU_FREQ_20M
default 10 if EXAMPLE_MIN_CPU_FREQ_10M
default 26 if EXAMPLE_MIN_CPU_FREQ_26M
default 13 if EXAMPLE_MIN_CPU_FREQ_13M
default 32 if EXAMPLE_MIN_CPU_FREQ_32M
endmenu

View File

@@ -0,0 +1,6 @@
version: "0.0.1"
dependencies:
idf: ">=4.4"
knob:
version: "*"
override_path: "../../../../components/knob"

View File

@@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_pm.h"
#include "iot_knob.h"
#include "esp_sleep.h"
#include "esp_idf_version.h"
#define ENCODER_A_GPIO 1
#define ENCODER_B_GPIO 2
static const char *TAG = "knob_power_save";
static knob_handle_t knob = NULL;
const char *knob_event_table[] = {
"KNOB_LEFT",
"KNOB_RIGHT",
"KNOB_H_LIM",
"KNOB_L_LIM",
"KNOB_ZERO",
};
static void knob_event_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "knob event %s, %d", knob_event_table[(knob_event_t)data], iot_knob_get_count_value(knob));
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause != ESP_SLEEP_WAKEUP_UNDEFINED) {
ESP_LOGI(TAG, "Wake up from light sleep, reason %d", cause);
}
}
static void knob_init(uint32_t encoder_a, uint32_t encoder_b)
{
knob_config_t cfg = {
.default_direction = 0,
.gpio_encoder_a = encoder_a,
.gpio_encoder_b = encoder_b,
#if CONFIG_PM_ENABLE
.enable_power_save = true,
#endif
};
knob = iot_knob_create(&cfg);
assert(knob);
esp_err_t err = iot_knob_register_cb(knob, KNOB_LEFT, knob_event_cb, (void *)KNOB_LEFT);
err |= iot_knob_register_cb(knob, KNOB_RIGHT, knob_event_cb, (void *)KNOB_RIGHT);
err |= iot_knob_register_cb(knob, KNOB_H_LIM, knob_event_cb, (void *)KNOB_H_LIM);
err |= iot_knob_register_cb(knob, KNOB_L_LIM, knob_event_cb, (void *)KNOB_L_LIM);
err |= iot_knob_register_cb(knob, KNOB_ZERO, knob_event_cb, (void *)KNOB_ZERO);
ESP_ERROR_CHECK(err);
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
void power_save_init(void)
{
esp_pm_config_t pm_config = {
.max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ,
.min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ,
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
.light_sleep_enable = true
#endif
};
ESP_ERROR_CHECK(esp_pm_configure(&pm_config));
}
#else
void power_save_init(void)
{
#if CONFIG_IDF_TARGET_ESP32
esp_pm_config_esp32_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S2
esp_pm_config_esp32s2_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32C3
esp_pm_config_esp32c3_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32S3
esp_pm_config_esp32s3_t pm_config = {
#elif CONFIG_IDF_TARGET_ESP32C2
esp_pm_config_esp32c2_t pm_config = {
#endif
.max_freq_mhz = CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ,
.min_freq_mhz = CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ,
#if CONFIG_FREERTOS_USE_TICKLESS_IDLE
.light_sleep_enable = true
#endif
};
ESP_ERROR_CHECK(esp_pm_configure(&pm_config));
}
#endif
void app_main(void)
{
power_save_init();
knob_init(ENCODER_A_GPIO, ENCODER_B_GPIO);
}

View File

@@ -0,0 +1,9 @@
# Enable support for power management
CONFIG_PM_ENABLE=y
# Enable tickless idle mode
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
# Put related source code in IRAM
CONFIG_PM_SLP_IRAM_OPT=y
CONFIG_PM_RTOS_IDLE_OPT=y
# Use 1000Hz freertos tick to lower sleep time threshold
CONFIG_FREERTOS_HZ=1000

View File

@@ -0,0 +1,6 @@
# 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)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(usb_surface_dial)

View File

@@ -0,0 +1,49 @@
## USB HID Surface Dial
This example shows how to use the ESP32-Sx USB function to emulate a Windows knob that allows volume control, page up and down, and more.
## Hardware Required
![surface_dial_physical](_static/surface_dial_physical.jpg)
- Any ESP32-Sx development board with **knob** functionality.
- The knob can be a series of rotary encoders with push function such as EC11
- It is also possible to use three buttons that simulate: press, left turn, right turn
- Hardware Connection
- GPIO19 to USB_D-
- GPIO20 to USB_D+
- GPIO42 to EC11_E
- GPIO1 to EC11_A
- GPIO2 to EC11_C
![schematic](_static/schematic.png)
![schematic](_static/package.png)
## Build and Flash
1. Set up the `ESP-IDF` environment variablesyou can refer [Set up the environment variables](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#step-4-set-up-the-environment-variables), Linux can using:
```
. $HOME/esp/esp-idf/export.sh
```
2. Set ESP-IDF build target to `esp32s2` or `esp32s3`
```bash
idf.py set-target esp32s2
```
3. Build, Flash, output log
```bash
idf.py build flash monitor
```
## How To Use
* Connect the USB to the Windows USB port and wait for the USB device to finish installing
* Press and hold the button to wake up the Windows wheel
![Surface dial](_static/surface_dial.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

View File

@@ -0,0 +1,8 @@
idf_component_register(
SRCS "main.c" "usb_descriptors.c"
INCLUDE_DIRS "."
)
idf_component_get_property(tusb_lib leeebo__tinyusb_src COMPONENT_LIB)
cmake_policy(SET CMP0079 NEW)
target_link_libraries(${tusb_lib} PRIVATE ${COMPONENT_LIB})

View File

@@ -0,0 +1,24 @@
menu "Example Configuration"
choice DEVELOPMENT_BOARD_SELECTION
prompt "Select the development board you are using"
default ESP32_S3_USB_OTG if IDF_TARGET_ESP32S3
default ESP32_S2_GENERIC if IDF_TARGET_ESP32S2
help
Select this option to choose the board for the example.
config ESP32_S3_USB_OTG
bool "ESP32 S3 USB OTG"
depends on IDF_TARGET_ESP32S3
config ESP32_S3_GENERIC
bool "ESP32 S3 GENERIC"
depends on IDF_TARGET_ESP32S3
config ESP32_S2_GENERIC
bool "ESP32 S2 GENERIC"
depends on IDF_TARGET_ESP32S2
endchoice
endmenu

View File

@@ -0,0 +1,19 @@
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: ">=4.4.0"
espressif/button:
version: ">=2.3.0"
override_path: "../../../../../components/button"
leeebo/tinyusb_src:
version: ">=0.0.4"
espressif/knob:
version: ">=0.1.0"
override_path: "../../../../../components/knob"
espressif/esp32_s3_usb_otg:
version: "^1.5.1"
rules:
- if: "target in [esp32s3]"
lvgl/lvgl: #temp to workaround bsp issue
version: "9.2.0"

View File

@@ -0,0 +1,154 @@
/*
* SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_private/usb_phy.h"
#include "sdkconfig.h"
#include "tusb.h"
#include "tusb_config.h"
#include "iot_button.h"
#include "iot_knob.h"
#ifdef CONFIG_ESP32_S3_USB_OTG
#include "bsp/esp-bsp.h"
#endif
#define TAG "DIAL"
#define GPIO_BUTTON 42
#define GPIO_KNOB_A 1
#define GPIO_KNOB_B 2
#define REPORT_ID 1
#define DIAL_R 0xC8
#define DIAL_L 0x38
#define DIAL_PRESS 0x01
#define DIAL_RELEASE 0x00
#define DIAL_R_F 0x14
#define DIAL_L_F 0xEC
static button_handle_t s_btn = 0;
static knob_handle_t s_knob = 0;
static usb_phy_handle_t s_phy_hdl;
static void usb_phy_init(void)
{
// Configure USB PHY
usb_phy_config_t phy_conf = {
.controller = USB_PHY_CTRL_OTG,
.otg_mode = USB_OTG_MODE_DEVICE,
.target = USB_PHY_TARGET_INT,
};
usb_new_phy(&phy_conf, &s_phy_hdl);
}
static void surface_dial_report(uint8_t key)
{
uint8_t _dial_report[2];
_dial_report[0] = key;
_dial_report[1] = 0;
if (key == DIAL_L || key == DIAL_L_F) {
_dial_report[1] = 0xff;
}
tud_hid_report(REPORT_ID, _dial_report, 2);
}
static void _button_press_down_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN: BUTTON_PRESS_DOWN");
surface_dial_report(DIAL_PRESS);
}
static void _button_press_up_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "BTN: BUTTON_PRESS_UP[%"PRIu32"]", iot_button_get_ticks_time((button_handle_t)arg));
surface_dial_report(DIAL_RELEASE);
}
static void _knob_right_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "KONB: KONB_RIGHT,count_value:%"PRId32"", iot_knob_get_count_value((button_handle_t)arg));
surface_dial_report(DIAL_L);
}
static void _knob_left_cb(void *arg, void *data)
{
ESP_LOGI(TAG, "KONB: KONB_LEFT,count_value:%"PRId32"", iot_knob_get_count_value((button_handle_t)arg));
surface_dial_report(DIAL_R);
}
static void _button_init(void)
{
button_config_t cfg = {
.type = BUTTON_TYPE_GPIO,
.long_press_time = 1000,
.short_press_time = 200,
.gpio_button_config = {
.gpio_num = GPIO_BUTTON,
.active_level = 0,
},
};
s_btn = iot_button_create(&cfg);
iot_button_register_cb(s_btn, BUTTON_PRESS_DOWN, _button_press_down_cb, NULL);
iot_button_register_cb(s_btn, BUTTON_PRESS_UP, _button_press_up_cb, NULL);
}
static void _knob_init(void)
{
knob_config_t cfg = {
.default_direction = 0,
.gpio_encoder_a = GPIO_KNOB_A,
.gpio_encoder_b = GPIO_KNOB_B,
};
s_knob = iot_knob_create(&cfg);
if (NULL == s_knob) {
ESP_LOGE(TAG, "knob create failed");
}
iot_knob_register_cb(s_knob, KNOB_LEFT, _knob_left_cb, NULL);
iot_knob_register_cb(s_knob, KNOB_RIGHT, _knob_right_cb, NULL);
}
void app_main(void)
{
#ifdef CONFIG_ESP32_S3_USB_OTG
bsp_usb_mode_select_device();
#endif
usb_phy_init();
_button_init();
_knob_init();
tusb_init();
size_t timeout_tick = pdMS_TO_TICKS(10);
while (1) {
tud_task();
vTaskDelay(timeout_tick);
}
}
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen)
{
// TODO not Implemented
(void) instance;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
return 0;
}
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize)
{
(void) instance;
(void) report_id;
(void) report_type;
(void) buffer;
(void) bufsize;
}

View File

@@ -0,0 +1,114 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifndef _TUSB_CONFIG_H_
#define _TUSB_CONFIG_H_
#ifdef __cplusplus
extern "C" {
#endif
//--------------------------------------------------------------------+
// Board Specific Configuration
//--------------------------------------------------------------------+
// RHPort number used for device can be defined by board.mk, default to port 0
#ifndef BOARD_TUD_RHPORT
#define BOARD_TUD_RHPORT 0
#endif
// RHPort max operational speed can defined by board.mk
#ifndef BOARD_TUD_MAX_SPEED
#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
#endif
//--------------------------------------------------------------------
// COMMON CONFIGURATION
//--------------------------------------------------------------------
// defined by compiler flags for flexibility
#ifndef CFG_TUSB_MCU
#error CFG_TUSB_MCU must be defined
#endif
#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE
#ifndef CFG_TUSB_OS
#define CFG_TUSB_OS OPT_OS_FREERTOS
#endif
// Espressif IDF requires "freertos/" prefix in include path
#if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3)
#define CFG_TUSB_OS_INC_PATH freertos/
#endif
#ifndef CFG_TUSB_DEBUG
#define CFG_TUSB_DEBUG 0
#endif
// Enable Device stack
#define CFG_TUD_ENABLED 1
// Default is max speed that hardware controller could support with on-chip PHY
#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
* Tinyusb use follows macros to declare transferring memory so that they can be put
* into those specific section.
* e.g
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
*/
#ifndef CFG_TUSB_MEM_SECTION
#define CFG_TUSB_MEM_SECTION
#endif
#ifndef CFG_TUSB_MEM_ALIGN
#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
#endif
//--------------------------------------------------------------------
// DEVICE CONFIGURATION
//--------------------------------------------------------------------
#ifndef CFG_TUD_ENDPOINT0_SIZE
#define CFG_TUD_ENDPOINT0_SIZE 64
#endif
//------------- CLASS -------------//
#define CFG_TUD_HID 1
#define CFG_TUD_CDC 0
#define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_EP_BUFSIZE 8
#ifdef __cplusplus
}
#endif
#endif /* _TUSB_CONFIG_H_ */

View File

@@ -0,0 +1,200 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include "tusb.h"
/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
* Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
*
* Auto ProductID layout's Bitmap:
* [MSB] HID | MSC | CDC [LSB]
*/
#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) )
#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
_PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) )
//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+
tusb_desc_device_t const desc_device = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0xCafe,
.idProduct = USB_PID,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
// Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to descriptor
uint8_t const *tud_descriptor_device_cb(void)
{
return (uint8_t const *) &desc_device;
}
//--------------------------------------------------------------------+
// HID Report Descriptor
//--------------------------------------------------------------------+
// Keyboard Report Descriptor Template
#define TUD_HID_REPORT_DESC_DIAL(...) \
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
HID_USAGE ( 0x0e ) ,\
HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
/* Report ID if any */\
__VA_ARGS__ \
HID_USAGE_PAGE ( HID_USAGE_PAGE_DIGITIZER ) ,\
HID_USAGE ( 0x21 ) ,\
HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) ,\
HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\
HID_USAGE ( 1 ) ,\
HID_REPORT_COUNT ( 1 ) ,\
HID_REPORT_SIZE ( 1 ) ,\
HID_LOGICAL_MIN ( 0 ) ,\
HID_LOGICAL_MAX ( 1 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_DIAL ) ,\
HID_REPORT_COUNT ( 1 ) ,\
HID_REPORT_SIZE ( 15 ) ,\
HID_UNIT_EXPONENT( 15 ) ,\
HID_UNIT ( 0x14 ) ,\
HID_PHYSICAL_MIN_N ( -3600, 2 ) ,\
HID_PHYSICAL_MAX_N ( 3600, 2 ) ,\
HID_LOGICAL_MIN_N ( -3600, 2 ) ,\
HID_LOGICAL_MAX_N ( 3600, 2 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE) ,\
HID_COLLECTION_END ,\
HID_COLLECTION_END \
uint8_t const desc_hid_report[] = {
TUD_HID_REPORT_DESC_DIAL(HID_REPORT_ID(1))
};
// Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf)
{
if (itf == 0) {
return desc_hid_report;
}
return NULL;
}
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
enum {
ITF_NUM_HID,
ITF_NUM_TOTAL
};
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN )
#define EPNUM_HID 0x81
uint8_t const desc_configuration[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0, 100),
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(ITF_NUM_HID, 4, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 10),
};
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
{
(void) index; // for multiple configurations
return desc_configuration;
}
//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+
// array of pointer to string descriptors
char const *string_desc_arr [] = {
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
"TinyUSB", // 1: Manufacturer
"TinyUSB Device", // 2: Product
"123456", // 3: Serials, should use chip ID
"Surface Dial", // 4: Interface 1 String
};
static uint16_t _desc_str[32];
// Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{
(void) langid;
uint8_t chr_count;
if (index == 0) {
memcpy(&_desc_str[1], string_desc_arr[0], 2);
chr_count = 1;
} else {
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) {
return NULL;
}
const char *str = string_desc_arr[index];
// Cap at max char
chr_count = (uint8_t) strlen(str);
if (chr_count > 31) {
chr_count = 31;
}
// Convert ASCII string into UTF-16
for (uint8_t i = 0; i < chr_count; i++) {
_desc_str[1 + i] = str[i];
}
}
// first byte is length (including header), second byte is string type
_desc_str[0] = (uint16_t)((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
return _desc_str;
}

View File

@@ -0,0 +1,2 @@
CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESP32_S3_USB_OTG=y

View File

@@ -0,0 +1,12 @@
dependencies:
cmake_utilities: 0.*
idf: '>=4.4.1'
description: Knob driver implemented through software pcnt
documentation: https://docs.espressif.com/projects/esp-iot-solution/en/latest/input_device/knob.html
issues: https://github.com/espressif/esp-iot-solution/issues
repository: git://github.com/espressif/esp-iot-solution.git
repository_info:
commit_sha: 17c072bdd192f5bdbe26976b50ae410ccdde1ff9
path: components/knob
url: https://github.com/espressif/esp-iot-solution/tree/master/components/knob
version: 1.0.0

View File

@@ -0,0 +1,138 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (* knob_cb_t)(void *, void *);
typedef void *knob_handle_t;
/**
* @brief Knob events
*
*/
typedef enum {
KNOB_LEFT = 0, /*!< EVENT: Rotate to the left */
KNOB_RIGHT, /*!< EVENT: Rotate to the right */
KNOB_H_LIM, /*!< EVENT: Count reaches maximum limit */
KNOB_L_LIM, /*!< EVENT: Count reaches the minimum limit */
KNOB_ZERO, /*!< EVENT: Count back to 0 */
KNOB_EVENT_MAX, /*!< EVENT: Number of events */
KNOB_NONE, /*!< EVENT: No event */
} knob_event_t;
/**
* @brief Knob config
*
*/
typedef struct {
uint8_t default_direction; /*!< 0:positive increase 1:negative increase */
uint8_t gpio_encoder_a; /*!< Encoder Pin A */
uint8_t gpio_encoder_b; /*!< Encoder Pin B */
bool enable_power_save; /*!< Enable power save mode */
} knob_config_t;
/**
* @brief create a knob
*
* @param config pointer of knob configuration
*
* @return A handle to the created knob
*/
knob_handle_t iot_knob_create(const knob_config_t *config);
/**
* @brief Delete a knob
*
* @param knob_handle A knob handle to delete
*
* @return
* - ESP_OK Success
* - ESP_FAIL Failure
*/
esp_err_t iot_knob_delete(knob_handle_t knob_handle);
/**
* @brief Register the knob event callback function
*
* @param knob_handle A knob handle to register
* @param event Knob event
* @param cb Callback function
* @param usr_data user data
*
* @return
* - ESP_OK Success
* - ESP_FAIL Failure
*/
esp_err_t iot_knob_register_cb(knob_handle_t knob_handle, knob_event_t event, knob_cb_t cb, void *usr_data);
/**
* @brief Unregister the knob event callback function
*
* @param knob_handle A knob handle to register
* @param event Knob event
*
* @return
* - ESP_OK Success
* - ESP_FAIL Failure
*/
esp_err_t iot_knob_unregister_cb(knob_handle_t knob_handle, knob_event_t event);
/**
* @brief Get knob event
*
* @param knob_handle A knob handle to register
* @return knob_event_t Knob event
*/
knob_event_t iot_knob_get_event(knob_handle_t knob_handle);
/**
* @brief Get knob count value
*
* @param knob_handle A knob handle to register
*
* @return int count_value
*/
int iot_knob_get_count_value(knob_handle_t knob_handle);
/**
* @brief Clear knob cout value to zero
*
* @param knob_handle A knob handle to register
*
* @return
* - ESP_OK Success
* - ESP_FAIL Failure
*/
esp_err_t iot_knob_clear_count_value(knob_handle_t knob_handle);
/**
* @brief resume knob timer, if knob timer is stopped. Make sure iot_knob_create() is called before calling this API.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE timer state is invalid.
*/
esp_err_t iot_knob_resume(void);
/**
* @brief stop knob timer, if knob timer is running. Make sure iot_knob_create() is called before calling this API.
*
* @return
* - ESP_OK on success
* - ESP_ERR_INVALID_STATE timer state is invalid
*/
esp_err_t iot_knob_stop(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initialize a GPIO pin for knob input.
*
* This function configures a specified GPIO pin as an input for knob control.
* It sets the pin mode, disables interrupts, and enables the pull-up resistor.
*
* @param gpio_num The GPIO number to be configured.
* @return
* - ESP_OK: Configuration successful.
* - ESP_ERR_INVALID_ARG: Parameter error.
* - ESP_FAIL: Configuration failed.
*/
esp_err_t knob_gpio_init(uint32_t gpio_num);
/**
* @brief Deinitialize a GPIO pin for knob input.
*
* This function resets the specified GPIO pin.
*
* @param gpio_num The GPIO number to be deinitialized.
* @return
* - ESP_OK: Reset successful.
* - ESP_FAIL: Reset failed.
*/
esp_err_t knob_gpio_deinit(uint32_t gpio_num);
/**
* @brief Get the level of a GPIO pin.
*
* This function returns the current level (high or low) of the specified GPIO pin.
*
* @param gpio_num The GPIO number to read the level from.
* @return The level of the GPIO pin (0 or 1).
*/
uint8_t knob_gpio_get_key_level(void *gpio_num);
/**
* @brief Initialize a GPIO pin with interrupt capability for knob input.
*
* This function configures a specified GPIO pin to trigger interrupts and installs
* an ISR service if not already installed. It adds an ISR handler for the GPIO pin.
*
* @param gpio_num The GPIO number to be configured.
* @param intr_type The type of interrupt to be configured.
* @param isr_handler The ISR handler function to be called on interrupt.
* @param args Arguments to be passed to the ISR handler.
* @return
* - ESP_OK: Configuration successful.
* - ESP_ERR_INVALID_ARG: Parameter error.
* - ESP_FAIL: Configuration failed.
*/
esp_err_t knob_gpio_init_intr(uint32_t gpio_num, gpio_int_type_t intr_type, gpio_isr_t isr_handler, void *args);
/**
* @brief Set the interrupt type for a GPIO pin.
*
* This function sets the interrupt type for the specified GPIO pin.
*
* @param gpio_num The GPIO number to configure.
* @param intr_type The type of interrupt to be configured.
* @return
* - ESP_OK: Configuration successful.
* - ESP_ERR_INVALID_ARG: Parameter error.
* - ESP_FAIL: Configuration failed.
*/
esp_err_t knob_gpio_set_intr(uint32_t gpio_num, gpio_int_type_t intr_type);
/**
* @brief Control the interrupt for a GPIO pin.
*
* This function enables or disables the interrupt for the specified GPIO pin.
*
* @param gpio_num The GPIO number to configure.
* @param enable Whether to enable or disable the interrupt.
* @return
* - ESP_OK: Configuration successful.
* - ESP_ERR_INVALID_ARG: Parameter error.
* - ESP_FAIL: Configuration failed.
*/
esp_err_t knob_gpio_intr_control(uint32_t gpio_num, bool enable);
/**
* @brief Control the wake up functionality of GPIO pins.
*
* This function enables or disables the wake up functionality from GPIO pins.
*
* @param enable Whether to enable or disable the wake up functionality.
* @param wake_up_level Level of the GPIO when triggered.
* @param enable Enable or disable the GPIO wakeup.
* @return
* - ESP_OK: Operation successful.
* - ESP_FAIL: Operation failed.
*/
esp_err_t knob_gpio_wake_up_control(uint32_t gpio_num, uint8_t wake_up_level, bool enable);
/**
* @brief Initialize wake up functionality for a GPIO pin.
*
* This function configures a specified GPIO pin to wake up the system from sleep
* based on the specified wake up level.
*
* @param gpio_num The GPIO number to configure.
* @param wake_up_level The level (0 or 1) to trigger wake up.
* @return
* - ESP_OK: Configuration successful.
* - ESP_ERR_INVALID_ARG: Parameter error.
* - ESP_FAIL: Configuration failed.
*/
esp_err_t knob_gpio_wake_up_init(uint32_t gpio_num, uint8_t wake_up_level);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,400 @@
/*
* SPDX-FileCopyrightText: 2016-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "driver/gpio.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "iot_knob.h"
#include "knob_gpio.h"
static const char *TAG = "Knob";
#define KNOB_CHECK(a, str, ret_val) \
if (!(a)) \
{ \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \
return (ret_val); \
}
#define KNOB_CHECK_GOTO(a, str, label) if(!(a)) { \
ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \
goto label; \
}
#define CALL_EVENT_CB(ev) if(knob->cb[ev])knob->cb[ev](knob, knob->usr_data[ev])
typedef enum {
KNOB_CHECK = -1, /*!< Knob state: check whether the knob is in the right position */
KNOB_READY = 0, /*!< Knob state: ready*/
KNOB_PHASE_A, /*!< Knob state: phase A arrives first */
KNOB_PHASE_B, /*!< Knob state: phase B arrives first */
} knob_state_t;
typedef struct Knob {
bool encoder_a_change; /*<! true means Encoder A phase Inverted*/
bool encoder_b_change; /*<! true means Encoder B phase Inverted*/
bool enable_power_save; /*<! Enable power save function */
uint8_t default_direction; /*!< 0:positive increase 1:negative increase */
knob_state_t state; /*!< knob state machine status */
uint8_t debounce_a_cnt; /*!< Encoder A phase debounce count */
uint8_t debounce_b_cnt; /*!< Encoder B phase debounce count */
uint8_t encoder_a_level; /*!< Encoder A phase current level */
uint8_t encoder_b_level; /*!< Encoder B phase current Level */
knob_event_t event; /*!< Current event */
uint16_t ticks; /*!< Timer interrupt count */
int count_value; /*!< Knob count */
uint8_t (*hal_knob_level)(void *hardware_data); /*!< Get current level */
void *encoder_a; /*!< Encoder A phase gpio number */
void *encoder_b; /*!< Encoder B phase gpio number */
void *usr_data[KNOB_EVENT_MAX]; /*!< User data for event */
knob_cb_t cb[KNOB_EVENT_MAX]; /*!< Event callback */
struct Knob *next; /*!< Next pointer */
} knob_dev_t;
static knob_dev_t *s_head_handle = NULL;
static esp_timer_handle_t s_knob_timer_handle;
static bool s_is_timer_running = false;
#define TICKS_INTERVAL CONFIG_KNOB_PERIOD_TIME_MS
#define DEBOUNCE_TICKS CONFIG_KNOB_DEBOUNCE_TICKS
#define HIGH_LIMIT CONFIG_KNOB_HIGH_LIMIT
#define LOW_LIMIT CONFIG_KNOB_LOW_LIMIT
static void knob_handler(knob_dev_t *knob)
{
uint8_t pha_value = knob->hal_knob_level(knob->encoder_a);
uint8_t phb_value = knob->hal_knob_level(knob->encoder_b);
if ((knob->state) > 0) {
knob->ticks++;
}
if (pha_value != knob->encoder_a_level) {
if (++(knob->debounce_a_cnt) >= DEBOUNCE_TICKS) {
knob->encoder_a_change = true;
knob->encoder_a_level = pha_value;
knob->debounce_a_cnt = 0;
}
} else {
knob->debounce_a_cnt = 0;
}
if (phb_value != knob->encoder_b_level) {
if (++(knob->debounce_b_cnt) >= DEBOUNCE_TICKS) {
knob->encoder_b_change = true;
knob->encoder_b_level = phb_value;
knob->debounce_b_cnt = 0;
}
} else {
knob->debounce_b_cnt = 0;
}
switch (knob->state) {
case KNOB_READY:
if (knob->encoder_a_change) {
knob->encoder_a_change = false;
knob->ticks = 0;
knob->state = KNOB_PHASE_A;
} else if (knob->encoder_b_change) {
knob->encoder_b_change = false;
knob->ticks = 0;
knob->state = KNOB_PHASE_B;
} else {
knob->event = KNOB_NONE;
}
break;
case KNOB_PHASE_A:
if (knob->encoder_b_change) {
knob->encoder_b_change = false;
if (knob->default_direction) {
knob->count_value--;
knob->event = KNOB_LEFT;
CALL_EVENT_CB(KNOB_LEFT);
if (knob->count_value <= LOW_LIMIT) {
knob->event = KNOB_L_LIM;
CALL_EVENT_CB(KNOB_L_LIM);
knob->count_value = 0;
} else if (knob->count_value == 0) {
knob->event = KNOB_ZERO;
CALL_EVENT_CB(KNOB_ZERO);
}
} else {
knob->count_value++;
knob->event = KNOB_RIGHT;
CALL_EVENT_CB(KNOB_RIGHT);
if (knob->count_value >= HIGH_LIMIT) {
knob->event = KNOB_H_LIM;
CALL_EVENT_CB(KNOB_H_LIM);
knob->count_value = 0;
} else if (knob->count_value == 0) {
knob->event = KNOB_ZERO;
CALL_EVENT_CB(KNOB_ZERO);
}
}
knob->ticks = 0;
knob->state = KNOB_READY;
} else if (knob->encoder_a_change) {
knob->encoder_a_change = false;
knob->ticks = 0;
knob->state = KNOB_READY;
} else {
knob->event = KNOB_NONE;
}
break;
case KNOB_PHASE_B:
if (knob->encoder_a_change) {
knob->encoder_a_change = false;
if (knob->default_direction) {
knob->count_value++;
knob->event = KNOB_RIGHT;
CALL_EVENT_CB(KNOB_RIGHT);
if (knob->count_value >= HIGH_LIMIT) {
knob->event = KNOB_H_LIM;
CALL_EVENT_CB(KNOB_H_LIM);
knob->count_value = 0;
} else if (knob->count_value == 0) {
knob->event = KNOB_ZERO;
CALL_EVENT_CB(KNOB_ZERO);
}
} else {
knob->count_value--;
knob->event = KNOB_LEFT;
CALL_EVENT_CB(KNOB_LEFT);
if (knob->count_value <= LOW_LIMIT) {
knob->event = KNOB_L_LIM;
CALL_EVENT_CB(KNOB_L_LIM);
knob->count_value = 0;
} else if (knob->count_value == 0) {
knob->event = KNOB_ZERO;
CALL_EVENT_CB(KNOB_ZERO);
}
}
knob->ticks = 0;
knob->state = KNOB_READY;
} else if (knob->encoder_b_change) {
knob->encoder_b_change = false;
knob->ticks = 0;
knob->state = KNOB_READY;
} else {
knob->event = KNOB_NONE;
}
break;
case KNOB_CHECK:
if (knob->encoder_a_level == knob->encoder_b_level) {
knob->state = KNOB_READY;
knob->encoder_a_change = false;
knob->encoder_b_change = false;
} else {
knob->event = KNOB_NONE;
}
break;
}
}
static void knob_cb(void *args)
{
knob_dev_t *target;
bool enter_power_save_flag = true;
for (target = s_head_handle; target; target = target->next) {
knob_handler(target);
if (!(target->enable_power_save && target->debounce_a_cnt == 0 && target->debounce_b_cnt == 0 && target->event == KNOB_NONE)) {
enter_power_save_flag = false;
}
}
if (enter_power_save_flag) {
/*!< Stop esp timer for power save */
if (s_is_timer_running) {
esp_timer_stop(s_knob_timer_handle);
s_is_timer_running = false;
}
for (target = s_head_handle; target; target = target->next) {
if (target->enable_power_save) {
knob_gpio_wake_up_control((uint32_t)target->encoder_a, !target->encoder_a_level, true);
knob_gpio_wake_up_control((uint32_t)target->encoder_b, !target->encoder_b_level, true);
knob_gpio_set_intr((uint32_t)target->encoder_a, !target->encoder_a_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
knob_gpio_set_intr((uint32_t)target->encoder_b, !target->encoder_b_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
knob_gpio_intr_control((uint32_t)(target->encoder_a), true);
knob_gpio_intr_control((uint32_t)(target->encoder_b), true);
}
}
}
}
static void IRAM_ATTR knob_power_save_isr_handler(void* arg)
{
if (!s_is_timer_running) {
esp_timer_start_periodic(s_knob_timer_handle, TICKS_INTERVAL * 1000U);
s_is_timer_running = true;
}
knob_gpio_intr_control((uint32_t)arg, false);
/*!< disable gpio wake up not need wake up level*/
knob_gpio_wake_up_control((uint32_t)arg, 0, false);
}
knob_handle_t iot_knob_create(const knob_config_t *config)
{
KNOB_CHECK(NULL != config, "config pointer can't be NULL!", NULL)
KNOB_CHECK(config->gpio_encoder_a != config->gpio_encoder_b, "encoder A can't be the same as encoder B", NULL);
knob_dev_t *knob = (knob_dev_t *)calloc(1, sizeof(knob_dev_t));
KNOB_CHECK(NULL != knob, "alloc knob failed", NULL);
esp_err_t ret = ESP_OK;
ret = knob_gpio_init(config->gpio_encoder_a);
KNOB_CHECK(ESP_OK == ret, "encoder A gpio init failed", NULL);
ret = knob_gpio_init(config->gpio_encoder_b);
KNOB_CHECK_GOTO(ESP_OK == ret, "encoder B gpio init failed", _encoder_deinit);
knob->default_direction = config->default_direction;
knob->hal_knob_level = knob_gpio_get_key_level;
knob->encoder_a = (void *)(long)config->gpio_encoder_a;
knob->encoder_b = (void *)(long)config->gpio_encoder_b;
knob->encoder_a_level = knob->hal_knob_level(knob->encoder_a);
knob->encoder_b_level = knob->hal_knob_level(knob->encoder_b);
if (config->enable_power_save) {
knob->enable_power_save = config->enable_power_save;
knob_gpio_init_intr(config->gpio_encoder_a, !knob->encoder_a_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL, knob_power_save_isr_handler, knob->encoder_a);
knob_gpio_init_intr(config->gpio_encoder_b, !knob->encoder_b_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL, knob_power_save_isr_handler, knob->encoder_b);
ret = knob_gpio_wake_up_init(config->gpio_encoder_a, !knob->encoder_a_level);
KNOB_CHECK_GOTO(ESP_OK == ret, "encoder A wake up gpio init failed", _encoder_deinit);
ret = knob_gpio_wake_up_init(config->gpio_encoder_b, !knob->encoder_b_level);
KNOB_CHECK_GOTO(ESP_OK == ret, "encoder B wake up gpio init failed", _encoder_deinit);
}
knob->state = KNOB_CHECK;
knob->event = KNOB_NONE;
knob->next = s_head_handle;
s_head_handle = knob;
if (!s_knob_timer_handle) {
esp_timer_create_args_t knob_timer = {0};
knob_timer.arg = NULL;
knob_timer.callback = knob_cb;
knob_timer.dispatch_method = ESP_TIMER_TASK;
knob_timer.name = "knob_timer";
esp_timer_create(&knob_timer, &s_knob_timer_handle);
}
if (!knob->enable_power_save && !s_is_timer_running) {
esp_timer_start_periodic(s_knob_timer_handle, TICKS_INTERVAL * 1000U);
s_is_timer_running = true;
}
ESP_LOGI(TAG, "Iot Knob Config Succeed, encoder A:%d, encoder B:%d, direction:%d, Version: %d.%d.%d", config->gpio_encoder_a, config->gpio_encoder_b, config->default_direction, KNOB_VER_MAJOR, KNOB_VER_MINOR, KNOB_VER_PATCH);
return (knob_handle_t)knob;
_encoder_deinit:
knob_gpio_deinit(config->gpio_encoder_b);
knob_gpio_deinit(config->gpio_encoder_a);
return NULL;
}
esp_err_t iot_knob_delete(knob_handle_t knob_handle)
{
esp_err_t ret = ESP_OK;
KNOB_CHECK(NULL != knob_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
knob_dev_t *knob = (knob_dev_t *)knob_handle;
ret = knob_gpio_deinit((int)(knob->usr_data));
KNOB_CHECK(ESP_OK == ret, "knob deinit failed", ESP_FAIL);
knob_dev_t **curr;
for (curr = &s_head_handle; *curr;) {
knob_dev_t *entry = *curr;
if (entry == knob) {
*curr = entry->next;
free(entry);
} else {
curr = &entry->next;
}
}
uint16_t number = 0;
knob_dev_t *target = s_head_handle;
while (target) {
target = target->next;
number++;
}
ESP_LOGD(TAG, "remain knob number=%d", number);
if (0 == number && s_is_timer_running) { /**< if all knob is deleted, stop the timer */
esp_timer_stop(s_knob_timer_handle);
esp_timer_delete(s_knob_timer_handle);
s_is_timer_running = false;
}
return ESP_OK;
}
esp_err_t iot_knob_register_cb(knob_handle_t knob_handle, knob_event_t event, knob_cb_t cb, void *usr_data)
{
KNOB_CHECK(NULL != knob_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
KNOB_CHECK(event < KNOB_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG);
knob_dev_t *knob = (knob_dev_t *) knob_handle;
knob->cb[event] = cb;
knob->usr_data[event] = usr_data;
return ESP_OK;
}
esp_err_t iot_knob_unregister_cb(knob_handle_t knob_handle, knob_event_t event)
{
KNOB_CHECK(NULL != knob_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
KNOB_CHECK(event < KNOB_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG);
knob_dev_t *knob = (knob_dev_t *) knob_handle;
knob->cb[event] = NULL;
knob->usr_data[event] = NULL;
return ESP_OK;
}
knob_event_t iot_knob_get_event(knob_handle_t knob_handle)
{
KNOB_CHECK(NULL != knob_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
knob_dev_t *knob = (knob_dev_t *) knob_handle;
return knob->event;
}
int iot_knob_get_count_value(knob_handle_t knob_handle)
{
KNOB_CHECK(NULL != knob_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
knob_dev_t *knob = (knob_dev_t *) knob_handle;
return knob->count_value;
}
esp_err_t iot_knob_clear_count_value(knob_handle_t knob_handle)
{
KNOB_CHECK(NULL != knob_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG);
knob_dev_t *knob = (knob_dev_t *) knob_handle;
knob->count_value = 0;
return ESP_OK;
}
esp_err_t iot_knob_resume(void)
{
KNOB_CHECK(s_knob_timer_handle, "knob timer handle is invalid", ESP_ERR_INVALID_STATE);
KNOB_CHECK(!s_is_timer_running, "knob timer is already running", ESP_ERR_INVALID_STATE);
esp_err_t err = esp_timer_start_periodic(s_knob_timer_handle, TICKS_INTERVAL * 1000U);
KNOB_CHECK(ESP_OK == err, "knob timer start failed", ESP_FAIL);
s_is_timer_running = true;
return ESP_OK;
}
esp_err_t iot_knob_stop(void)
{
KNOB_CHECK(s_knob_timer_handle, "knob timer handle is invalid", ESP_ERR_INVALID_STATE);
KNOB_CHECK(s_is_timer_running, "knob timer is not running", ESP_ERR_INVALID_STATE);
esp_err_t err = esp_timer_stop(s_knob_timer_handle);
KNOB_CHECK(ESP_OK == err, "knob timer stop failed", ESP_FAIL);
s_is_timer_running = false;
return ESP_OK;
}

View File

@@ -0,0 +1,118 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_sleep.h"
#include "esp_check.h"
#include "driver/gpio.h"
#include "knob_gpio.h"
static const char *TAG = "knob gpio";
esp_err_t knob_gpio_init(uint32_t gpio_num)
{
gpio_config_t gpio_cfg = {
.pin_bit_mask = (1ULL << gpio_num),
.mode = GPIO_MODE_INPUT,
.intr_type = GPIO_INTR_DISABLE,
.pull_up_en = 1,
};
esp_err_t ret = gpio_config(&gpio_cfg);
return ret;
}
esp_err_t knob_gpio_deinit(uint32_t gpio_num)
{
return gpio_reset_pin(gpio_num);
}
uint8_t knob_gpio_get_key_level(void *gpio_num)
{
return (uint8_t)gpio_get_level((uint32_t)gpio_num);
}
esp_err_t knob_gpio_init_intr(uint32_t gpio_num, gpio_int_type_t intr_type, gpio_isr_t isr_handler, void *args)
{
static bool isr_service_installed = false;
gpio_set_intr_type(gpio_num, intr_type);
if (!isr_service_installed) {
gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
isr_service_installed = true;
}
gpio_isr_handler_add(gpio_num, isr_handler, args);
return ESP_OK;
}
esp_err_t knob_gpio_set_intr(uint32_t gpio_num, gpio_int_type_t intr_type)
{
return gpio_set_intr_type(gpio_num, intr_type);
}
esp_err_t knob_gpio_intr_control(uint32_t gpio_num, bool enable)
{
if (enable) {
gpio_intr_enable(gpio_num);
} else {
gpio_intr_disable(gpio_num);
}
return ESP_OK;
}
esp_err_t knob_gpio_wake_up_control(uint32_t gpio_num, uint8_t wake_up_level, bool enable)
{
esp_err_t ret;
if (enable) {
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
#if SOC_PM_SUPPORT_EXT1_WAKEUP
ret = esp_sleep_enable_ext1_wakeup_io((1ULL << gpio_num), wake_up_level == 0 ? ESP_EXT1_WAKEUP_ANY_LOW : ESP_EXT1_WAKEUP_ANY_HIGH);
#else
/*!< Not support etc: esp32c2, esp32c3. Target must support ext1 wakeup */
ret = ESP_FAIL;
ESP_RETURN_ON_FALSE(ret == ESP_OK, ESP_FAIL, TAG, "Target must support ext1 wakeup");
#endif
#endif
/* Enable wake up from GPIO */
ret = gpio_wakeup_enable(gpio_num, wake_up_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
} else {
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
#if SOC_PM_SUPPORT_EXT1_WAKEUP
ret = esp_sleep_disable_ext1_wakeup_io(1ULL << gpio_num);
#endif
#endif
ret = gpio_wakeup_disable(gpio_num);
}
return ret;
}
esp_err_t knob_gpio_wake_up_init(uint32_t gpio_num, uint8_t wake_up_level)
{
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
if (!esp_sleep_is_valid_wakeup_gpio(gpio_num)) {
ESP_LOGE(TAG, "GPIO %ld is not a valid wakeup source under CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP",
gpio_num);
return ESP_FAIL;
}
gpio_hold_en(gpio_num);
#endif
/* Enable wake up from GPIO */
esp_err_t ret = gpio_wakeup_enable(gpio_num, wake_up_level == 0 ? GPIO_INTR_LOW_LEVEL : GPIO_INTR_HIGH_LEVEL);
ESP_RETURN_ON_FALSE(ret == ESP_OK, ESP_FAIL, TAG, "Enable gpio wakeup failed");
#if CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP
#if SOC_PM_SUPPORT_EXT1_WAKEUP
ret = esp_sleep_enable_ext1_wakeup_io((1ULL << gpio_num), wake_up_level == 0 ? ESP_EXT1_WAKEUP_ANY_LOW : ESP_EXT1_WAKEUP_ANY_HIGH);
#else
/*!< Not support etc: esp32c2, esp32c3. Target must support ext1 wakeup */
ret = ESP_FAIL;
ESP_RETURN_ON_FALSE(ret == ESP_OK, ESP_FAIL, TAG, "Target must support ext1 wakeup");
#endif
#else
ret = esp_sleep_enable_gpio_wakeup();
ESP_RETURN_ON_FALSE(ret == ESP_OK, ESP_FAIL, TAG, "esp sleep enable gpio wakeup failed");
#endif
return ESP_OK;
}

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@@ -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}/tools/unit-test-app/components"
"../../knob")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(usb_stream_test)

View File

@@ -0,0 +1,3 @@
idf_component_register(SRC_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES unity test_utils knob)

View File

@@ -0,0 +1,149 @@
/*
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/timers.h"
#include "esp_idf_version.h"
#include "esp_log.h"
#include "unity.h"
#include "iot_knob.h"
#include "sdkconfig.h"
static const char *TAG = "KNOB TEST";
#define TEST_MEMORY_LEAK_THRESHOLD (-400)
#define GPIO_KNOB_A 1
#define GPIO_KNOB_B 2
#define KNOB_NUM 3
static knob_handle_t s_knob[KNOB_NUM] = {0};
static int get_knob_index(knob_handle_t knob)
{
for (size_t i = 0; i < KNOB_NUM; i++) {
if (knob == s_knob[i]) {
return i;
}
}
return -1;
}
static void knob_left_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(KNOB_LEFT, iot_knob_get_event(arg));
ESP_LOGI(TAG, "KNOB %d: KNOB_LEFT Count is %d usr_data: %s", get_knob_index((knob_handle_t)arg), iot_knob_get_count_value((knob_handle_t)arg), (char *)data);
}
static void knob_right_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(KNOB_RIGHT, iot_knob_get_event(arg));
ESP_LOGI(TAG, "KNOB%d: KNOB_RIGHT Count is %d usr_data: %s", get_knob_index((knob_handle_t)arg), iot_knob_get_count_value((knob_handle_t)arg), (char *)data);
}
static void knob_h_lim_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(KNOB_H_LIM, iot_knob_get_event(arg));
ESP_LOGI(TAG, "KNOB%d: KNOB_H_LIM usr_data: %s", get_knob_index((knob_handle_t)arg), (char *)data);
}
static void knob_l_lim_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(KNOB_L_LIM, iot_knob_get_event(arg));
ESP_LOGI(TAG, "KNOB%d: KNOB_L_LIM usr_data: %s", get_knob_index((knob_handle_t)arg), (char *)data);
}
static void knob_zero_cb(void *arg, void *data)
{
TEST_ASSERT_EQUAL_HEX(KNOB_ZERO, iot_knob_get_event(arg));
ESP_LOGI(TAG, "KNOB%d: KNOB_ZERO usr_data: %s", get_knob_index((knob_handle_t)arg), (char *)data);
}
static const char *knob_name[] = {"knob_0",
"knob_1",
"knob_2"
};
TEST_CASE("three knob test", "[knob][iot]")
{
knob_config_t *cfg = calloc(1, sizeof(knob_config_t));
cfg->default_direction = 0;
cfg->gpio_encoder_a = GPIO_KNOB_A;
cfg->gpio_encoder_b = GPIO_KNOB_B;
for (int i = 0; i < KNOB_NUM; i++) {
s_knob[i] = iot_knob_create(cfg);
TEST_ASSERT_NOT_NULL(s_knob[i]);
iot_knob_register_cb(s_knob[i], KNOB_LEFT, knob_left_cb, (void *)knob_name[i]);
iot_knob_register_cb(s_knob[i], KNOB_RIGHT, knob_right_cb, (void *)knob_name[i]);
iot_knob_register_cb(s_knob[i], KNOB_H_LIM, knob_h_lim_cb, (void *)knob_name[i]);
iot_knob_register_cb(s_knob[i], KNOB_L_LIM, knob_l_lim_cb, (void *)knob_name[i]);
iot_knob_register_cb(s_knob[i], KNOB_ZERO, knob_zero_cb, (void *)knob_name[i]);
}
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
for (int i = 0; i < KNOB_NUM; i++) {
iot_knob_delete(s_knob[i]);
}
}
TEST_CASE("one knob test", "[knob][iot]")
{
knob_config_t *cfg = calloc(1, sizeof(knob_config_t));
cfg->default_direction = 0;
cfg->gpio_encoder_a = GPIO_KNOB_A;
cfg->gpio_encoder_b = GPIO_KNOB_B;
s_knob[0] = iot_knob_create(cfg);
TEST_ASSERT_NOT_NULL(s_knob[0]);
iot_knob_register_cb(s_knob[0], KNOB_LEFT, knob_left_cb, (void *)knob_name[0]);
iot_knob_register_cb(s_knob[0], KNOB_RIGHT, knob_right_cb, (void *)knob_name[0]);
iot_knob_register_cb(s_knob[0], KNOB_H_LIM, knob_h_lim_cb, (void *)knob_name[0]);
iot_knob_register_cb(s_knob[0], KNOB_L_LIM, knob_l_lim_cb, (void *)knob_name[0]);
iot_knob_register_cb(s_knob[0], KNOB_ZERO, knob_zero_cb, (void *)knob_name[0]);
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000));
}
iot_knob_delete(s_knob[0]);
}
static size_t before_free_8bit;
static size_t before_free_32bit;
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}
void app_main(void)
{
printf("IOT KNOB TEST\n");
unity_run_menu();
}

View File

@@ -0,0 +1,9 @@
# For IDF 5.0
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT_EN=n
# For IDF4.4
CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP_TASK_WDT=n