2025-12-28 12:46:10 -06:00
|
|
|
#include "encoder.hpp"
|
|
|
|
|
#include "driver/gpio.h"
|
|
|
|
|
#include "esp_log.h"
|
2025-12-28 15:31:20 -06:00
|
|
|
#include "soc/gpio_struct.h"
|
2025-12-31 15:44:48 -06:00
|
|
|
#include "servo.hpp"
|
2026-01-03 22:59:36 -06:00
|
|
|
#include "defines.h"
|
2025-12-28 12:46:10 -06:00
|
|
|
|
2025-12-28 15:31:20 -06:00
|
|
|
static const char *TAG = "ENCODER";
|
2025-12-28 12:46:10 -06:00
|
|
|
|
2025-12-28 15:31:20 -06:00
|
|
|
// Constructor
|
|
|
|
|
Encoder::Encoder(gpio_num_t pinA, gpio_num_t pinB)
|
|
|
|
|
: pin_a(pinA), pin_b(pinB), count(0),
|
2026-01-07 16:53:50 -06:00
|
|
|
last_state_a(0), last_state_b(0), last_count_base(0),
|
|
|
|
|
watchdog_handle(nullptr) {}
|
2025-12-28 12:46:10 -06:00
|
|
|
|
2025-12-28 15:31:20 -06:00
|
|
|
// Static ISR - receives Encoder instance via arg
|
|
|
|
|
void IRAM_ATTR Encoder::isr_handler(void* arg)
|
|
|
|
|
{
|
2025-12-31 15:44:48 -06:00
|
|
|
Encoder* encoder = static_cast<Encoder*>(arg);
|
|
|
|
|
|
|
|
|
|
// Read GPIO levels directly from hardware
|
|
|
|
|
uint32_t gpio_levels = GPIO.in.val;
|
|
|
|
|
uint8_t current_a = (gpio_levels >> encoder->pin_a) & 0x1;
|
|
|
|
|
uint8_t current_b = (gpio_levels >> encoder->pin_b) & 0x1;
|
2025-12-28 12:46:10 -06:00
|
|
|
|
2025-12-31 15:44:48 -06:00
|
|
|
// Quadrature decoding logic
|
|
|
|
|
if (current_a != encoder->last_state_a) {
|
|
|
|
|
if (!current_a) {
|
|
|
|
|
if (current_b) encoder->last_count_base++;
|
|
|
|
|
else encoder->last_count_base--;
|
2025-12-28 12:46:10 -06:00
|
|
|
}
|
2025-12-31 15:44:48 -06:00
|
|
|
else {
|
|
|
|
|
if (current_b) encoder->last_count_base--;
|
|
|
|
|
else encoder->last_count_base++;
|
2025-12-28 12:46:10 -06:00
|
|
|
}
|
2025-12-31 15:44:48 -06:00
|
|
|
}
|
|
|
|
|
else if (current_b != encoder->last_state_b) {
|
|
|
|
|
if (!current_b) {
|
|
|
|
|
if (current_a) encoder->last_count_base--;
|
|
|
|
|
else encoder->last_count_base++;
|
2025-12-28 12:46:10 -06:00
|
|
|
}
|
2025-12-31 15:44:48 -06:00
|
|
|
else {
|
|
|
|
|
if (current_a) encoder->last_count_base++;
|
|
|
|
|
else encoder->last_count_base--;
|
2025-12-28 12:46:10 -06:00
|
|
|
}
|
2025-12-31 15:44:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Accumulate to full detent count
|
|
|
|
|
if (encoder->last_count_base > 3) {
|
|
|
|
|
encoder->count += 1;
|
|
|
|
|
encoder->last_count_base -= 4;
|
2026-01-03 22:59:36 -06:00
|
|
|
|
|
|
|
|
// DEFER to task via queue instead of direct function calls
|
|
|
|
|
encoder_event_t event = {
|
|
|
|
|
.count = encoder->count.load(),
|
|
|
|
|
.is_top_encoder = (encoder == topEnc)
|
|
|
|
|
};
|
|
|
|
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
|
|
|
|
if (g_encoder_event_queue != NULL) {
|
|
|
|
|
xQueueSendFromISR(g_encoder_event_queue, &event, &xHigherPriorityTaskWoken);
|
|
|
|
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
|
|
|
|
}
|
2025-12-31 15:44:48 -06:00
|
|
|
}
|
|
|
|
|
else if (encoder->last_count_base < 0) {
|
|
|
|
|
encoder->count -= 1;
|
|
|
|
|
encoder->last_count_base += 4;
|
2026-01-03 22:59:36 -06:00
|
|
|
|
|
|
|
|
// DEFER to task via queue instead of direct function calls
|
|
|
|
|
encoder_event_t event = {
|
|
|
|
|
.count = encoder->count.load(),
|
|
|
|
|
.is_top_encoder = (encoder == topEnc)
|
|
|
|
|
};
|
|
|
|
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
|
|
|
|
if (g_encoder_event_queue != NULL) {
|
|
|
|
|
xQueueSendFromISR(g_encoder_event_queue, &event, &xHigherPriorityTaskWoken);
|
|
|
|
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
|
|
|
|
}
|
2025-12-31 15:44:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
encoder->last_state_a = current_a;
|
|
|
|
|
encoder->last_state_b = current_b;
|
2025-12-28 12:46:10 -06:00
|
|
|
}
|
|
|
|
|
|
2025-12-28 15:31:20 -06:00
|
|
|
void Encoder::init()
|
2025-12-28 12:46:10 -06:00
|
|
|
{
|
|
|
|
|
gpio_config_t io_conf = {};
|
|
|
|
|
io_conf.intr_type = GPIO_INTR_ANYEDGE;
|
2025-12-28 15:31:20 -06:00
|
|
|
io_conf.pin_bit_mask = (1ULL << pin_a) | (1ULL << pin_b);
|
2025-12-28 12:46:10 -06:00
|
|
|
io_conf.mode = GPIO_MODE_INPUT;
|
|
|
|
|
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
|
|
|
|
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
|
|
|
|
gpio_config(&io_conf);
|
|
|
|
|
|
2025-12-28 15:31:20 -06:00
|
|
|
// Install ISR service if not already installed
|
2025-12-31 15:44:48 -06:00
|
|
|
gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1);
|
2025-12-28 12:46:10 -06:00
|
|
|
|
2025-12-28 15:31:20 -06:00
|
|
|
// Attach ISR with THIS instance as argument
|
|
|
|
|
gpio_isr_handler_add(pin_a, Encoder::isr_handler, this);
|
|
|
|
|
gpio_isr_handler_add(pin_b, Encoder::isr_handler, this);
|
2025-12-28 12:46:10 -06:00
|
|
|
|
2025-12-28 15:31:20 -06:00
|
|
|
ESP_LOGI(TAG, "Encoder initialized on pins %d and %d", pin_a, pin_b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Encoder::deinit()
|
|
|
|
|
{
|
|
|
|
|
gpio_isr_handler_remove(pin_a);
|
|
|
|
|
gpio_isr_handler_remove(pin_b);
|
|
|
|
|
ESP_LOGI(TAG, "Encoder deinitialized");
|
2025-12-31 15:44:48 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Encoder::setupWatchdog() {
|
|
|
|
|
if (watchdog_handle == NULL) {
|
|
|
|
|
const esp_timer_create_args_t enc_watchdog_args = {
|
|
|
|
|
.callback = &watchdogCallback,
|
|
|
|
|
.dispatch_method = ESP_TIMER_ISR,
|
|
|
|
|
.name = "encoder_wdt",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ESP_ERROR_CHECK(esp_timer_create(&enc_watchdog_args, &watchdog_handle));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ESP_ERROR_CHECK(esp_timer_start_once(watchdog_handle, 500000));
|
|
|
|
|
feedWDog = true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-07 16:53:50 -06:00
|
|
|
void IRAM_ATTR Encoder::pauseWatchdog() {
|
|
|
|
|
if (watchdog_handle != nullptr) esp_timer_stop(watchdog_handle);
|
2025-12-31 15:44:48 -06:00
|
|
|
feedWDog = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Encoder::~Encoder() {
|
|
|
|
|
if (watchdog_handle != NULL) {
|
|
|
|
|
esp_timer_stop(watchdog_handle);
|
|
|
|
|
esp_timer_delete(watchdog_handle);
|
|
|
|
|
watchdog_handle = NULL;
|
|
|
|
|
}
|
2025-12-28 12:46:10 -06:00
|
|
|
}
|