Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 275 additions & 1 deletion src/ESP32Encoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@
#include <soc/soc_caps.h>
#if SOC_PCNT_SUPPORTED
// Not all esp32 chips support the pcnt (notably the esp32c3 does not)
#include <soc/pcnt_struct.h>
#include "esp_log.h"
#if ESP32ENCODER_USE_NEW_PCNT
#include <rom/gpio.h>
#else
#include <soc/pcnt_struct.h>
#include "esp_ipc.h"
#if ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 3) )
#include <freertos/FreeRTOS.h>
#include <rom/gpio.h>
#endif
#endif

static const char* TAG_ENCODER = "ESP32Encoder";

Expand All @@ -44,7 +48,11 @@ ESP32Encoder::ESP32Encoder(bool always_interrupt_, enc_isr_cb_t enc_isr_cb, void
always_interrupt{always_interrupt_},
aPinNumber{(gpio_num_t) 0},
bPinNumber{(gpio_num_t) 0},
#if ESP32ENCODER_USE_NEW_PCNT
unit{-1},
#else
unit{(pcnt_unit_t) -1},
#endif
countsMode{2},
count{0},
r_enc_config{},
Expand All @@ -62,6 +70,271 @@ ESP32Encoder::ESP32Encoder(bool always_interrupt_, enc_isr_cb_t enc_isr_cb, void

ESP32Encoder::~ESP32Encoder() {}

#if ESP32ENCODER_USE_NEW_PCNT

bool ESP32Encoder::pcntCallback(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx) {
ESP32Encoder *esp32enc = static_cast<ESP32Encoder *>(user_ctx);
if (esp32enc == nullptr || esp32enc->always_interrupt == false) {
return false;
}
if (edata == nullptr || (edata->watch_point_value != -1 && edata->watch_point_value != 1)) {
return false;
}

int raw_count = 0;
pcnt_unit_get_count(unit, &raw_count);
_ENTER_CRITICAL();
esp32enc->count = esp32enc->count + raw_count;
_EXIT_CRITICAL();
pcnt_unit_clear_count(unit);

if (esp32enc->_enc_isr_cb) {
esp32enc->_enc_isr_cb(esp32enc->_enc_isr_cb_data);
}
return false;
}

void ESP32Encoder::detach(){
if (unitHandle == nullptr) {
return;
}

if (attached) {
if (working) {
pcnt_unit_stop(unitHandle);
working = false;
}
pcnt_unit_disable(unitHandle);
}

if (channelHandles[0] != nullptr) {
pcnt_del_channel(channelHandles[0]);
channelHandles[0] = nullptr;
}
if (channelHandles[1] != nullptr) {
pcnt_del_channel(channelHandles[1]);
channelHandles[1] = nullptr;
}

pcnt_del_unit(unitHandle);
unitHandle = nullptr;
if (unit >= 0 && unit < MAX_ESP32_ENCODERS) {
ESP32Encoder::encoders[unit] = NULL;
}
unit = -1;
attached = false;
}

void ESP32Encoder::detatch(){
this->detach();
}

void ESP32Encoder::attach(int a, int b, encType et) {
if (attached) {
ESP_LOGE(TAG_ENCODER, "attach: already attached");
return;
}
int index = 0;
for (; index < MAX_ESP32_ENCODERS; index++) {
if (ESP32Encoder::encoders[index] == NULL) {
encoders[index] = this;
break;
}
}
if (index == MAX_ESP32_ENCODERS) {
while(1){
ESP_LOGE(TAG_ENCODER, "Too many encoders, FAIL!");
delay(100);
}
}

unit = index;
this->aPinNumber = (gpio_num_t) a;
this->bPinNumber = (gpio_num_t) b;

esp_rom_gpio_pad_select_gpio(aPinNumber);
esp_rom_gpio_pad_select_gpio(bPinNumber);
gpio_set_direction(aPinNumber, GPIO_MODE_INPUT);
gpio_set_direction(bPinNumber, GPIO_MODE_INPUT);
if(useInternalWeakPullResistors == puType::down){
gpio_pulldown_en(aPinNumber);
gpio_pulldown_en(bPinNumber);
}
if(useInternalWeakPullResistors == puType::up){
gpio_pullup_en(aPinNumber);
gpio_pullup_en(bPinNumber);
}

r_enc_config = {};
r_enc_config.high_limit = _INT16_MAX;
r_enc_config.low_limit = _INT16_MIN;
r_enc_config.flags.accum_count = true;

esp_err_t err = pcnt_new_unit(&r_enc_config, &unitHandle);
if (err != ESP_OK) {
ESP32Encoder::encoders[index] = NULL;
unit = -1;
ESP_LOGE(TAG_ENCODER, "Encoder create PCNT unit failed");
return;
}

setFilter(250);

pcnt_chan_config_t chan_a_config = {};
chan_a_config.edge_gpio_num = aPinNumber;
chan_a_config.level_gpio_num = bPinNumber;
err = pcnt_new_channel(unitHandle, &chan_a_config, &channelHandles[0]);
if (err != ESP_OK) {
ESP_LOGE(TAG_ENCODER, "Encoder create channel A failed");
detach();
return;
}

pcnt_channel_edge_action_t a_pos_action = et != encType::single ? PCNT_CHANNEL_EDGE_ACTION_DECREASE : PCNT_CHANNEL_EDGE_ACTION_HOLD;
pcnt_channel_edge_action_t a_neg_action = PCNT_CHANNEL_EDGE_ACTION_INCREASE;
pcnt_channel_set_edge_action(channelHandles[0], a_pos_action, a_neg_action);
pcnt_channel_set_level_action(channelHandles[0], PCNT_CHANNEL_LEVEL_ACTION_INVERSE, PCNT_CHANNEL_LEVEL_ACTION_KEEP);

if (et == encType::full) {
pcnt_chan_config_t chan_b_config = {};
chan_b_config.edge_gpio_num = bPinNumber;
chan_b_config.level_gpio_num = aPinNumber;
err = pcnt_new_channel(unitHandle, &chan_b_config, &channelHandles[1]);
if (err != ESP_OK) {
ESP_LOGE(TAG_ENCODER, "Encoder create channel B failed");
detach();
return;
}
pcnt_channel_set_edge_action(channelHandles[1], PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE);
pcnt_channel_set_level_action(channelHandles[1], PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE);
}

pcnt_unit_add_watch_point(unitHandle, _INT16_MAX);
pcnt_unit_add_watch_point(unitHandle, _INT16_MIN);

if (always_interrupt) {
pcnt_unit_add_watch_point(unitHandle, -1);
pcnt_unit_add_watch_point(unitHandle, 1);
pcnt_event_callbacks_t callbacks = {};
callbacks.on_reach = ESP32Encoder::pcntCallback;
pcnt_unit_register_event_callbacks(unitHandle, &callbacks, this);
}

err = pcnt_unit_enable(unitHandle);
if (err != ESP_OK) {
ESP_LOGE(TAG_ENCODER, "Encoder enable PCNT unit failed");
detach();
return;
}
attached = true;
pcnt_unit_clear_count(unitHandle);
err = pcnt_unit_start(unitHandle);
if (err != ESP_OK) {
ESP_LOGE(TAG_ENCODER, "Encoder start PCNT unit failed");
detach();
return;
}

working = true;
}

void ESP32Encoder::attachHalfQuad(int aPintNumber, int bPinNumber) {
attach(aPintNumber, bPinNumber, encType::half);
}

void ESP32Encoder::attachSingleEdge(int aPintNumber, int bPinNumber) {
attach(aPintNumber, bPinNumber, encType::single);
}

void ESP32Encoder::attachFullQuad(int aPintNumber, int bPinNumber) {
attach(aPintNumber, bPinNumber, encType::full);
}

void ESP32Encoder::setCount(int64_t value) {
_ENTER_CRITICAL();
count = value - getCountRaw();
_EXIT_CRITICAL();
}

int64_t ESP32Encoder::getCountRaw() {
int raw_count = 0;
if (unitHandle != nullptr) {
pcnt_unit_get_count(unitHandle, &raw_count);
}
return raw_count;
}

int64_t ESP32Encoder::getCount() {
_ENTER_CRITICAL();
int64_t result = count + getCountRaw();
_EXIT_CRITICAL();
return result;
}

int64_t ESP32Encoder::clearCount() {
_ENTER_CRITICAL();
count = 0;
_EXIT_CRITICAL();
if (unitHandle == nullptr) {
return ESP_ERR_INVALID_STATE;
}
return pcnt_unit_clear_count(unitHandle);
}

int64_t ESP32Encoder::pauseCount() {
if (unitHandle == nullptr) {
return ESP_ERR_INVALID_STATE;
}
esp_err_t err = pcnt_unit_stop(unitHandle);
if (err == ESP_OK) {
working = false;
}
return err;
}

int64_t ESP32Encoder::resumeCount() {
if (unitHandle == nullptr) {
return ESP_ERR_INVALID_STATE;
}
esp_err_t err = pcnt_unit_start(unitHandle);
if (err == ESP_OK) {
working = true;
}
return err;
}

void ESP32Encoder::setFilter(uint16_t value) {
if (unitHandle == nullptr) {
return;
}
if(value>1023)value=1023;

bool restart = attached && working;
if (attached) {
if (working) {
pcnt_unit_stop(unitHandle);
}
pcnt_unit_disable(unitHandle);
}

if(value==0) {
pcnt_unit_set_glitch_filter(unitHandle, nullptr);
} else {
pcnt_glitch_filter_config_t filter_config = {};
filter_config.max_glitch_ns = (static_cast<uint32_t>(value) * 125 + 9) / 10;
pcnt_unit_set_glitch_filter(unitHandle, &filter_config);
}

if (attached) {
pcnt_unit_enable(unitHandle);
if (restart) {
pcnt_unit_start(unitHandle);
}
}
}

#else

/* Decode what PCNT's unit originated an interrupt
* and pass this information together with the event type
* the main program using a queue.
Expand Down Expand Up @@ -332,6 +605,7 @@ void ESP32Encoder::setFilter(uint16_t value) {
}

}
#endif // ESP32ENCODER_USE_NEW_PCNT
#else
#warning PCNT not supported on this SoC, this will likely lead to linker errors when using ESP32Encoder
#endif // SOC_PCNT_SUPPORTED
27 changes: 26 additions & 1 deletion src/ESP32Encoder.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
#pragma once
#include <driver/gpio.h>
#if __has_include(<esp_idf_version.h>)
#include <esp_idf_version.h>
#endif
#include <soc/soc_caps.h>

#if defined(ESP_IDF_VERSION) && defined(ESP_IDF_VERSION_VAL) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#define ESP32ENCODER_USE_NEW_PCNT 1
#include <driver/pulse_cnt.h>
#else
#define ESP32ENCODER_USE_NEW_PCNT 0
#include <driver/pcnt.h>
#endif
#ifndef ARDUINO
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/portable.h>
#include <freertos/semphr.h>
#endif

#if ESP32ENCODER_USE_NEW_PCNT
#define MAX_ESP32_ENCODERS SOC_PCNT_UNITS_PER_GROUP
#else
#define MAX_ESP32_ENCODERS PCNT_UNIT_MAX
#endif
#define _INT16_MAX 32766
#define _INT16_MIN -32766
#define ISR_CORE_USE_DEFAULT (0xffffffff)
Expand Down Expand Up @@ -57,10 +72,15 @@ class ESP32Encoder {
bool always_interrupt;
gpio_num_t aPinNumber;
gpio_num_t bPinNumber;
#if ESP32ENCODER_USE_NEW_PCNT
int unit;
pcnt_unit_config_t r_enc_config;
#else
pcnt_unit_t unit;
pcnt_config_t r_enc_config;
#endif
int countsMode = 2;
volatile int64_t count=0;
pcnt_config_t r_enc_config;
static puType useInternalWeakPullResistors;
static uint32_t isrServiceCpuCore;
enc_isr_cb_t _enc_isr_cb;
Expand All @@ -70,6 +90,11 @@ class ESP32Encoder {
static bool attachedInterrupt;
void attach(int aPintNumber, int bPinNumber, encType et);
int64_t getCountRaw();
#if ESP32ENCODER_USE_NEW_PCNT
static bool pcntCallback(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx);
pcnt_unit_handle_t unitHandle = nullptr;
pcnt_channel_handle_t channelHandles[2] = {nullptr, nullptr};
#endif
bool attached;
bool direction;
bool working;
Expand Down