From 153e819e8ac3bb736562deb91b705a134cb23310 Mon Sep 17 00:00:00 2001 From: morris Date: Wed, 16 Sep 2020 13:50:56 +0800 Subject: [PATCH] pcnt: add rotary encoder example --- components/driver/include/driver/pcnt.h | 13 ++ components/driver/pcnt.c | 17 +- components/hal/include/hal/pcnt_hal.h | 9 + docs/en/api-reference/peripherals/pcnt.rst | 3 +- examples/peripherals/pcnt/main/CMakeLists.txt | 2 - .../{ => pulse_count_event}/CMakeLists.txt | 2 +- .../pcnt/{ => pulse_count_event}/Makefile | 2 +- .../pcnt/{ => pulse_count_event}/README.md | 7 +- .../pulse_count_event/main/CMakeLists.txt | 2 + .../{ => pulse_count_event}/main/component.mk | 0 .../main/pcnt_event_example_main.c} | 0 .../pcnt/rotary_encoder/CMakeLists.txt | 6 + .../peripherals/pcnt/rotary_encoder/Makefile | 9 + .../peripherals/pcnt/rotary_encoder/README.md | 75 ++++++++ .../components/rotary_encoder/CMakeLists.txt | 8 + .../components/rotary_encoder/component.mk | 3 + .../rotary_encoder/include/rotary_encoder.h | 127 ++++++++++++++ .../src/rotary_encoder_pcnt_ec11.c | 164 ++++++++++++++++++ .../pcnt/rotary_encoder/main/CMakeLists.txt | 2 + .../pcnt/rotary_encoder/main/component.mk | 3 + .../main/rotary_encoder_example_main.c | 37 ++++ 21 files changed, 480 insertions(+), 11 deletions(-) delete mode 100644 examples/peripherals/pcnt/main/CMakeLists.txt rename examples/peripherals/pcnt/{ => pulse_count_event}/CMakeLists.txt (91%) rename examples/peripherals/pcnt/{ => pulse_count_event}/Makefile (85%) rename examples/peripherals/pcnt/{ => pulse_count_event}/README.md (93%) create mode 100644 examples/peripherals/pcnt/pulse_count_event/main/CMakeLists.txt rename examples/peripherals/pcnt/{ => pulse_count_event}/main/component.mk (100%) rename examples/peripherals/pcnt/{main/pcnt_example_main.c => pulse_count_event/main/pcnt_event_example_main.c} (100%) create mode 100644 examples/peripherals/pcnt/rotary_encoder/CMakeLists.txt create mode 100644 examples/peripherals/pcnt/rotary_encoder/Makefile create mode 100644 examples/peripherals/pcnt/rotary_encoder/README.md create mode 100644 examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/CMakeLists.txt create mode 100644 examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/component.mk create mode 100644 examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/include/rotary_encoder.h create mode 100644 examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/src/rotary_encoder_pcnt_ec11.c create mode 100644 examples/peripherals/pcnt/rotary_encoder/main/CMakeLists.txt create mode 100644 examples/peripherals/pcnt/rotary_encoder/main/component.mk create mode 100644 examples/peripherals/pcnt/rotary_encoder/main/rotary_encoder_example_main.c diff --git a/components/driver/include/driver/pcnt.h b/components/driver/include/driver/pcnt.h index 00f3626a63..6236e5407c 100644 --- a/components/driver/include/driver/pcnt.h +++ b/components/driver/include/driver/pcnt.h @@ -175,6 +175,19 @@ esp_err_t pcnt_set_event_value(pcnt_unit_t unit, pcnt_evt_type_t evt_type, int16 */ esp_err_t pcnt_get_event_value(pcnt_unit_t unit, pcnt_evt_type_t evt_type, int16_t *value); +/** + * @brief Get PCNT event status of PCNT unit + * + * @param unit PCNT unit number + * @param status Pointer to accept event status word + * @return + * + * - ESP_OK Success + * - ESP_ERR_INVALID_STATE pcnt driver has not been initialized + * - ESP_ERR_INVALID_ARG Parameter error + */ +esp_err_t pcnt_get_event_status(pcnt_unit_t unit, uint32_t *status); + /** * @brief Unregister PCNT interrupt handler (registered by pcnt_isr_register), the handler is an ISR. * The handler will be attached to the same CPU core that this function is running on. diff --git a/components/driver/pcnt.c b/components/driver/pcnt.c index dbfdb8d00c..2c637a2486 100644 --- a/components/driver/pcnt.c +++ b/components/driver/pcnt.c @@ -201,6 +201,16 @@ static inline esp_err_t _pcnt_get_event_value(pcnt_port_t pcnt_port, pcnt_unit_t return ESP_OK; } +static inline esp_err_t _pcnt_get_event_status(pcnt_port_t pcnt_port, pcnt_unit_t unit, uint32_t *status) +{ + PCNT_OBJ_CHECK(pcnt_port); + PCNT_CHECK(unit < PCNT_UNIT_MAX, PCNT_UNIT_ERR_STR, ESP_ERR_INVALID_ARG); + PCNT_CHECK(status != NULL, PCNT_ADDRESS_ERR_STR, ESP_ERR_INVALID_ARG); + + *status = pcnt_hal_get_event_status(&(p_pcnt_obj[pcnt_port]->hal), unit); + return ESP_OK; +} + static inline esp_err_t _pcnt_set_filter_value(pcnt_port_t pcnt_port, pcnt_unit_t unit, uint16_t filter_val) { PCNT_OBJ_CHECK(pcnt_port); @@ -278,7 +288,7 @@ static void IRAM_ATTR pcnt_intr_service(void *arg) pcnt_port_t pcnt_port = (pcnt_port_t)arg; pcnt_hal_get_intr_status(&(p_pcnt_obj[pcnt_port]->hal), &status); pcnt_hal_clear_intr_status(&(p_pcnt_obj[pcnt_port]->hal), status); - + while (status) { int unit = __builtin_ffs(status) - 1; status &= ~(1 << unit); @@ -459,6 +469,11 @@ esp_err_t pcnt_get_event_value(pcnt_unit_t unit, pcnt_evt_type_t evt_type, int16 return _pcnt_get_event_value(PCNT_PORT_0, unit, evt_type, value); } +esp_err_t pcnt_get_event_status(pcnt_unit_t unit, uint32_t *status) +{ + return _pcnt_get_event_status(PCNT_PORT_0, unit, status); +} + esp_err_t pcnt_set_filter_value(pcnt_unit_t unit, uint16_t filter_val) { return _pcnt_set_filter_value(PCNT_PORT_0, unit, filter_val); diff --git a/components/hal/include/hal/pcnt_hal.h b/components/hal/include/hal/pcnt_hal.h index 2a5972d8d7..416af3fa85 100644 --- a/components/hal/include/hal/pcnt_hal.h +++ b/components/hal/include/hal/pcnt_hal.h @@ -164,6 +164,15 @@ typedef struct { */ #define pcnt_hal_get_event_value(hal, unit, evt_type, value) pcnt_ll_get_event_value((hal)->dev, unit, evt_type, value) +/** + * @brief Get PCNT event status + * + * @param hal Context of the HAL layer + * @param unit PCNT unit number + * @return event status word + */ +#define pcnt_hal_get_event_status(hal, unit) pcnt_ll_get_event_status((hal)->dev, unit) + /** * @brief Set PCNT filter value * diff --git a/docs/en/api-reference/peripherals/pcnt.rst b/docs/en/api-reference/peripherals/pcnt.rst index a5f0b3c606..723c5e268e 100644 --- a/docs/en/api-reference/peripherals/pcnt.rst +++ b/docs/en/api-reference/peripherals/pcnt.rst @@ -91,7 +91,8 @@ In order to check what are the threshold values currently set, use function :cpp Application Example ------------------- -Pulse counter with control signal and event interrupt example: :example:`peripherals/pcnt`. +* Pulse counter with control signal and event interrupt example: :example:`peripherals/pcnt/pulse_count_event`. +* Parse the signal generated from rotary encoder: :example:`peripherals/pcnt/rotary_encoder`. API Reference diff --git a/examples/peripherals/pcnt/main/CMakeLists.txt b/examples/peripherals/pcnt/main/CMakeLists.txt deleted file mode 100644 index 11ace9793f..0000000000 --- a/examples/peripherals/pcnt/main/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -idf_component_register(SRCS "pcnt_example_main.c" - INCLUDE_DIRS ".") \ No newline at end of file diff --git a/examples/peripherals/pcnt/CMakeLists.txt b/examples/peripherals/pcnt/pulse_count_event/CMakeLists.txt similarity index 91% rename from examples/peripherals/pcnt/CMakeLists.txt rename to examples/peripherals/pcnt/pulse_count_event/CMakeLists.txt index 8f9d35a794..c6f45070a1 100644 --- a/examples/peripherals/pcnt/CMakeLists.txt +++ b/examples/peripherals/pcnt/pulse_count_event/CMakeLists.txt @@ -3,4 +3,4 @@ cmake_minimum_required(VERSION 3.5) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(pcnt) +project(pcnt_event) diff --git a/examples/peripherals/pcnt/Makefile b/examples/peripherals/pcnt/pulse_count_event/Makefile similarity index 85% rename from examples/peripherals/pcnt/Makefile rename to examples/peripherals/pcnt/pulse_count_event/Makefile index 5ae4b0b53f..d08ed7033b 100644 --- a/examples/peripherals/pcnt/Makefile +++ b/examples/peripherals/pcnt/pulse_count_event/Makefile @@ -3,7 +3,7 @@ # project subdirectory. # -PROJECT_NAME := pcnt +PROJECT_NAME := pcnt_event include $(IDF_PATH)/make/project.mk diff --git a/examples/peripherals/pcnt/README.md b/examples/peripherals/pcnt/pulse_count_event/README.md similarity index 93% rename from examples/peripherals/pcnt/README.md rename to examples/peripherals/pcnt/pulse_count_event/README.md index 3464d8177f..d640a24c8b 100644 --- a/examples/peripherals/pcnt/README.md +++ b/examples/peripherals/pcnt/pulse_count_event/README.md @@ -1,7 +1,4 @@ -| Supported Targets | ESP32 | -| ----------------- | ----- | - -# PCNT Example +# Pulse Count Event Example (See the README.md file in the upper level 'examples' directory for more information about examples.) @@ -18,7 +15,7 @@ Pin connection: * GPIO4 is the default output GPIO of the 1 Hz pulse generator. * GPIO18 is the default pulse input GPIO. We need to short GPIO4 and GPIO18. -* GPIO5 is the default control signal, which can be left floating with internal pull up, or connected to Ground (If GPIO5 is left floating, the value of counter increases with the rising edges of the PWM pulses. If GPIO15 is connected to Ground, the value decreases). +* GPIO5 is the default control signal, which can be left floating with internal pull up, or connected to Ground (If GPIO5 is left floating, the value of counter increases with the rising edges of the PWM pulses. If GPIO5 is connected to Ground, the value decreases). ### Configure the project diff --git a/examples/peripherals/pcnt/pulse_count_event/main/CMakeLists.txt b/examples/peripherals/pcnt/pulse_count_event/main/CMakeLists.txt new file mode 100644 index 0000000000..1fbf1d87c1 --- /dev/null +++ b/examples/peripherals/pcnt/pulse_count_event/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "pcnt_event_example_main.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/examples/peripherals/pcnt/main/component.mk b/examples/peripherals/pcnt/pulse_count_event/main/component.mk similarity index 100% rename from examples/peripherals/pcnt/main/component.mk rename to examples/peripherals/pcnt/pulse_count_event/main/component.mk diff --git a/examples/peripherals/pcnt/main/pcnt_example_main.c b/examples/peripherals/pcnt/pulse_count_event/main/pcnt_event_example_main.c similarity index 100% rename from examples/peripherals/pcnt/main/pcnt_example_main.c rename to examples/peripherals/pcnt/pulse_count_event/main/pcnt_event_example_main.c diff --git a/examples/peripherals/pcnt/rotary_encoder/CMakeLists.txt b/examples/peripherals/pcnt/rotary_encoder/CMakeLists.txt new file mode 100644 index 0000000000..d5bb198a6d --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/CMakeLists.txt @@ -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(rotary_encoder) diff --git a/examples/peripherals/pcnt/rotary_encoder/Makefile b/examples/peripherals/pcnt/rotary_encoder/Makefile new file mode 100644 index 0000000000..5a74869c3f --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := rotary_encoder + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/pcnt/rotary_encoder/README.md b/examples/peripherals/pcnt/rotary_encoder/README.md new file mode 100644 index 0000000000..7f963c9589 --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/README.md @@ -0,0 +1,75 @@ +# Rotary Encoder Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +The PCNT peripheral is designed to count the number of rising and/or falling edges of an input signal. Each PCNT unit has two channels, which makes it possible to extract more information from two input signals than only one signal. +This example shows how to make use of the HW features to decode the differential signals generated from a common rotary encoder -- [EC11](https://tech.alpsalpine.com/prod/e/html/encoder/incremental/ec11/ec11_list.html). + +The signals a rotary encoder produces (and what can be handled by this example) are based on a 2-bit gray code available on 2 digital data signal lines. The typical encoders use 3 output pins: 2 for the signals and one for the common signal usually GND. + +Typical signals: + +``` +A +-----+ +-----+ +-----+ + | | | | + | | | | + +-----+ +-----+ +B +-----+ +-----+ +-----+ + | | | | + | | | | + +-----+ +-----+ + + +---------------------------------------> + CW direction +``` + +## How to Use Example + +### Hardware Required + +* An ESP development board +* EC11 rotary encoder (or other encoders which can produce quadrature waveforms) + +Connection : + +``` + +--------+ +---------------------------------+ + | | | | + | A +--------------+ GPIO_A (internal pull up) | + | | | | ++-------+ | | | +| | | GND +--------------+ GND | ++-------+ | | | + | | | | + | B +--------------+ GPIO_B (internal pull up) | + | | | | + +--------+ +---------------------------------+ +``` + +### Build and Flash + +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](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +``` +I (181323) example: Encoder value: 0 +I (182323) example: Encoder value: 0 +I (183323) example: Encoder value: -12 +I (184323) example: Encoder value: -18 +I (185323) example: Encoder value: -24 +I (188323) example: Encoder value: 4 +I (189323) example: Encoder value: 8 +I (190323) example: Encoder value: 8 +I (191323) example: Encoder value: 8 +``` + +This example enables the 4X mode to parse the rotary signals, which means, each complete rotary step will result PCNT counter to increase/decrease by 4, depending on the direction of rotation. + +## Troubleshooting + +For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/CMakeLists.txt b/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/CMakeLists.txt new file mode 100644 index 0000000000..7d9a499df6 --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/CMakeLists.txt @@ -0,0 +1,8 @@ +set(component_srcs "src/rotary_encoder_pcnt_ec11.c") + +idf_component_register(SRCS "${component_srcs}" + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "" + PRIV_REQUIRES "driver" + REQUIRES "") + diff --git a/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/component.mk b/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/component.mk new file mode 100644 index 0000000000..24cab8b637 --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/component.mk @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS := include + +COMPONENT_SRCDIRS := src diff --git a/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/include/rotary_encoder.h b/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/include/rotary_encoder.h new file mode 100644 index 0000000000..46cff3eb92 --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/include/rotary_encoder.h @@ -0,0 +1,127 @@ +// 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 __cplusplus +extern "C" { +#endif + +#include "esp_err.h" + +/** + * @brief Type of Rotary underlying device handle + * + */ +typedef void *rotary_encoder_dev_t; + +/** + * @brief Type of rotary encoder configuration + * + */ +typedef struct { + rotary_encoder_dev_t dev; /*!< Underlying device handle */ + int phase_a_gpio_num; /*!< Phase A GPIO number */ + int phase_b_gpio_num; /*!< Phase B GPIO number */ + int flags; /*!< Extra flags */ +} rotary_encoder_config_t; + +/** + * @brief Default rotary encoder configuration + * + */ +#define ROTARY_ENCODER_DEFAULT_CONFIG(dev_hdl, gpio_a, gpio_b) \ + { \ + .dev = dev_hdl, \ + .phase_a_gpio_num = gpio_a, \ + .phase_b_gpio_num = gpio_b, \ + .flags = 0, \ + } + +/** + * @brief Type of rotary encoder handle + * + */ +typedef struct rotary_encoder_t rotary_encoder_t; + +/** + * @brief Rotary encoder interface + * + */ +struct rotary_encoder_t { + /** + * @brief Filter out glitch from input signals + * + * @param encoder Rotary encoder handle + * @param max_glitch_us Maximum glitch duration, in us + * @return + * - ESP_OK: Set glitch filter successfully + * - ESP_FAIL: Set glitch filter failed because of other error + */ + esp_err_t (*set_glitch_filter)(rotary_encoder_t *encoder, uint32_t max_glitch_us); + + /** + * @brief Start rotary encoder + * + * @param encoder Rotary encoder handle + * @return + * - ESP_OK: Start rotary encoder successfully + * - ESP_FAIL: Start rotary encoder failed because of other error + */ + esp_err_t (*start)(rotary_encoder_t *encoder); + + /** + * @brief Stop rotary encoder + * + * @param encoder Rotary encoder handle + * @return + * - ESP_OK: Stop rotary encoder successfully + * - ESP_FAIL: Stop rotary encoder failed because of other error + */ + esp_err_t (*stop)(rotary_encoder_t *encoder); + + /** + * @brief Recycle rotary encoder memory + * + * @param encoder Rotary encoder handle + * @return + * - ESP_OK: Recycle rotary encoder memory successfully + * - ESP_FAIL: rotary encoder memory failed because of other error + */ + esp_err_t (*del)(rotary_encoder_t *encoder); + + /** + * @brief Get rotary encoder counter value + * + * @param encoder Rotary encoder handle + * @return Current counter value (the sign indicates the direction of rotation) + */ + int (*get_counter_value)(rotary_encoder_t *encoder); +}; + +/** + * @brief Create rotary encoder instance for EC11 + * + * @param config Rotary encoder configuration + * @param ret_encoder Returned rotary encoder handle + * @return + * - ESP_OK: Create rotary encoder instance successfully + * - ESP_ERR_INVALID_ARG: Create rotary encoder instance failed because of some invalid argument + * - ESP_ERR_NO_MEM: Create rotary encoder instance failed because there's no enough capable memory + * - ESP_FAIL: Create rotary encoder instance failed because of other error + */ +esp_err_t rotary_encoder_new_ec11(const rotary_encoder_config_t *config, rotary_encoder_t **ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/src/rotary_encoder_pcnt_ec11.c b/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/src/rotary_encoder_pcnt_ec11.c new file mode 100644 index 0000000000..afaec8e35f --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/src/rotary_encoder_pcnt_ec11.c @@ -0,0 +1,164 @@ +// 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 +#include +#include "esp_compiler.h" +#include "esp_log.h" +#include "driver/pcnt.h" +#include "hal/pcnt_hal.h" +#include "rotary_encoder.h" + +static const char *TAG = "rotary_encoder"; + +#define ROTARY_CHECK(a, msg, tag, ret, ...) \ + do { \ + if (unlikely(!(a))) { \ + ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + ret_code = ret; \ + goto tag; \ + } \ + } while (0) + +#define EC11_PCNT_DEFAULT_HIGH_LIMIT (100) +#define EC11_PCNT_DEFAULT_LOW_LIMIT (-100) + +typedef struct { + int accumu_count; + rotary_encoder_t parent; + pcnt_unit_t pcnt_unit; +} ec11_t; + +static esp_err_t ec11_set_glitch_filter(rotary_encoder_t *encoder, uint32_t max_glitch_us) +{ + esp_err_t ret_code = ESP_OK; + ec11_t *ec11 = __containerof(encoder, ec11_t, parent); + + /* Configure and enable the input filter */ + ROTARY_CHECK(pcnt_set_filter_value(ec11->pcnt_unit, max_glitch_us * 80) == ESP_OK, "set glitch filter failed", err, ESP_FAIL); + + if (max_glitch_us) { + pcnt_filter_enable(ec11->pcnt_unit); + } else { + pcnt_filter_disable(ec11->pcnt_unit); + } + + return ESP_OK; +err: + return ret_code; +} + +static esp_err_t ec11_start(rotary_encoder_t *encoder) +{ + ec11_t *ec11 = __containerof(encoder, ec11_t, parent); + pcnt_counter_resume(ec11->pcnt_unit); + return ESP_OK; +} + +static esp_err_t ec11_stop(rotary_encoder_t *encoder) +{ + ec11_t *ec11 = __containerof(encoder, ec11_t, parent); + pcnt_counter_pause(ec11->pcnt_unit); + return ESP_OK; +} + +static int ec11_get_counter_value(rotary_encoder_t *encoder) +{ + ec11_t *ec11 = __containerof(encoder, ec11_t, parent); + int16_t val = 0; + pcnt_get_counter_value(ec11->pcnt_unit, &val); + return val + ec11->accumu_count; +} + +static esp_err_t ec11_del(rotary_encoder_t *encoder) +{ + ec11_t *ec11 = __containerof(encoder, ec11_t, parent); + free(ec11); + return ESP_OK; +} + +static void ec11_pcnt_overflow_handler(void *arg) +{ + ec11_t *ec11 = (ec11_t *)arg; + uint32_t status = 0; + pcnt_get_event_status(ec11->pcnt_unit, &status); + + if (status & PCNT_EVT_H_LIM) { + ec11->accumu_count += EC11_PCNT_DEFAULT_HIGH_LIMIT; + } else if (status & PCNT_EVT_L_LIM) { + ec11->accumu_count += EC11_PCNT_DEFAULT_LOW_LIMIT; + } +} + +esp_err_t rotary_encoder_new_ec11(const rotary_encoder_config_t *config, rotary_encoder_t **ret_encoder) +{ + esp_err_t ret_code = ESP_OK; + ec11_t *ec11 = NULL; + + ROTARY_CHECK(config, "configuration can't be null", err, ESP_ERR_INVALID_ARG); + ROTARY_CHECK(ret_encoder, "can't assign context to null", err, ESP_ERR_INVALID_ARG); + + ec11 = calloc(1, sizeof(ec11_t)); + ROTARY_CHECK(ec11, "allocate context memory failed", err, ESP_ERR_NO_MEM); + + ec11->pcnt_unit = (pcnt_unit_t)(config->dev); + + // Configure channel 0 + pcnt_config_t dev_config = { + .pulse_gpio_num = config->phase_a_gpio_num, + .ctrl_gpio_num = config->phase_b_gpio_num, + .channel = PCNT_CHANNEL_0, + .unit = ec11->pcnt_unit, + .pos_mode = PCNT_COUNT_DEC, + .neg_mode = PCNT_COUNT_INC, + .lctrl_mode = PCNT_MODE_REVERSE, + .hctrl_mode = PCNT_MODE_KEEP, + .counter_h_lim = EC11_PCNT_DEFAULT_HIGH_LIMIT, + .counter_l_lim = EC11_PCNT_DEFAULT_LOW_LIMIT, + }; + ROTARY_CHECK(pcnt_unit_config(&dev_config) == ESP_OK, "config pcnt channel 0 failed", err, ESP_FAIL); + + // Configure channel 1 + dev_config.pulse_gpio_num = config->phase_b_gpio_num; + dev_config.ctrl_gpio_num = config->phase_a_gpio_num; + dev_config.channel = PCNT_CHANNEL_1; + dev_config.pos_mode = PCNT_COUNT_INC; + dev_config.neg_mode = PCNT_COUNT_DEC; + ROTARY_CHECK(pcnt_unit_config(&dev_config) == ESP_OK, "config pcnt channel 1 failed", err, ESP_FAIL); + + // PCNT pause and reset value + pcnt_counter_pause(ec11->pcnt_unit); + pcnt_counter_clear(ec11->pcnt_unit); + + // register interrupt handler + ROTARY_CHECK(pcnt_isr_service_install(0) == ESP_OK, "install isr service failed", err, ESP_FAIL); + pcnt_isr_handler_add(ec11->pcnt_unit, ec11_pcnt_overflow_handler, ec11); + + pcnt_event_enable(ec11->pcnt_unit, PCNT_EVT_H_LIM); + pcnt_event_enable(ec11->pcnt_unit, PCNT_EVT_L_LIM); + + ec11->parent.del = ec11_del; + ec11->parent.start = ec11_start; + ec11->parent.stop = ec11_stop; + ec11->parent.set_glitch_filter = ec11_set_glitch_filter; + ec11->parent.get_counter_value = ec11_get_counter_value; + + *ret_encoder = &(ec11->parent); + return ESP_OK; +err: + if (ec11) { + free(ec11); + } + return ret_code; +} diff --git a/examples/peripherals/pcnt/rotary_encoder/main/CMakeLists.txt b/examples/peripherals/pcnt/rotary_encoder/main/CMakeLists.txt new file mode 100644 index 0000000000..f49bb25673 --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "rotary_encoder_example_main.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/pcnt/rotary_encoder/main/component.mk b/examples/peripherals/pcnt/rotary_encoder/main/component.mk new file mode 100644 index 0000000000..44bd2b5273 --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/main/component.mk @@ -0,0 +1,3 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# diff --git a/examples/peripherals/pcnt/rotary_encoder/main/rotary_encoder_example_main.c b/examples/peripherals/pcnt/rotary_encoder/main/rotary_encoder_example_main.c new file mode 100644 index 0000000000..1f960a1a06 --- /dev/null +++ b/examples/peripherals/pcnt/rotary_encoder/main/rotary_encoder_example_main.c @@ -0,0 +1,37 @@ +/* PCNT example -- Rotary Encoder + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "rotary_encoder.h" + +static const char *TAG = "example"; + +void app_main(void) +{ + // Rotary encoder underlying device is represented by a PCNT unit in this example + uint32_t pcnt_unit = 0; + + // Create rotary encoder instance + rotary_encoder_config_t config = ROTARY_ENCODER_DEFAULT_CONFIG((rotary_encoder_dev_t)pcnt_unit, 14, 15); + rotary_encoder_t *encoder = NULL; + ESP_ERROR_CHECK(rotary_encoder_new_ec11(&config, &encoder)); + + // Filter out glitch (1us) + ESP_ERROR_CHECK(encoder->set_glitch_filter(encoder, 1)); + + // Start encoder + ESP_ERROR_CHECK(encoder->start(encoder)); + + // Report counter value + while (1) { + ESP_LOGI(TAG, "Encoder value: %d", encoder->get_counter_value(encoder)); + vTaskDelay(pdMS_TO_TICKS(1000)); + } +}