pcnt: add rotary encoder example

This commit is contained in:
morris 2020-09-16 13:50:56 +08:00
parent 74d78148bc
commit 153e819e8a
21 changed files with 480 additions and 11 deletions

View File

@ -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.

View File

@ -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);

View File

@ -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
*

View File

@ -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

View File

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

View File

@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(pcnt)
project(pcnt_event)

View File

@ -3,7 +3,7 @@
# project subdirectory.
#
PROJECT_NAME := pcnt
PROJECT_NAME := pcnt_event
include $(IDF_PATH)/make/project.mk

View File

@ -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

View File

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

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(rotary_encoder)

View File

@ -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

View File

@ -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.

View File

@ -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 "")

View File

@ -0,0 +1,3 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := src

View File

@ -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

View File

@ -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 <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#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;
}

View File

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

View File

@ -0,0 +1,3 @@
#
# Main Makefile. This is basically the same as a component makefile.
#

View File

@ -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));
}
}