Merge branch 'feature/pcnt_driver_ng' into 'master'

 Driver-NG: Introduce a brand new driver for PCNT

Closes IDF-2196 and IDF-2896

See merge request espressif/esp-idf!10507
This commit is contained in:
morris
2022-03-04 12:25:35 +08:00
74 changed files with 2778 additions and 1475 deletions

View File

@@ -2,8 +2,7 @@
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/common_components/pid_ctrl"
"$ENV{IDF_PATH}/examples/peripherals/pcnt/rotary_encoder/components")
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/common_components/pid_ctrl")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mcpwm_brushed_dc_control)

View File

@@ -9,8 +9,8 @@
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gptimer.h"
#include "driver/pulse_cnt.h"
#include "driver/mcpwm.h"
#include "rotary_encoder.h"
#include "pid_ctrl.h"
#include "esp_console.h"
#include "argtable3/argtable3.h"
@@ -23,7 +23,9 @@
#define BDC_MCPWM_GENA_GPIO_NUM 7
#define BDC_MCPWM_GENB_GPIO_NUM 15
#define BDC_MCPWM_FREQ_HZ 1500
#define BDC_ENCODER_PCNT_UNIT 0
#define BDC_ENCODER_PCNT_HIGH_LIMIT 100
#define BDC_ENCODER_PCNT_LOW_LIMIT -100
#define BDC_ENCODER_PHASEA_GPIO_NUM 36
#define BDC_ENCODER_PHASEB_GPIO_NUM 35
@@ -45,7 +47,8 @@ static int expect_pulses = 300;
static int real_pulses;
typedef struct {
rotary_encoder_t *encoder;
pcnt_unit_handle_t hall_pcnt_encoder;
int accumu_count;
QueueHandle_t pid_feedback_queue;
} motor_control_timer_context_t;
@@ -54,6 +57,13 @@ typedef struct {
pid_ctrl_block_handle_t pid_ctrl;
} motor_control_task_context_t;
static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
{
motor_control_timer_context_t *ctx = (motor_control_timer_context_t *)user_ctx;
ctx->accumu_count += edata->watch_point_value;
return false;
}
static void brushed_motor_set_duty(float duty_cycle)
{
/* motor moves in forward direction, with duty cycle = duty % */
@@ -75,9 +85,12 @@ static bool motor_ctrl_timer_cb(gptimer_handle_t timer, const gptimer_alarm_even
static int last_pulse_count = 0;
BaseType_t high_task_awoken = pdFALSE;
motor_control_timer_context_t *user_ctx = (motor_control_timer_context_t *)arg;
rotary_encoder_t *encoder = user_ctx->encoder;
pcnt_unit_handle_t pcnt_unit = user_ctx->hall_pcnt_encoder;
int cur_pulse_count = 0;
pcnt_unit_get_count(pcnt_unit, &cur_pulse_count);
cur_pulse_count += user_ctx->accumu_count;
int cur_pulse_count = encoder->get_counter_value(encoder);
int delta = cur_pulse_count - last_pulse_count;
last_pulse_count = cur_pulse_count;
xQueueSendFromISR(user_ctx->pid_feedback_queue, &delta, &high_task_awoken);
@@ -143,6 +156,8 @@ static void register_pid_console_command(void)
void app_main(void)
{
static motor_control_timer_context_t my_timer_ctx = {};
QueueHandle_t pid_fb_queue = xQueueCreate(BDC_PID_FEEDBACK_QUEUE_LEN, sizeof(int));
assert(pid_fb_queue);
@@ -160,15 +175,40 @@ void app_main(void)
ESP_ERROR_CHECK(mcpwm_init(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, &pwm_config));
printf("init and start rotary encoder\r\n");
rotary_encoder_config_t config = {
.dev = (rotary_encoder_dev_t)BDC_ENCODER_PCNT_UNIT,
.phase_a_gpio_num = BDC_ENCODER_PHASEA_GPIO_NUM,
.phase_b_gpio_num = BDC_ENCODER_PHASEB_GPIO_NUM,
pcnt_unit_config_t unit_config = {
.high_limit = BDC_ENCODER_PCNT_HIGH_LIMIT,
.low_limit = BDC_ENCODER_PCNT_LOW_LIMIT,
};
rotary_encoder_t *speed_encoder = NULL;
ESP_ERROR_CHECK(rotary_encoder_new_ec11(&config, &speed_encoder));
ESP_ERROR_CHECK(speed_encoder->set_glitch_filter(speed_encoder, 1));
ESP_ERROR_CHECK(speed_encoder->start(speed_encoder));
pcnt_unit_handle_t pcnt_unit = NULL;
ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &pcnt_unit));
pcnt_glitch_filter_config_t filter_config = {
.max_glitch_ns = 1000,
};
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
pcnt_chan_config_t chan_a_config = {
.edge_gpio_num = BDC_ENCODER_PHASEA_GPIO_NUM,
.level_gpio_num = BDC_ENCODER_PHASEB_GPIO_NUM,
};
pcnt_channel_handle_t pcnt_chan_a = NULL;
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_a_config, &pcnt_chan_a));
pcnt_chan_config_t chan_b_config = {
.edge_gpio_num = BDC_ENCODER_PHASEB_GPIO_NUM,
.level_gpio_num = BDC_ENCODER_PHASEA_GPIO_NUM,
};
pcnt_channel_handle_t pcnt_chan_b = NULL;
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_b_config, &pcnt_chan_b));
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_a, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_a, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_b, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE));
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_b, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, BDC_ENCODER_PCNT_HIGH_LIMIT));
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, BDC_ENCODER_PCNT_LOW_LIMIT));
pcnt_event_callbacks_t pcnt_cbs = {
.on_reach = example_pcnt_on_reach,
};
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &pcnt_cbs, &my_timer_ctx));
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));
printf("init PID control block\r\n");
pid_ctrl_block_handle_t pid_ctrl;
@@ -193,13 +233,12 @@ void app_main(void)
xTaskCreate(bdc_ctrl_task, "bdc_ctrl_task", 4096, &my_ctrl_task_ctx, 5, NULL);
printf("start motor control timer\r\n");
static motor_control_timer_context_t my_timer_ctx = {};
my_timer_ctx.pid_feedback_queue = pid_fb_queue;
my_timer_ctx.encoder = speed_encoder;
gptimer_event_callbacks_t cbs = {
my_timer_ctx.hall_pcnt_encoder = pcnt_unit;
gptimer_event_callbacks_t gptimer_cbs = {
.on_alarm = motor_ctrl_timer_cb,
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, &my_timer_ctx));
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &gptimer_cbs, &my_timer_ctx));
gptimer_alarm_config_t alarm_config = {
.reload_count = 0,
.alarm_count = BDC_PID_CALCULATION_PERIOD_US,

View File

@@ -1,6 +0,0 @@
# 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(pcnt_event)

View File

@@ -1,75 +0,0 @@
| Supported Targets | ESP32 | ESP32-S2 |
| ----------------- | ----- | -------- |
# Pulse Count Event Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example uses the pulse counter module (PCNT) to count the rising edges of the PWM pulses generated by the LED Controller module (LEDC).
## How to use example
### Hardware Required
* A development board with ESP32 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for power supply and programming
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 GPIO5 is connected to Ground, the value decreases).
### Configure the project
```
idf.py menuconfig
```
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
Run this example, and you can see the following output log on the serial monitor:
(Here, GPIO5 is connected to Ground)
```
Current counter value :-1
Current counter value :-2
Current counter value :-3
Current counter value :-4
Event PCNT unit[0]; cnt: -5
THRES0 EVT
Current counter value :-5
Current counter value :-6
Current counter value :-7
Current counter value :-8
Current counter value :-9
Event PCNT unit[0]; cnt: 0
L_LIM EVT
ZERO EVT
Current counter value :0
Current counter value :-1
...
```
## Troubleshooting
* program upload failure
* Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs.
* The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again.
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

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

View File

@@ -1,197 +0,0 @@
/* Pulse counter module - Example
For other examples please check:
https://github.com/espressif/esp-idf/tree/master/examples
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 "freertos/queue.h"
#include "driver/ledc.h"
#include "driver/pcnt.h"
#include "esp_attr.h"
#include "esp_log.h"
static const char *TAG = "example";
/**
* TEST CODE BRIEF
*
* Use PCNT module to count rising edges generated by LEDC module.
*
* Functionality of GPIOs used in this example:
* - GPIO18 - output pin of a sample 1 Hz pulse generator,
* - GPIO4 - pulse input pin,
* - GPIO5 - control input pin.
*
* Load example, open a serial port to view the message printed on your screen.
*
* To do this test, you should connect GPIO18 with GPIO4.
* GPIO5 is the control signal, you can leave it floating with internal pull up,
* or connect it to ground. If left floating, the count value will be increasing.
* If you connect GPIO5 to GND, the count value will be decreasing.
*
* An interrupt will be triggered when the counter value:
* - reaches 'thresh1' or 'thresh0' value,
* - reaches 'l_lim' value or 'h_lim' value,
* - will be reset to zero.
*/
#define PCNT_H_LIM_VAL 10
#define PCNT_L_LIM_VAL -10
#define PCNT_THRESH1_VAL 5
#define PCNT_THRESH0_VAL -5
#define PCNT_INPUT_SIG_IO 4 // Pulse Input GPIO
#define PCNT_INPUT_CTRL_IO 5 // Control GPIO HIGH=count up, LOW=count down
#define LEDC_OUTPUT_IO 18 // Output GPIO of a sample 1 Hz pulse generator
QueueHandle_t pcnt_evt_queue; // A queue to handle pulse counter events
/* A sample structure to pass events from the PCNT
* interrupt handler to the main program.
*/
typedef struct {
int unit; // the PCNT unit that originated an interrupt
uint32_t status; // information on the event type that caused the interrupt
} pcnt_evt_t;
/* Decode what PCNT's unit originated an interrupt
* and pass this information together with the event type
* the main program using a queue.
*/
static void IRAM_ATTR pcnt_example_intr_handler(void *arg)
{
int pcnt_unit = (int)arg;
pcnt_evt_t evt;
evt.unit = pcnt_unit;
/* Save the PCNT event type that caused an interrupt
to pass it to the main program */
pcnt_get_event_status(pcnt_unit, &evt.status);
xQueueSendFromISR(pcnt_evt_queue, &evt, NULL);
}
/* Configure LED PWM Controller
* to output sample pulses at 1 Hz with duty of about 10%
*/
static void ledc_init(void)
{
// Prepare and then apply the LEDC PWM timer configuration
ledc_timer_config_t ledc_timer;
ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE;
ledc_timer.timer_num = LEDC_TIMER_1;
ledc_timer.duty_resolution = LEDC_TIMER_10_BIT;
ledc_timer.freq_hz = 1; // set output frequency at 1 Hz
ledc_timer.clk_cfg = LEDC_AUTO_CLK;
ledc_timer_config(&ledc_timer);
// Prepare and then apply the LEDC PWM channel configuration
ledc_channel_config_t ledc_channel;
ledc_channel.speed_mode = LEDC_LOW_SPEED_MODE;
ledc_channel.channel = LEDC_CHANNEL_1;
ledc_channel.timer_sel = LEDC_TIMER_1;
ledc_channel.intr_type = LEDC_INTR_DISABLE;
ledc_channel.gpio_num = LEDC_OUTPUT_IO;
ledc_channel.duty = 100; // set duty at about 10%
ledc_channel.hpoint = 0;
ledc_channel_config(&ledc_channel);
}
/* Initialize PCNT functions:
* - configure and initialize PCNT
* - set up the input filter
* - set up the counter events to watch
*/
static void pcnt_example_init(int unit)
{
/* Prepare configuration for the PCNT unit */
pcnt_config_t pcnt_config = {
// Set PCNT input signal and control GPIOs
.pulse_gpio_num = PCNT_INPUT_SIG_IO,
.ctrl_gpio_num = PCNT_INPUT_CTRL_IO,
.channel = PCNT_CHANNEL_0,
.unit = unit,
// What to do on the positive / negative edge of pulse input?
.pos_mode = PCNT_COUNT_INC, // Count up on the positive edge
.neg_mode = PCNT_COUNT_DIS, // Keep the counter value on the negative edge
// What to do when control input is low or high?
.lctrl_mode = PCNT_MODE_REVERSE, // Reverse counting direction if low
.hctrl_mode = PCNT_MODE_KEEP, // Keep the primary counter mode if high
// Set the maximum and minimum limit values to watch
.counter_h_lim = PCNT_H_LIM_VAL,
.counter_l_lim = PCNT_L_LIM_VAL,
};
/* Initialize PCNT unit */
pcnt_unit_config(&pcnt_config);
/* Configure and enable the input filter */
pcnt_set_filter_value(unit, 100);
pcnt_filter_enable(unit);
/* Set threshold 0 and 1 values and enable events to watch */
pcnt_set_event_value(unit, PCNT_EVT_THRES_1, PCNT_THRESH1_VAL);
pcnt_event_enable(unit, PCNT_EVT_THRES_1);
pcnt_set_event_value(unit, PCNT_EVT_THRES_0, PCNT_THRESH0_VAL);
pcnt_event_enable(unit, PCNT_EVT_THRES_0);
/* Enable events on zero, maximum and minimum limit values */
pcnt_event_enable(unit, PCNT_EVT_ZERO);
pcnt_event_enable(unit, PCNT_EVT_H_LIM);
pcnt_event_enable(unit, PCNT_EVT_L_LIM);
/* Initialize PCNT's counter */
pcnt_counter_pause(unit);
pcnt_counter_clear(unit);
/* Install interrupt service and add isr callback handler */
pcnt_isr_service_install(0);
pcnt_isr_handler_add(unit, pcnt_example_intr_handler, (void *)unit);
/* Everything is set up, now go to counting */
pcnt_counter_resume(unit);
}
void app_main(void)
{
int pcnt_unit = PCNT_UNIT_0;
/* Initialize LEDC to generate sample pulse signal */
ledc_init();
/* Initialize PCNT event queue and PCNT functions */
pcnt_evt_queue = xQueueCreate(10, sizeof(pcnt_evt_t));
pcnt_example_init(pcnt_unit);
int16_t count = 0;
pcnt_evt_t evt;
portBASE_TYPE res;
while (1) {
/* Wait for the event information passed from PCNT's interrupt handler.
* Once received, decode the event type and print it on the serial monitor.
*/
res = xQueueReceive(pcnt_evt_queue, &evt, 1000 / portTICK_PERIOD_MS);
if (res == pdTRUE) {
pcnt_get_counter_value(pcnt_unit, &count);
ESP_LOGI(TAG, "Event PCNT unit[%d]; cnt: %d", evt.unit, count);
if (evt.status & PCNT_EVT_THRES_1) {
ESP_LOGI(TAG, "THRES1 EVT");
}
if (evt.status & PCNT_EVT_THRES_0) {
ESP_LOGI(TAG, "THRES0 EVT");
}
if (evt.status & PCNT_EVT_L_LIM) {
ESP_LOGI(TAG, "L_LIM EVT");
}
if (evt.status & PCNT_EVT_H_LIM) {
ESP_LOGI(TAG, "H_LIM EVT");
}
if (evt.status & PCNT_EVT_ZERO) {
ESP_LOGI(TAG, "ZERO EVT");
}
} else {
pcnt_get_counter_value(pcnt_unit, &count);
ESP_LOGI(TAG, "Current counter value :%d", count);
}
}
}

View File

@@ -1,5 +1,5 @@
| Supported Targets | ESP32 | ESP32-S2 |
| ----------------- | ----- | -------- |
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- |
# Rotary Encoder Example
@@ -49,6 +49,8 @@ Connection :
+--------+ +---------------------------------+
```
The GPIO used by the example can be changed according to your board by `EXAMPLE_EC11_GPIO_A` and `EXAMPLE_EC11_GPIO_B` in [source file](main/rotary_encoder_example_main.c);
### Build and Flash
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
@@ -60,18 +62,42 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
## 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
I (0) cpu_start: Starting scheduler on APP CPU.
I (325) example: install pcnt unit
I (335) example: set glitch filter
I (345) example: install pcnt channels
I (395) example: set edge and level actions for pcnt channels
I (405) example: add watch points and register callbacks
I (405) example: clear pcnt unit
I (415) example: start pcnt unit
I (1415) example: Pulse count: 0
I (2415) example: Pulse count: 8
I (3415) example: Pulse count: 27
I (4415) example: Pulse count: 40
I (4705) example: Watch point event, count: 50
I (5705) example: Pulse count: 72
I (6705) example: Pulse count: 96
I (6785) example: Watch point event, count: 100
I (6785) example: Watch point event, count: 0
I (7785) example: Pulse count: 8
I (8785) example: Pulse count: 8
I (9225) example: Watch point event, count: 0
I (10225) example: Pulse count: -20
I (11225) example: Pulse count: -28
I (12225) example: Pulse count: -48
I (12995) example: Watch point event, count: -50
I (13995) example: Pulse count: -68
I (14995) example: Pulse count: -82
I (15995) example: Pulse count: -92
I (16875) example: Watch point event, count: -100
I (16875) example: Watch point event, count: 0
I (17875) example: Pulse count: -12
I (18875) example: Pulse count: -12
I (19875) example: Pulse count: -12
```
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.
This example enables the 4X mode to parse the rotary signals, which means, each complete rotary step will result in PCNT counter increasing or decreasing by 4, depending on the direction of rotation.
The example adds five watch points, events will be triggered when counter reaches to any watch point.
## Troubleshooting

View File

@@ -1,7 +0,0 @@
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

@@ -1,127 +0,0 @@
// 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

@@ -1,180 +0,0 @@
// 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 "sys/lock.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)
// A flag to identify if pcnt isr service has been installed.
static bool is_pcnt_isr_service_installed = false;
// A lock to avoid pcnt isr service being installed twice in multiple threads.
static _lock_t isr_service_install_lock;
#define LOCK_ACQUIRE() _lock_acquire(&isr_service_install_lock)
#define LOCK_RELEASE() _lock_release(&isr_service_install_lock)
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 in a thread-safe way
LOCK_ACQUIRE();
if (!is_pcnt_isr_service_installed) {
ROTARY_CHECK(pcnt_isr_service_install(0) == ESP_OK, "install isr service failed", err, ESP_FAIL);
// make sure pcnt isr service won't be installed more than one time
is_pcnt_isr_service_installed = true;
}
LOCK_RELEASE();
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

@@ -1,37 +1,93 @@
/* PCNT example -- Rotary Encoder
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
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 "freertos/queue.h"
#include "esp_log.h"
#include "rotary_encoder.h"
#include "driver/pulse_cnt.h"
static const char *TAG = "example";
#define EXAMPLE_PCNT_HIGH_LIMIT 100
#define EXAMPLE_PCNT_LOW_LIMIT -100
#define EXAMPLE_EC11_GPIO_A 0
#define EXAMPLE_EC11_GPIO_B 2
static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
{
BaseType_t high_task_wakeup;
QueueHandle_t queue = (QueueHandle_t)user_ctx;
// send event data to queue, from this interrupt callback
xQueueSendFromISR(queue, &(edata->watch_point_value), &high_task_wakeup);
return (high_task_wakeup == pdTRUE);
}
void app_main(void)
{
// Rotary encoder underlying device is represented by a PCNT unit in this example
uint32_t pcnt_unit = 0;
ESP_LOGI(TAG, "install pcnt unit");
pcnt_unit_config_t unit_config = {
.high_limit = EXAMPLE_PCNT_HIGH_LIMIT,
.low_limit = EXAMPLE_PCNT_LOW_LIMIT,
};
pcnt_unit_handle_t pcnt_unit = NULL;
ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &pcnt_unit));
// 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));
ESP_LOGI(TAG, "set glitch filter");
pcnt_glitch_filter_config_t filter_config = {
.max_glitch_ns = 1000,
};
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
// Filter out glitch (1us)
ESP_ERROR_CHECK(encoder->set_glitch_filter(encoder, 1));
ESP_LOGI(TAG, "install pcnt channels");
pcnt_chan_config_t chan_a_config = {
.edge_gpio_num = EXAMPLE_EC11_GPIO_A,
.level_gpio_num = EXAMPLE_EC11_GPIO_B,
};
pcnt_channel_handle_t pcnt_chan_a = NULL;
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_a_config, &pcnt_chan_a));
pcnt_chan_config_t chan_b_config = {
.edge_gpio_num = EXAMPLE_EC11_GPIO_B,
.level_gpio_num = EXAMPLE_EC11_GPIO_A,
};
pcnt_channel_handle_t pcnt_chan_b = NULL;
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_b_config, &pcnt_chan_b));
// Start encoder
ESP_ERROR_CHECK(encoder->start(encoder));
ESP_LOGI(TAG, "set edge and level actions for pcnt channels");
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_a, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_a, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan_b, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE));
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan_b, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
ESP_LOGI(TAG, "add watch points and register callbacks");
int watch_points[] = {EXAMPLE_PCNT_LOW_LIMIT, -50, 0, 50, EXAMPLE_PCNT_HIGH_LIMIT};
for (size_t i = 0; i < sizeof(watch_points) / sizeof(watch_points[0]); i++) {
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, watch_points[i]));
}
pcnt_event_callbacks_t cbs = {
.on_reach = example_pcnt_on_reach,
};
QueueHandle_t queue = xQueueCreate(10, sizeof(int));
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue));
ESP_LOGI(TAG, "clear pcnt unit");
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
ESP_LOGI(TAG, "start pcnt unit");
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));
// Report counter value
int pulse_count = 0;
int event_count = 0;
while (1) {
ESP_LOGI(TAG, "Encoder value: %d", encoder->get_counter_value(encoder));
vTaskDelay(pdMS_TO_TICKS(1000));
if (xQueueReceive(queue, &event_count, pdMS_TO_TICKS(1000))) {
ESP_LOGI(TAG, "Watch point event, count: %d", event_count);
} else {
ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_unit, &pulse_count));
ESP_LOGI(TAG, "Pulse count: %d", pulse_count);
}
}
}

View File

@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded.dut import Dut
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.generic
def test_gptimer_example(dut: Dut) -> None:
dut.expect_exact('install pcnt unit')
dut.expect_exact('set glitch filter')
dut.expect_exact('install pcnt channels')
dut.expect_exact('set edge and level actions for pcnt channels')
dut.expect_exact('add watch points and register callbacks')
dut.expect_exact('clear pcnt unit')
dut.expect_exact('start pcnt unit')
res = dut.expect(r'Pulse count: (\d+)')
count_val = res.group(1).decode('utf8')
assert -100 <= int(count_val) <= 100

View File

@@ -8,29 +8,29 @@ from pytest_embedded import Dut
@pytest.mark.supported_targets
@pytest.mark.generic
def test_gptimer_example(dut: Dut) -> None:
dut.expect(r'Create timer handle', timeout=5)
dut.expect(r'Start timer, stop it at alarm event', timeout=5)
dut.expect_exact('Create timer handle', timeout=5)
dut.expect_exact('Start timer, stop it at alarm event', timeout=5)
res = dut.expect(r'Timer stopped, count=(\d+)', timeout=30)
stopped_count = res.group(1).decode('utf8')
assert (1000000 - 10) < int(stopped_count) < (1000000 + 10)
dut.expect(r'Set count value')
dut.expect(r'Get count value')
dut.expect_exact('Set count value')
dut.expect_exact('Get count value')
res = dut.expect(r'Timer count value=(\d+)', timeout=5)
count_val = res.group(1).decode('utf8')
assert int(count_val) == 100
dut.expect(r'Start timer, auto-reload at alarm event', timeout=5)
dut.expect_exact('Start timer, auto-reload at alarm event', timeout=5)
res = dut.expect(r'Timer reloaded, count=(\d+)', timeout=5)
reloaded_count = res.group(1).decode('utf8')
assert 0 <= int(reloaded_count) < 10
dut.expect(r'Stop timer')
dut.expect(r'Update alarm value dynamically')
dut.expect_exact('Stop timer')
dut.expect_exact('Update alarm value dynamically')
for i in range(1,5):
res = dut.expect(r'Timer alarmed, count=(\d+)', timeout=5)
alarm_count = res.group(1).decode('utf8')
assert (i * 1000000 - 10) < int(alarm_count) < (i * 1000000 + 10)
dut.expect(r'Stop timer')
dut.expect(r'Delete timer')
dut.expect_exact('Stop timer')
dut.expect_exact('Delete timer')