adding rotary encoder + better jack gpio handling

This commit is contained in:
philippe44
2020-02-05 00:17:48 -08:00
parent 5f84dc3cb0
commit ed4eb6a42e
12 changed files with 752 additions and 66 deletions

View File

@@ -132,6 +132,8 @@ const static actrls_t controls = {
bt_pause, bt_stop, // pause, stop
NULL, NULL, // rew, fwd
bt_prev, bt_next, // prev, next
NULL, NULL, NULL, NULL, // left, right, up, down
bt_volume_down, bt_volume_up, bt_toggle// knob left, knob_right, knob push
};
/* disconnection */

View File

@@ -77,6 +77,8 @@ const static actrls_t controls = {
raop_pause, raop_stop, // pause, stop
NULL, NULL, // rew, fwd
raop_prev, raop_next, // prev, next
NULL, NULL, NULL, NULL, // left, right, up, down
raop_volume_down, raop_volume_up, raop_toggle// knob left, knob_right, knob push
};
/****************************************************************************************

View File

@@ -62,7 +62,9 @@ static const actrls_config_map_t actrls_config_map[] =
#define EP(x) [x] = #x /* ENUM PRINT */
static const char * actrls_action_s[ ] = { EP(ACTRLS_VOLUP),EP(ACTRLS_VOLDOWN),EP(ACTRLS_TOGGLE),EP(ACTRLS_PLAY),
EP(ACTRLS_PAUSE),EP(ACTRLS_STOP),EP(ACTRLS_REW),EP(ACTRLS_FWD),EP(ACTRLS_PREV),EP(ACTRLS_NEXT),
EP(BCTRLS_PUSH), EP(BCTRLS_UP),EP(BCTRLS_DOWN),EP(BCTRLS_LEFT),EP(BCTRLS_RIGHT), ""} ;
EP(BCTRLS_UP),EP(BCTRLS_DOWN),EP(BCTRLS_LEFT),EP(BCTRLS_RIGHT),
EP(KNOB_LEFT),EP(KNOB_RIGHT),EP(KNOB_PUSH),
""} ;
static const char * TAG = "audio controls";
static actrls_config_t *json_config;
@@ -119,6 +121,28 @@ static void control_handler(void *client, button_event_e event, button_press_e p
}
}
static void control_rotary_handler(void *client, rotary_event_e event, bool long_press) {
actrls_action_e action;
switch(event) {
case ROTARY_LEFT:
action = KNOB_LEFT;
break;
case ROTARY_RIGHT:
action = KNOB_RIGHT;
break;
case ROTARY_PRESSED:
// no handling of rotary long press
action = KNOB_PUSH;
break;
default:
action = ACTRLS_NONE;
break;
}
if (action != ACTRLS_NONE) (*current_controls[action])();
}
/*
void up(void *id, button_event_e event, button_press_e press, bool longpress) {
if (press == BUTTON_NORMAL) {
@@ -365,7 +389,25 @@ esp_err_t actrls_init_json(const char *profile_name, bool create) {
actrls_config_t *config_root = NULL;
const cJSON *button;
char *config = config_alloc_get_default(NVS_TYPE_STR, profile_name, NULL, 0);
char *config = config_alloc_get_default(NVS_TYPE_STR, "rotary_config", NULL, 0);
if (config && *config) {
char *p;
int A = -1, B = -1, SW = -1;
// parse config
if ((p = strcasestr(config, "A")) != NULL) A = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "B")) != NULL) B = atoi(strchr(p, '=') + 1);
if ((p = strcasestr(config, "SW")) != NULL) SW = atoi(strchr(p, '=') + 1);
// create rotary (no handling of long press)
err = create_rotary(NULL, A, B, SW, 0, control_rotary_handler) ? ESP_OK : ESP_FAIL;
}
if (config) free(config);
if (!profile_name || !*profile_name) return ESP_OK;
config = config_alloc_get_default(NVS_TYPE_STR, profile_name, NULL, 0);
if(!config) return ESP_FAIL;
ESP_LOGD(TAG,"Parsing JSON structure %s", config);

View File

@@ -23,8 +23,9 @@
// BEWARE: this is the index of the array of action below (change actrls_action_s as well!)
typedef enum { ACTRLS_NONE = -1, ACTRLS_VOLUP, ACTRLS_VOLDOWN, ACTRLS_TOGGLE, ACTRLS_PLAY,
ACTRLS_PAUSE, ACTRLS_STOP, ACTRLS_REW, ACTRLS_FWD, ACTRLS_PREV, ACTRLS_NEXT,
BCTRLS_PUSH, BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT, ACTRLS_REMAP,
ACTRLS_MAX
BCTRLS_UP, BCTRLS_DOWN, BCTRLS_LEFT, BCTRLS_RIGHT,
KNOB_LEFT, KNOB_RIGHT, KNOB_PUSH,
ACTRLS_REMAP, ACTRLS_MAX
} actrls_action_e;
typedef void (*actrls_handler)(void);

View File

@@ -31,6 +31,7 @@
#include "esp_task.h"
#include "driver/gpio.h"
#include "buttons.h"
#include "rotary_encoder.h"
#include "globdefs.h"
bool gpio36_39_used;
@@ -42,6 +43,7 @@ static int n_buttons = 0;
#define BUTTON_STACK_SIZE 4096
#define MAX_BUTTONS 16
#define DEBOUNCE 50
#define BUTTON_QUEUE_LEN 10
static EXT_RAM_ATTR struct button_s {
void *client;
@@ -56,7 +58,16 @@ static EXT_RAM_ATTR struct button_s {
TimerHandle_t timer;
} buttons[MAX_BUTTONS];
static xQueueHandle button_evt_queue = NULL;
static struct {
QueueHandle_t queue;
void *client;
rotary_encoder_info_t info;
int A, B, SW;
rotary_handler handler;
} rotary;
static xQueueHandle button_evt_queue;
static QueueSetHandle_t button_queue_set;
static void buttons_task(void* arg);
@@ -103,43 +114,62 @@ static void buttons_task(void* arg) {
ESP_LOGI(TAG, "starting button tasks");
while (1) {
struct button_s button;
button_event_e event;
button_press_e press;
QueueSetMemberHandle_t xActivatedMember;
if (!xQueueReceive(button_evt_queue, &button, portMAX_DELAY)) continue;
// wait on button and rotary queues
if ((xActivatedMember = xQueueSelectFromSet( button_queue_set, portMAX_DELAY )) == NULL) continue;
event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;
if (xActivatedMember == button_evt_queue) {
struct button_s button;
button_event_e event;
button_press_e press;
ESP_LOGD(TAG, "received event:%u from gpio:%u level:%u (timer %u shifting %u)", event, button.gpio, button.level, button.long_timer, button.shifting);
// received a button event
xQueueReceive(button_evt_queue, &button, 0);
// find if shifting is activated
if (button.shifter && button.shifter->type == button.shifter->level) press = BUTTON_SHIFTED;
else press = BUTTON_NORMAL;
event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;
/*
long_timer will be set either because we truly have a long press
or we have a release before the long press timer elapsed, so two
events shall be sent
*/
if (button.long_timer) {
if (event == BUTTON_RELEASED) {
// early release of a long-press button, send press/release
if (!button.shifting) {
(*button.handler)(button.client, BUTTON_PRESSED, press, false);
(*button.handler)(button.client, BUTTON_RELEASED, press, false);
ESP_LOGD(TAG, "received event:%u from gpio:%u level:%u (timer %u shifting %u)", event, button.gpio, button.level, button.long_timer, button.shifting);
// find if shifting is activated
if (button.shifter && button.shifter->type == button.shifter->level) press = BUTTON_SHIFTED;
else press = BUTTON_NORMAL;
/*
long_timer will be set either because we truly have a long press
or we have a release before the long press timer elapsed, so two
events shall be sent
*/
if (button.long_timer) {
if (event == BUTTON_RELEASED) {
// early release of a long-press button, send press/release
if (!button.shifting) {
(*button.handler)(button.client, BUTTON_PRESSED, press, false);
(*button.handler)(button.client, BUTTON_RELEASED, press, false);
}
// button is a copy, so need to go to real context
button.self->shifting = false;
} else if (!button.shifting) {
// normal long press and not shifting so don't discard
(*button.handler)(button.client, BUTTON_PRESSED, press, true);
}
} else {
// normal press/release of a button or release of a long-press button
if (!button.shifting) (*button.handler)(button.client, event, press, button.long_press);
// button is a copy, so need to go to real context
button.self->shifting = false;
} else if (!button.shifting) {
// normal long press and not shifting so don't discard
(*button.handler)(button.client, BUTTON_PRESSED, press, true);
}
} else {
// normal press/release of a button or release of a long-press button
if (!button.shifting) (*button.handler)(button.client, event, press, button.long_press);
// button is a copy, so need to go to real context
button.self->shifting = false;
rotary_encoder_event_t event = { 0 };
// received a rotary event
xQueueReceive(rotary.queue, &event, 0);
ESP_LOGI(TAG, "Event: position %d, direction %s", event.state.position,
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
rotary.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ?
ROTARY_RIGHT : ROTARY_LEFT, false);
}
}
}
@@ -163,7 +193,9 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
ESP_LOGI(TAG, "Creating button using GPIO %u, type %u, pull-up/down %u, long press %u shifter %u", gpio, type, pull, long_press, shifter_gpio);
if (!n_buttons) {
button_evt_queue = xQueueCreate(10, sizeof(struct button_s));
button_evt_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
if (!button_queue_set) button_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
xQueueAddToSet( button_evt_queue, button_queue_set );
xTaskCreateStatic( (TaskFunction_t) buttons_task, "buttons_thread", BUTTON_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
}
@@ -229,7 +261,18 @@ void *button_get_client(int gpio) {
if (buttons[i].gpio == gpio) return buttons[i].client;
}
return NULL;
}
}
/****************************************************************************************
* Get stored id
*/
bool button_is_pressed(int gpio, void *client) {
for (int i = 0; i < n_buttons; i++) {
if (gpio != -1 && buttons[i].gpio == gpio) return buttons[i].level == buttons[i].type;
else if (client && buttons[i].client == client) return buttons[i].level == buttons[i].type;
}
return false;
}
/****************************************************************************************
* Update buttons
@@ -270,3 +313,47 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
return prev_client;
}
/****************************************************************************************
* Create rotary encoder
*/
static void rotary_button_handler(void *id, button_event_e event, button_press_e mode, bool long_press) {
ESP_LOGI(TAG, "Rotary push-button %d", event);
rotary.handler(id, event == BUTTON_PRESSED ? ROTARY_PRESSED : ROTARY_RELEASED, long_press);
}
/****************************************************************************************
* Create rotary encoder
*/
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler) {
if (A == -1 || B == -1) {
ESP_LOGI(TAG, "Cannot create rotary %d %d", A, B);
return false;
}
rotary.A = A;
rotary.B = B;
rotary.SW = SW;
rotary.client = id;
rotary.handler = handler;
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
if (A == 36 || A == 39 || B == 36 || B == 39 || SW == 36 || SW == 39) gpio36_39_used = true;
// Initialise the rotary encoder device with the GPIOs for A and B signals
rotary_encoder_init(&rotary.info, A, B);
// Create a queue for events from the rotary encoder driver.
rotary.queue = rotary_encoder_create_queue();
rotary_encoder_set_queue(&rotary.info, rotary.queue);
if (!button_queue_set) button_queue_set = xQueueCreateSet(BUTTON_QUEUE_LEN + 1);
xQueueAddToSet( rotary.queue, button_queue_set );
// create companion button if rotary has a switch
if (SW != -1) button_create(id, SW, BUTTON_LOW, true, 0, rotary_button_handler, long_press, -1);
ESP_LOGI(TAG, "Creating rotary encoder A:%d B:%d, SW:%d", A, B, SW);
return true;
}

View File

@@ -18,7 +18,7 @@
#pragma once
// button type (pressed = LOW or HIGH)
// button type (pressed = LOW or HIGH, matches GPIO level)
#define BUTTON_LOW 0
#define BUTTON_HIGH 1
@@ -36,3 +36,9 @@ NOTE: shifter buttons *must* be created before shiftee
void button_create(void *client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio);
void *button_remap(void *client, int gpio, button_handler handler, int long_press, int shifter_gpio);
void *button_get_client(int gpio);
bool button_is_pressed(int gpio, void *client);
typedef enum { ROTARY_LEFT, ROTARY_RIGHT, ROTARY_PRESSED, ROTARY_RELEASED } rotary_event_e;
typedef void (*rotary_handler)(void *id, rotary_event_e event, bool long_press);
bool create_rotary(void *id, int A, int B, int SW, int long_press, rotary_handler handler);

View File

@@ -20,12 +20,17 @@
#include "led.h"
#include "globdefs.h"
#include "config.h"
#include "accessors.h"
#define MONITOR_TIMER (10*1000)
static const char *TAG = "monitor";
static TimerHandle_t monitor_timer;
#ifdef JACK_GPIO
static int jack_gpio = JACK_GPIO;
#else
static int jack_gpio = -1;
#endif
void (*jack_handler_svc)(bool inserted);
bool jack_inserted_svc(void);
@@ -56,11 +61,8 @@ static void jack_handler_default(void *id, button_event_e event, button_press_e
*
*/
bool jack_inserted_svc (void) {
#ifdef JACK_GPIO
return !gpio_get_level(JACK_GPIO);
#else
return false;
#endif
if (jack_gpio != -1) return button_is_pressed(jack_gpio, NULL);
else return false;
}
/****************************************************************************************
@@ -88,15 +90,25 @@ bool spkfault_svc (void) {
*
*/
void set_jack_gpio(int gpio, char *value) {
if (!strcasecmp(value, "jack")) {
ESP_LOGI(TAG,"Adding jack detection GPIO %d", gpio);
bool low = false;
gpio_pad_select_gpio(JACK_GPIO);
gpio_set_direction(JACK_GPIO, GPIO_MODE_INPUT);
if (!strcasecmp(value, "jack_l")) {
jack_gpio = gpio;
low = true;
} else if (!strcasecmp(value, "jack_h")) {
jack_gpio = gpio;
}
if (jack_gpio != -1) {
gpio_pad_select_gpio(jack_gpio);
gpio_set_direction(jack_gpio, GPIO_MODE_INPUT);
gpio_set_pull_mode(jack_gpio, low ? GPIO_PULLUP_ONLY : GPIO_PULLDOWN_ONLY);
ESP_LOGI(TAG,"Adding jack (%s) detection GPIO %d", low ? "low" : "high", gpio);
// re-use button management for jack handler, it's a GPIO after all
button_create(NULL, JACK_GPIO, BUTTON_LOW, false, 250, jack_handler_default, 0, -1);
}
button_create(NULL, jack_gpio, low ? BUTTON_LOW : BUTTON_HIGH, false, 250, jack_handler_default, 0, -1);
}
}
/****************************************************************************************
@@ -105,11 +117,16 @@ void set_jack_gpio(int gpio, char *value) {
void monitor_svc_init(void) {
ESP_LOGI(TAG, "Initializing monitoring");
#if !defined(JACK_GPIO) || JACK_GPIO == -1
parse_set_GPIO(set_jack_gpio);
// if JACK_GPIO is compiled-time defined set it there
if (jack_gpio != -1) {
#if JACK_GPIO_LEVEL == 1
set_jack_gpio(JACK_GPIO, "jack_h");
#else
set_jack_gpio(JACK_GPIO, "jack");
set_jack_gpio(JACK_GPIO, "jack_l");
#endif
} else {
parse_set_GPIO(set_jack_gpio);
}
#ifdef SPKFAULT_GPIO
gpio_pad_select_gpio(SPKFAULT_GPIO);

View File

@@ -0,0 +1,345 @@
/*
* Copyright (c) 2019 David Antliff
* Copyright 2011 Ben Buxton
*
* This file is part of the esp32-rotary-encoder component.
*
* esp32-rotary-encoder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* esp32-rotary-encoder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with esp32-rotary-encoder. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* @file rotary_encoder.c
* @brief Driver implementation for the ESP32-compatible Incremental Rotary Encoder component.
*
* Based on https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
* Original header follows:
*
* Rotary encoder handler for arduino. v1.1
*
* Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
* Contact: bb@cactii.net
*
* A typical mechanical rotary encoder emits a two bit gray code
* on 3 output pins. Every step in the output (often accompanied
* by a physical 'click') generates a specific sequence of output
* codes on the pins.
*
* There are 3 pins used for the rotary encoding - one common and
* two 'bit' pins.
*
* The following is the typical sequence of code on the output when
* moving from one step to the next:
*
* Position Bit1 Bit2
* ----------------------
* Step1 0 0
* 1/4 1 0
* 1/2 1 1
* 3/4 0 1
* Step2 0 0
*
* From this table, we can see that when moving from one 'click' to
* the next, there are 4 changes in the output code.
*
* - From an initial 0 - 0, Bit1 goes high, Bit0 stays low.
* - Then both bits are high, halfway through the step.
* - Then Bit1 goes low, but Bit2 stays high.
* - Finally at the end of the step, both bits return to 0.
*
* Detecting the direction is easy - the table simply goes in the other
* direction (read up instead of down).
*
* To decode this, we use a simple state machine. Every time the output
* code changes, it follows state, until finally a full steps worth of
* code is received (in the correct order). At the final 0-0, it returns
* a value indicating a step in one direction or the other.
*
* It's also possible to use 'half-step' mode. This just emits an event
* at both the 0-0 and 1-1 positions. This might be useful for some
* encoders where you want to detect all positions.
*
* If an invalid state happens (for example we go from '0-1' straight
* to '1-0'), the state machine resets to the start until 0-0 and the
* next valid codes occur.
*
* The biggest advantage of using a state machine over other algorithms
* is that this has inherent debounce built in. Other algorithms emit spurious
* output with switch bounce, but this one will simply flip between
* sub-states until the bounce settles, then continue along the state
* machine.
* A side effect of debounce is that fast rotations can cause steps to
* be skipped. By not requiring debounce, fast rotations can be accurately
* measured.
* Another advantage is the ability to properly handle bad state, such
* as due to EMI, etc.
* It is also a lot simpler than others - a static state table and less
* than 10 lines of logic.
*/
#include "rotary_encoder.h"
#include "esp_log.h"
#include "driver/gpio.h"
#define TAG "rotary_encoder"
//#define ROTARY_ENCODER_DEBUG
// Use a single-item queue so that the last value can be easily overwritten by the interrupt handler
#define EVENT_QUEUE_LENGTH 1
#define TABLE_ROWS 7
#define DIR_NONE 0x0 // No complete step yet.
#define DIR_CW 0x10 // Clockwise step.
#define DIR_CCW 0x20 // Anti-clockwise step.
// Create the half-step state table (emits a code at 00 and 11)
#define R_START 0x0
#define H_CCW_BEGIN 0x1
#define H_CW_BEGIN 0x2
#define H_START_M 0x3
#define H_CW_BEGIN_M 0x4
#define H_CCW_BEGIN_M 0x5
static const uint8_t _ttable_half[TABLE_ROWS][TABLE_COLS] = {
// 00 01 10 11 // BA
{H_START_M, H_CW_BEGIN, H_CCW_BEGIN, R_START}, // R_START (00)
{H_START_M | DIR_CCW, R_START, H_CCW_BEGIN, R_START}, // H_CCW_BEGIN
{H_START_M | DIR_CW, H_CW_BEGIN, R_START, R_START}, // H_CW_BEGIN
{H_START_M, H_CCW_BEGIN_M, H_CW_BEGIN_M, R_START}, // H_START_M (11)
{H_START_M, H_START_M, H_CW_BEGIN_M, R_START | DIR_CW}, // H_CW_BEGIN_M
{H_START_M, H_CCW_BEGIN_M, H_START_M, R_START | DIR_CCW}, // H_CCW_BEGIN_M
};
// Create the full-step state table (emits a code at 00 only)
# define F_CW_FINAL 0x1
# define F_CW_BEGIN 0x2
# define F_CW_NEXT 0x3
# define F_CCW_BEGIN 0x4
# define F_CCW_FINAL 0x5
# define F_CCW_NEXT 0x6
static const uint8_t _ttable_full[TABLE_ROWS][TABLE_COLS] = {
// 00 01 10 11 // BA
{R_START, F_CW_BEGIN, F_CCW_BEGIN, R_START}, // R_START
{F_CW_NEXT, R_START, F_CW_FINAL, R_START | DIR_CW}, // F_CW_FINAL
{F_CW_NEXT, F_CW_BEGIN, R_START, R_START}, // F_CW_BEGIN
{F_CW_NEXT, F_CW_BEGIN, F_CW_FINAL, R_START}, // F_CW_NEXT
{F_CCW_NEXT, R_START, F_CCW_BEGIN, R_START}, // F_CCW_BEGIN
{F_CCW_NEXT, F_CCW_FINAL, R_START, R_START | DIR_CCW}, // F_CCW_FINAL
{F_CCW_NEXT, F_CCW_FINAL, F_CCW_BEGIN, R_START}, // F_CCW_NEXT
};
static uint8_t _process(rotary_encoder_info_t * info)
{
uint8_t event = 0;
if (info != NULL)
{
// Get state of input pins.
uint8_t pin_state = (gpio_get_level(info->pin_b) << 1) | gpio_get_level(info->pin_a);
// Determine new state from the pins and state table.
#ifdef ROTARY_ENCODER_DEBUG
uint8_t old_state = info->table_state;
#endif
info->table_state = info->table[info->table_state & 0xf][pin_state];
// Return emit bits, i.e. the generated event.
event = info->table_state & 0x30;
#ifdef ROTARY_ENCODER_DEBUG
ESP_EARLY_LOGD(TAG, "BA %d%d, state 0x%02x, new state 0x%02x, event 0x%02x",
pin_state >> 1, pin_state & 1, old_state, info->table_state, event);
#endif
}
return event;
}
static void _isr_rotenc(void * args)
{
rotary_encoder_info_t * info = (rotary_encoder_info_t *)args;
uint8_t event = _process(info);
bool send_event = false;
switch (event)
{
case DIR_CW:
++info->state.position;
info->state.direction = ROTARY_ENCODER_DIRECTION_CLOCKWISE;
send_event = true;
break;
case DIR_CCW:
--info->state.position;
info->state.direction = ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE;
send_event = true;
break;
default:
break;
}
if (send_event && info->queue)
{
rotary_encoder_event_t queue_event =
{
.state =
{
.position = info->state.position,
.direction = info->state.direction,
},
};
BaseType_t task_woken = pdFALSE;
xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
if (task_woken)
{
portYIELD_FROM_ISR();
}
}
}
esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b)
{
esp_err_t err = ESP_OK;
if (info)
{
info->pin_a = pin_a;
info->pin_b = pin_b;
info->table = &_ttable_full[0]; //enable_half_step ? &_ttable_half[0] : &_ttable_full[0];
info->table_state = R_START;
info->state.position = 0;
info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
// configure GPIOs
gpio_pad_select_gpio(info->pin_a);
gpio_set_pull_mode(info->pin_a, GPIO_PULLUP_ONLY);
gpio_set_direction(info->pin_a, GPIO_MODE_INPUT);
gpio_set_intr_type(info->pin_a, GPIO_INTR_ANYEDGE);
gpio_pad_select_gpio(info->pin_b);
gpio_set_pull_mode(info->pin_b, GPIO_PULLUP_ONLY);
gpio_set_direction(info->pin_b, GPIO_MODE_INPUT);
gpio_set_intr_type(info->pin_b, GPIO_INTR_ANYEDGE);
// install interrupt handlers
gpio_isr_handler_add(info->pin_a, _isr_rotenc, info);
gpio_isr_handler_add(info->pin_b, _isr_rotenc, info);
}
else
{
ESP_LOGE(TAG, "info is NULL");
err = ESP_ERR_INVALID_ARG;
}
return err;
}
esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable)
{
esp_err_t err = ESP_OK;
if (info)
{
info->table = enable ? &_ttable_half[0] : &_ttable_full[0];
info->table_state = R_START;
}
else
{
ESP_LOGE(TAG, "info is NULL");
err = ESP_ERR_INVALID_ARG;
}
return err;
}
esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info)
{
esp_err_t err = ESP_OK;
if (info)
{
gpio_num_t temp = info->pin_a;
info->pin_a = info->pin_b;
info->pin_b = temp;
}
else
{
ESP_LOGE(TAG, "info is NULL");
err = ESP_ERR_INVALID_ARG;
}
return err;
}
esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info)
{
esp_err_t err = ESP_OK;
if (info)
{
gpio_isr_handler_remove(info->pin_a);
gpio_isr_handler_remove(info->pin_b);
}
else
{
ESP_LOGE(TAG, "info is NULL");
err = ESP_ERR_INVALID_ARG;
}
return err;
}
QueueHandle_t rotary_encoder_create_queue(void)
{
return xQueueCreate(EVENT_QUEUE_LENGTH, sizeof(rotary_encoder_event_t));
}
esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue)
{
esp_err_t err = ESP_OK;
if (info)
{
info->queue = queue;
}
else
{
ESP_LOGE(TAG, "info is NULL");
err = ESP_ERR_INVALID_ARG;
}
return err;
}
esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state)
{
esp_err_t err = ESP_OK;
if (info && state)
{
// make a snapshot of the state
state->position = info->state.position;
state->direction = info->state.direction;
}
else
{
ESP_LOGE(TAG, "info and/or state is NULL");
err = ESP_ERR_INVALID_ARG;
}
return err;
}
esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info)
{
esp_err_t err = ESP_OK;
if (info)
{
info->state.position = 0;
info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
}
else
{
ESP_LOGE(TAG, "info is NULL");
err = ESP_ERR_INVALID_ARG;
}
return err;
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (c) 2019 David Antliff
* Copyright 2011 Ben Buxton
*
* This file is part of the esp32-rotary-encoder component.
*
* esp32-rotary-encoder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* esp32-rotary-encoder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with esp32-rotary-encoder. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* @file rotary_encoder.h
* @brief Interface definitions for the ESP32-compatible Incremental Rotary Encoder component.
*
* This component provides a means to interface with a typical rotary encoder such as the EC11 or LPD3806.
* These encoders produce a quadrature signal on two outputs, which can be used to track the position and
* direction as movement occurs.
*
* This component provides functions to initialise the GPIOs and install appropriate interrupt handlers to
* track a single device's position. An event queue is used to provide a way for a user task to obtain
* position information from the component as it is generated.
*
* Note that the queue is of length 1, and old values will be overwritten. Using a longer queue is
* possible with some minor modifications however newer values are lost if the queue overruns. A circular
* buffer where old values are lost would be better (maybe StreamBuffer in FreeRTOS 10.0.0?).
*/
#ifndef ROTARY_ENCODER_H
#define ROTARY_ENCODER_H
#include <stdbool.h>
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "esp_err.h"
#include "driver/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef int32_t rotary_encoder_position_t;
/**
* @brief Enum representing the direction of rotation.
*/
typedef enum
{
ROTARY_ENCODER_DIRECTION_NOT_SET = 0, ///< Direction not yet known (stationary since reset)
ROTARY_ENCODER_DIRECTION_CLOCKWISE,
ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE,
} rotary_encoder_direction_t;
// Used internally
///@cond INTERNAL
#define TABLE_COLS 4
typedef uint8_t table_row_t[TABLE_COLS];
///@endcond
/**
* @brief Struct represents the current state of the device in terms of incremental position and direction of last movement
*/
typedef struct
{
rotary_encoder_position_t position; ///< Numerical position since reset. This value increments on clockwise rotation, and decrements on counter-clockewise rotation. Counts full or half steps depending on mode. Set to zero on reset.
rotary_encoder_direction_t direction; ///< Direction of last movement. Set to NOT_SET on reset.
} rotary_encoder_state_t;
/**
* @brief Struct carries all the information needed by this driver to manage the rotary encoder device.
* The fields of this structure should not be accessed directly.
*/
typedef struct
{
gpio_num_t pin_a; ///< GPIO for Signal A from the rotary encoder device
gpio_num_t pin_b; ///< GPIO for Signal B from the rotary encoder device
QueueHandle_t queue; ///< Handle for event queue, created by ::rotary_encoder_create_queue
const table_row_t * table; ///< Pointer to active state transition table
uint8_t table_state; ///< Internal state
volatile rotary_encoder_state_t state; ///< Device state
} rotary_encoder_info_t;
/**
* @brief Struct represents a queued event, used to communicate current position to a waiting task
*/
typedef struct
{
rotary_encoder_state_t state; ///< The device state corresponding to this event
} rotary_encoder_event_t;
/**
* @brief Initialise the rotary encoder device with the specified GPIO pins and full step increments.
* This function will set up the GPIOs as needed,
* Note: this function assumes that gpio_install_isr_service(0) has already been called.
* @param[in, out] info Pointer to allocated rotary encoder info structure.
* @param[in] pin_a GPIO number for rotary encoder output A.
* @param[in] pin_b GPIO number for rotary encoder output B.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b);
/**
* @brief Enable half-stepping mode. This generates twice as many counted steps per rotation.
* @param[in] info Pointer to initialised rotary encoder info structure.
* @param[in] enable If true, count half steps. If false, only count full steps.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable);
/**
* @brief Reverse (flip) the sense of the direction.
* Use this if clockwise/counterclockwise are not what you expect.
* @param[in] info Pointer to initialised rotary encoder info structure.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info);
/**
* @brief Remove the interrupt handlers installed by ::rotary_encoder_init.
* Note: GPIOs will be left in the state they were configured by ::rotary_encoder_init.
* @param[in] info Pointer to initialised rotary encoder info structure.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info);
/**
* @brief Create a queue handle suitable for use as an event queue.
* @return A handle to a new queue suitable for use as an event queue.
*/
QueueHandle_t rotary_encoder_create_queue(void);
/**
* @brief Set the driver to use the specified queue as an event queue.
* It is recommended that a queue constructed by ::rotary_encoder_create_queue is used.
* @param[in] info Pointer to initialised rotary encoder info structure.
* @param[in] queue Handle to queue suitable for use as an event queue. See ::rotary_encoder_create_queue.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue);
/**
* @brief Get the current position of the rotary encoder.
* @param[in] info Pointer to initialised rotary encoder info structure.
* @param[in, out] state Pointer to an allocated rotary_encoder_state_t struct that will
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state);
/**
* @brief Reset the current position of the rotary encoder to zero.
* @param[in] info Pointer to initialised rotary encoder info structure.
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
*/
esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info);
#ifdef __cplusplus
}
#endif
#endif // ROTARY_ENCODER_H

View File

@@ -85,7 +85,15 @@ static void lms_right(void) {
cli_send_cmd("button arrow_right");
}
static void lms_push(void) {
static void lms_knob_left(void) {
cli_send_cmd("button knob_left");
}
static void lms_knob_right(void) {
cli_send_cmd("button knob_right");
}
static void lms_knob_push(void) {
cli_send_cmd("button knob_push");
}
@@ -95,9 +103,9 @@ const actrls_t LMS_controls = {
lms_pause, lms_stop, // pause, stop
lms_rew, lms_fwd, // rew, fwd
lms_prev, lms_next, // prev, next
lms_push,
lms_up, lms_down,
lms_left, lms_right,
lms_knob_left, lms_knob_right, lms_knob_push,
};
/****************************************************************************************

View File

@@ -207,7 +207,11 @@ menu "Squeezelite-ESP32"
int "Jack insertion GPIO"
default -1
help
GPIO to detect speaker jack insertion (0 = inserted). Set to -1 for no detection
GPIO to detect speaker jack insertion. Set to -1 for no detection
config JACK_GPIO_LEVEL
depends on JACK_GPIO != -1
int "Level when inserted (0/1)"
default 0
endmenu
endmenu

View File

@@ -282,6 +282,9 @@ void register_default_nvs(){
ESP_LOGD(TAG,"Registering default Audio control board type %s, value ","actrls_config");
config_set_default(NVS_TYPE_STR, "actrls_config", "", 0);
ESP_LOGD(TAG,"Registering default Audio control board type %s, value ","rotary_config");
config_set_default(NVS_TYPE_STR, "rotary_config", "", 0);
char number_buffer[101] = {};
snprintf(number_buffer,sizeof(number_buffer)-1,"%u",OTA_FLASH_ERASE_BLOCK);
ESP_LOGD(TAG,"Registering default value for key %s, value %s", "ota_erase_blk", number_buffer);
@@ -375,15 +378,12 @@ void app_main()
ESP_LOGD(TAG,"Getting audio control mapping ");
char *actrls_config = config_alloc_get_default(NVS_TYPE_STR, "actrls_config", NULL, 0);
if (actrls_config) {
if(actrls_config[0] !='\0'){
ESP_LOGD(TAG,"Initializing audio control buttons type %s", actrls_config);
actrls_init_json(actrls_config, true);
}
free(actrls_config);
if (actrls_init_json(actrls_config, true) == ESP_OK) {
ESP_LOGD(TAG,"Initializing audio control buttons type %s", actrls_config);
} else {
ESP_LOGD(TAG,"No audio control buttons");
}
if (actrls_config) free(actrls_config);
/* start the wifi manager */
ESP_LOGD(TAG,"Blinking led");