mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-08 20:47:08 +03:00
first commit of GPIO expander
This commit is contained in:
@@ -11,6 +11,7 @@ CONFIG_DISPLAY_CONFIG=""
|
||||
CONFIG_I2C_CONFIG=""
|
||||
CONFIG_SPI_CONFIG=""
|
||||
CONFIG_SET_GPIO=""
|
||||
CONFIG_GPIO_EXP_CONFIG=""
|
||||
CONFIG_ROTARY_ENCODER=""
|
||||
CONFIG_LED_GREEN_GPIO=-1
|
||||
CONFIG_LED_GREEN_GPIO_LEVEL=1
|
||||
|
||||
@@ -12,6 +12,7 @@ CONFIG_DISPLAY_CONFIG=""
|
||||
CONFIG_I2C_CONFIG=""
|
||||
CONFIG_SPI_CONFIG=""
|
||||
CONFIG_SET_GPIO=""
|
||||
CONFIG_GPIO_EXP_CONFIG=""
|
||||
CONFIG_ROTARY_ENCODER=""
|
||||
CONFIG_LED_GREEN_GPIO=-1
|
||||
CONFIG_LED_GREEN_GPIO_LEVEL=1
|
||||
|
||||
@@ -16,6 +16,7 @@ CONFIG_DISPLAY_CONFIG=""
|
||||
CONFIG_I2C_CONFIG=""
|
||||
CONFIG_SPI_CONFIG=""
|
||||
CONFIG_SET_GPIO=""
|
||||
CONFIG_GPIO_EXP_CONFIG=""
|
||||
CONFIG_ROTARY_ENCODER=""
|
||||
CONFIG_LED_GREEN_GPIO=12
|
||||
CONFIG_LED_GREEN_GPIO_LEVEL=0
|
||||
|
||||
@@ -482,6 +482,35 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
|
||||
return &i2c;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Get IO expander config structure from config string
|
||||
*/
|
||||
const gpio_exp_config_t* config_gpio_exp_get(void) {
|
||||
char *nvs_item, *p;
|
||||
static gpio_exp_config_t config = {
|
||||
.intr = -1,
|
||||
.count = 8,
|
||||
};
|
||||
config.phy.port = i2c_system_port;
|
||||
|
||||
nvs_item = config_alloc_get(NVS_TYPE_STR, "gpio_exp_config");
|
||||
if (!nvs_item) return NULL;
|
||||
|
||||
if ((p = strcasestr(nvs_item, "addr")) != NULL) config.phy.addr = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "intr")) != NULL) config.intr = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "base")) != NULL) config.base = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "count")) != NULL) config.count = atoi(strchr(p, '=') + 1);
|
||||
if ((p = strcasestr(nvs_item, "model")) != NULL) sscanf(p, "%*[^=]=%31[^,]", config.model);
|
||||
if ((p = strcasestr(nvs_item, "port")) != NULL) {
|
||||
char port[8] = "";
|
||||
sscanf(p, "%*[^=]=%7[^,]", port);
|
||||
if (strcasestr(port, "dac")) config.phy.port = 0;
|
||||
}
|
||||
|
||||
free(nvs_item);
|
||||
return &config;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -12,8 +12,11 @@
|
||||
#include "driver/i2c.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "gpio_exp.h"
|
||||
|
||||
extern const char *i2c_name_type;
|
||||
extern const char *spi_name_type;
|
||||
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
@@ -92,6 +95,7 @@ esp_err_t config_i2s_set(const i2s_platform_config_t * config, const char *
|
||||
esp_err_t config_spi_set(const spi_bus_config_t * config, int host, int dc);
|
||||
const i2c_config_t * config_i2c_get(int * i2c_port);
|
||||
const spi_bus_config_t * config_spi_get(spi_host_device_t * spi_host);
|
||||
const gpio_exp_config_t * config_gpio_exp_get(void);
|
||||
void parse_set_GPIO(void (*cb)(int gpio, char *value));
|
||||
const i2s_platform_config_t * config_dac_get();
|
||||
const i2s_platform_config_t * config_spdif_get( );
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "esp_task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/rmt.h"
|
||||
#include "gpio_exp.h"
|
||||
#include "buttons.h"
|
||||
#include "rotary_encoder.h"
|
||||
#include "globdefs.h"
|
||||
@@ -68,10 +69,12 @@ static EXT_RAM_ATTR struct {
|
||||
infrared_handler handler;
|
||||
} infrared;
|
||||
|
||||
static xQueueHandle button_evt_queue;
|
||||
static QueueHandle_t button_queue, button_exp_queue;
|
||||
static TimerHandle_t button_exp_timer;
|
||||
static QueueSetHandle_t common_queue_set;
|
||||
|
||||
static void buttons_task(void* arg);
|
||||
static void buttons_handler(struct button_s *button, int level);
|
||||
|
||||
/****************************************************************************************
|
||||
* Start task needed by button,s rotaty and infrared
|
||||
@@ -87,7 +90,7 @@ static void common_task_init(void) {
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* GPIO low-level handler
|
||||
* GPIO low-level ISR handler
|
||||
*/
|
||||
static void IRAM_ATTR gpio_isr_handler(void* arg)
|
||||
{
|
||||
@@ -103,23 +106,37 @@ static void IRAM_ATTR gpio_isr_handler(void* arg)
|
||||
/****************************************************************************************
|
||||
* Buttons debounce/longpress timer
|
||||
*/
|
||||
static void buttons_timer( TimerHandle_t xTimer ) {
|
||||
static void buttons_timer_handler( TimerHandle_t xTimer ) {
|
||||
struct button_s *button = (struct button_s*) pvTimerGetTimerID (xTimer);
|
||||
buttons_handler(button, gpio_get_level(button->gpio));
|
||||
}
|
||||
|
||||
button->level = gpio_get_level(button->gpio);
|
||||
if (button->shifter && button->shifter->type == button->shifter->level) button->shifter->shifting = true;
|
||||
/****************************************************************************************
|
||||
* GPIO expander low-level ISR handler
|
||||
*/
|
||||
static BaseType_t IRAM_ATTR gpio_exp_isr_handler(void* arg)
|
||||
{
|
||||
BaseType_t woken = pdFALSE;
|
||||
xTimerResetFromISR((TimerHandle_t) arg, &woken);
|
||||
return woken;
|
||||
}
|
||||
|
||||
if (button->long_press && !button->long_timer && button->level == button->type) {
|
||||
// detect a long press, so hold event generation
|
||||
ESP_LOGD(TAG, "setting long timer gpio:%u level:%u", button->gpio, button->level);
|
||||
xTimerChangePeriod(xTimer, button->long_press / portTICK_RATE_MS, 0);
|
||||
button->long_timer = true;
|
||||
} else {
|
||||
// send a button pressed/released event (content is copied in queue)
|
||||
ESP_LOGD(TAG, "sending event for gpio:%u level:%u", button->gpio, button->level);
|
||||
// queue will have a copy of button's context
|
||||
xQueueSend(button_evt_queue, button, 0);
|
||||
button->long_timer = false;
|
||||
/****************************************************************************************
|
||||
* Buttons expander debounce timer
|
||||
*/
|
||||
static void buttons_exp_timer_handler( TimerHandle_t xTimer ) {
|
||||
struct gpio_exp_s *expander = (struct gpio_exp_s*) pvTimerGetTimerID (xTimer);
|
||||
xQueueSend(button_exp_queue, &expander, 0);
|
||||
ESP_LOGI(TAG, "Button expander base %u debounced", gpio_exp_base(expander));
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Buttons expander enumerator
|
||||
*/
|
||||
static void buttons_exp_enumerator(int gpio, int level, struct gpio_exp_s *expander) {
|
||||
for (int i = 0; i < n_buttons; i++) if (buttons[i].gpio == gpio) {
|
||||
buttons_handler(buttons + i, level);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,11 +151,33 @@ static void buttons_polling( TimerHandle_t xTimer ) {
|
||||
|
||||
if (level != polled_gpio[i].level) {
|
||||
polled_gpio[i].level = level;
|
||||
buttons_timer(polled_gpio[i].button->timer);
|
||||
buttons_handler(polled_gpio[i].button, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Buttons timer handler for press/longpress
|
||||
*/
|
||||
static void buttons_handler(struct button_s *button, int level) {
|
||||
button->level = level;
|
||||
|
||||
if (button->shifter && button->shifter->type == button->shifter->level) button->shifter->shifting = true;
|
||||
|
||||
if (button->long_press && !button->long_timer && button->level == button->type) {
|
||||
// detect a long press, so hold event generation
|
||||
ESP_LOGD(TAG, "setting long timer gpio:%u level:%u", button->gpio, button->level);
|
||||
xTimerChangePeriod(button->timer, button->long_press / portTICK_RATE_MS, 0);
|
||||
button->long_timer = true;
|
||||
} else {
|
||||
// send a button pressed/released event (content is copied in queue)
|
||||
ESP_LOGD(TAG, "sending event for gpio:%u level:%u", button->gpio, button->level);
|
||||
// queue will have a copy of button's context
|
||||
xQueueSend(button_queue, button, 0);
|
||||
button->long_timer = false;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Tasks that calls the appropriate functions when buttons are pressed
|
||||
*/
|
||||
@@ -151,13 +190,13 @@ static void buttons_task(void* arg) {
|
||||
// wait on button, rotary and infrared queues
|
||||
if ((xActivatedMember = xQueueSelectFromSet( common_queue_set, portMAX_DELAY )) == NULL) continue;
|
||||
|
||||
if (xActivatedMember == button_evt_queue) {
|
||||
if (xActivatedMember == button_queue) {
|
||||
struct button_s button;
|
||||
button_event_e event;
|
||||
button_press_e press;
|
||||
|
||||
// received a button event
|
||||
xQueueReceive(button_evt_queue, &button, 0);
|
||||
xQueueReceive(button_queue, &button, 0);
|
||||
|
||||
event = (button.level == button.type) ? BUTTON_PRESSED : BUTTON_RELEASED;
|
||||
|
||||
@@ -176,21 +215,29 @@ static void buttons_task(void* arg) {
|
||||
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.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);
|
||||
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);
|
||||
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 (xActivatedMember == button_exp_queue) {
|
||||
struct gpio_exp_s *expander;
|
||||
/*
|
||||
we are not there yet, this is just a notice of a debounce, we need to enumerate
|
||||
GPIOs and let buttons_handler take care of longpress & al
|
||||
*/
|
||||
xQueueReceive(button_exp_queue, &expander, 0);
|
||||
gpio_exp_enumerate(buttons_exp_enumerator, expander);
|
||||
} else if (xActivatedMember == rotary.queue) {
|
||||
rotary_encoder_event_t event = { 0 };
|
||||
|
||||
@@ -225,9 +272,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 %d", gpio, type, pull, long_press, shifter_gpio);
|
||||
|
||||
if (!n_buttons) {
|
||||
button_evt_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
|
||||
button_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct button_s));
|
||||
common_task_init();
|
||||
xQueueAddToSet( button_evt_queue, common_queue_set );
|
||||
xQueueAddToSet( button_queue, common_queue_set );
|
||||
}
|
||||
|
||||
// just in case this structure is allocated in a future release
|
||||
@@ -241,7 +288,7 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
|
||||
buttons[n_buttons].long_press = long_press;
|
||||
buttons[n_buttons].shifter_gpio = shifter_gpio;
|
||||
buttons[n_buttons].type = type;
|
||||
buttons[n_buttons].timer = xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void *) &buttons[n_buttons], buttons_timer);
|
||||
buttons[n_buttons].timer = xTimerCreate("buttonTimer", buttons[n_buttons].debounce / portTICK_RATE_MS, pdFALSE, (void *) &buttons[n_buttons], buttons_timer_handler);
|
||||
buttons[n_buttons].self = buttons + n_buttons;
|
||||
|
||||
for (int i = 0; i < n_buttons; i++) {
|
||||
@@ -258,45 +305,59 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu
|
||||
}
|
||||
}
|
||||
|
||||
gpio_pad_select_gpio(gpio);
|
||||
gpio_set_direction(gpio, GPIO_MODE_INPUT);
|
||||
// creation is different is this is a native or an expanded GPIO
|
||||
if (gpio < GPIO_EXP_BASE_MIN) {
|
||||
gpio_pad_select_gpio(gpio);
|
||||
gpio_set_direction(gpio, GPIO_MODE_INPUT);
|
||||
|
||||
// we need any edge detection
|
||||
gpio_set_intr_type(gpio, GPIO_INTR_ANYEDGE);
|
||||
|
||||
// do we need pullup or pulldown
|
||||
if (pull) {
|
||||
if (GPIO_IS_VALID_OUTPUT_GPIO(gpio)) {
|
||||
if (type == BUTTON_LOW) gpio_set_pull_mode(gpio, GPIO_PULLUP_ONLY);
|
||||
else gpio_set_pull_mode(gpio, GPIO_PULLDOWN_ONLY);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "cannot set pull up/down for gpio %u", gpio);
|
||||
// do we need pullup or pulldown
|
||||
if (pull) {
|
||||
if (GPIO_IS_VALID_OUTPUT_GPIO(gpio)) {
|
||||
if (type == BUTTON_LOW) gpio_set_pull_mode(gpio, GPIO_PULLUP_ONLY);
|
||||
else gpio_set_pull_mode(gpio, GPIO_PULLDOWN_ONLY);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "cannot set pull up/down for gpio %u", gpio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// and initialize level ...
|
||||
buttons[n_buttons].level = gpio_get_level(gpio);
|
||||
// and initialize level ...
|
||||
buttons[n_buttons].level = gpio_get_level(gpio);
|
||||
|
||||
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
|
||||
for (int i = 0; polled_gpio[i].gpio != -1; i++) if (polled_gpio[i].gpio == gpio) {
|
||||
if (!polled_timer) {
|
||||
polled_timer = xTimerCreate("buttonsPolling", 100 / portTICK_RATE_MS, pdTRUE, polled_gpio, buttons_polling);
|
||||
xTimerStart(polled_timer, portMAX_DELAY);
|
||||
// nasty ESP32 bug: fire-up constantly INT on GPIO 36/39 if ADC1, AMP, Hall used which WiFi does when PS is activated
|
||||
for (int i = 0; polled_gpio[i].gpio != -1; i++) if (polled_gpio[i].gpio == gpio) {
|
||||
if (!polled_timer) {
|
||||
polled_timer = xTimerCreate("buttonsPolling", 100 / portTICK_RATE_MS, pdTRUE, polled_gpio, buttons_polling);
|
||||
xTimerStart(polled_timer, portMAX_DELAY);
|
||||
}
|
||||
|
||||
polled_gpio[i].button = buttons + n_buttons;
|
||||
polled_gpio[i].level = gpio_get_level(gpio);
|
||||
ESP_LOGW(TAG, "creating polled gpio %u, level %u", gpio, polled_gpio[i].level);
|
||||
|
||||
gpio = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// only create ISR if this is not a polled gpio
|
||||
if (gpio != -1) {
|
||||
// we need any edge detection
|
||||
gpio_set_intr_type(gpio, GPIO_INTR_ANYEDGE);
|
||||
gpio_isr_handler_add(gpio, gpio_isr_handler, (void*) &buttons[n_buttons]);
|
||||
gpio_intr_enable(gpio);
|
||||
}
|
||||
} else {
|
||||
// set GPIO as an ouptut and acquire value
|
||||
struct gpio_exp_s *expander = gpio_exp_set_direction(gpio, GPIO_MODE_INPUT, NULL);
|
||||
buttons[n_buttons].level = gpio_exp_get_level(gpio, 0, expander);
|
||||
|
||||
// create queue and timer for GPIO expander
|
||||
if (!button_exp_queue && expander) {
|
||||
button_exp_queue = xQueueCreate(BUTTON_QUEUE_LEN, sizeof(struct gpio_exp_s*));
|
||||
button_exp_timer = xTimerCreate("button_expander", pdMS_TO_TICKS(DEBOUNCE), pdFALSE, expander, buttons_exp_timer_handler);
|
||||
xQueueAddToSet( button_exp_queue, common_queue_set );
|
||||
gpio_exp_add_isr(gpio_exp_isr_handler, button_exp_timer, expander);
|
||||
}
|
||||
|
||||
polled_gpio[i].button = buttons + n_buttons;
|
||||
polled_gpio[i].level = gpio_get_level(gpio);
|
||||
ESP_LOGW(TAG, "creating polled gpio %u, level %u", gpio, polled_gpio[i].level);
|
||||
|
||||
gpio = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// only create timers and ISR is this is not a polled gpio
|
||||
if (gpio != -1) {
|
||||
gpio_isr_handler_add(gpio, gpio_isr_handler, (void*) &buttons[n_buttons]);
|
||||
gpio_intr_enable(gpio);
|
||||
}
|
||||
|
||||
n_buttons++;
|
||||
}
|
||||
@@ -363,7 +424,7 @@ void *button_remap(void *client, int gpio, button_handler handler, int long_pres
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Create rotary encoder
|
||||
* Rotary encoder handler
|
||||
*/
|
||||
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);
|
||||
|
||||
347
components/services/gpio_exp.c
Normal file
347
components/services/gpio_exp.c
Normal file
@@ -0,0 +1,347 @@
|
||||
/* GDS Example
|
||||
|
||||
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 <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c.h"
|
||||
#include "gpio_exp.h"
|
||||
|
||||
static const char TAG[] = "gpio expander";
|
||||
|
||||
static void IRAM_ATTR intr_isr_handler(void* arg);
|
||||
static struct gpio_exp_s* find_expander(struct gpio_exp_s *expander, int *gpio);
|
||||
|
||||
static void pca9535_set_direction(union gpio_exp_phy_u *phy, uint32_t r_mask, uint32_t w_mask);
|
||||
static int pca9535_read(union gpio_exp_phy_u *phy);
|
||||
|
||||
static esp_err_t i2c_write_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint8_t val);
|
||||
static uint8_t i2c_read_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg);
|
||||
static uint16_t i2c_read_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg);
|
||||
static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint16_t data);
|
||||
|
||||
static const struct gpio_exp_model_s {
|
||||
char *model;
|
||||
gpio_int_type_t trigger;
|
||||
void (*set_direction)(union gpio_exp_phy_u *phy, uint32_t r_mask, uint32_t mask);
|
||||
int (*read)(union gpio_exp_phy_u *phy);
|
||||
} registered[] = {
|
||||
{ .model = "pca9535",
|
||||
.trigger = GPIO_INTR_NEGEDGE,
|
||||
.set_direction = pca9535_set_direction,
|
||||
.read = pca9535_read, }
|
||||
};
|
||||
|
||||
static uint8_t n_expanders;
|
||||
|
||||
static EXT_RAM_ATTR struct gpio_exp_s {
|
||||
uint32_t first, last;
|
||||
union gpio_exp_phy_u phy;
|
||||
uint32_t shadow;
|
||||
TickType_t age;
|
||||
uint32_t r_mask, w_mask;
|
||||
struct {
|
||||
gpio_exp_isr handler;
|
||||
void *arg;
|
||||
} isr[4];
|
||||
struct gpio_exp_model_s const *model;
|
||||
} expanders[4];
|
||||
|
||||
/******************************************************************************
|
||||
* Retrieve base from an expander reference
|
||||
*/
|
||||
uint32_t gpio_exp_base(struct gpio_exp_s *expander) {
|
||||
return expander->first;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Retrieve reference from a GPIO
|
||||
*/
|
||||
struct gpio_exp_s *gpio_exp_expander(int gpio) {
|
||||
int _gpio = gpio;
|
||||
return find_expander(NULL, &_gpio);
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Create an I2C expander
|
||||
*/
|
||||
struct gpio_exp_s* gpio_exp_create(const gpio_exp_config_t *config) {
|
||||
struct gpio_exp_s *expander = expanders + n_expanders;
|
||||
|
||||
if (config->base < GPIO_EXP_BASE_MIN || n_expanders == sizeof(expanders)/sizeof(struct gpio_exp_s)) {
|
||||
ESP_LOGE(TAG, "Base %d GPIO must be > %d for %s or too many expanders %d", config->base, GPIO_EXP_BASE_MIN, config->model, n_expanders);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// See if we know that model (expanders is zero-initialized)
|
||||
for (int i = 0; !expander->model && i < sizeof(registered)/sizeof(struct gpio_exp_model_s); i++) {
|
||||
if (strcasestr(config->model, registered[i].model)) expander->model = registered + i;
|
||||
}
|
||||
|
||||
// well... try again
|
||||
if (!expander->model) {
|
||||
ESP_LOGE(TAG,"Unknown GPIO expansion chip %s", config->model);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
n_expanders++;
|
||||
expander->first = config->base;
|
||||
expander->last = config->base + config->count - 1;
|
||||
memcpy(&expander->phy, &config->phy, sizeof(union gpio_exp_phy_u));
|
||||
|
||||
// set interrupt if possible
|
||||
if (config->intr > 0) {
|
||||
gpio_pad_select_gpio(config->intr);
|
||||
gpio_set_direction(config->intr, GPIO_MODE_INPUT);
|
||||
|
||||
switch (expander->model->trigger) {
|
||||
case GPIO_INTR_NEGEDGE:
|
||||
case GPIO_INTR_LOW_LEVEL:
|
||||
gpio_set_pull_mode(config->intr, GPIO_PULLUP_ONLY);
|
||||
break;
|
||||
case GPIO_INTR_POSEDGE:
|
||||
case GPIO_INTR_HIGH_LEVEL:
|
||||
gpio_set_pull_mode(config->intr, GPIO_PULLDOWN_ONLY);
|
||||
break;
|
||||
default:
|
||||
gpio_set_pull_mode(config->intr, GPIO_PULLUP_PULLDOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
gpio_set_intr_type(config->intr, expander->model->trigger);
|
||||
gpio_isr_handler_add(config->intr, intr_isr_handler, expander);
|
||||
gpio_intr_enable(config->intr);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Create GPIO expander at base %u with INT %u at @%x", config->base, config->intr, config->phy.addr);
|
||||
return expander;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Add ISR handler
|
||||
*/
|
||||
bool gpio_exp_add_isr(gpio_exp_isr isr, void *arg, struct gpio_exp_s *expander) {
|
||||
for (int i = 0; i < sizeof(expander->isr)/sizeof(*expander->isr); i++) {
|
||||
if (!expander->isr[i].handler) {
|
||||
expander->isr[i].handler = isr;
|
||||
expander->isr[i].arg = arg;
|
||||
ESP_LOGI(TAG, "Added new ISR for expander base %d", expander->first);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "No room left to add new ISR");
|
||||
return false;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Set GPIO direction
|
||||
*/
|
||||
struct gpio_exp_s* gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpio_exp_s *expander) {
|
||||
if ((expander = find_expander(expander, &gpio)) == NULL) return NULL;
|
||||
|
||||
if (mode == GPIO_MODE_INPUT) {
|
||||
expander->r_mask |= 1 << gpio;
|
||||
expander->age = ~xTaskGetTickCount();
|
||||
} else {
|
||||
expander->w_mask |= 1 << gpio;
|
||||
}
|
||||
|
||||
if (expander->r_mask & expander->w_mask) {
|
||||
ESP_LOGE(TAG, "GPIO %d on expander base %u can't be r/w", gpio, expander->first);
|
||||
return false;
|
||||
}
|
||||
|
||||
// most expanders want unconfigured GPIO to be set to output
|
||||
if (expander->model->set_direction) expander->model->set_direction(&expander->phy, expander->r_mask, expander->w_mask);
|
||||
|
||||
return expander;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Get GPIO level with cache
|
||||
*/
|
||||
int gpio_exp_get_level(int gpio, uint32_t age, struct gpio_exp_s *expander) {
|
||||
if ((expander = find_expander(expander, &gpio)) == NULL) return false;
|
||||
|
||||
uint32_t now = xTaskGetTickCount();
|
||||
|
||||
if (now - expander->age >= pdMS_TO_TICKS(age)) {
|
||||
expander->shadow = expander->model->read(&expander->phy);
|
||||
expander->age = now;
|
||||
}
|
||||
|
||||
return expander->shadow & (1 << gpio) ? 1 : 0;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Enumerate modified GPIO
|
||||
*/
|
||||
void gpio_exp_enumerate(gpio_exp_enumerator enumerator, struct gpio_exp_s *expander) {
|
||||
uint32_t value = expander->model->read(&expander->phy) ^ expander->shadow;
|
||||
uint8_t clz;
|
||||
|
||||
// memorize newly read value and just update if requested
|
||||
expander->shadow ^= value;
|
||||
if (!enumerator) return;
|
||||
|
||||
// now we have a bitmap of all modified GPIO since last call
|
||||
for (int gpio = 0; value; value <<= (clz + 1)) {
|
||||
clz = __builtin_clz(value);
|
||||
gpio += clz;
|
||||
enumerator(expander->first + 31 - gpio, (expander->shadow >> (31 - gpio)) & 0x01, expander);
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Find the expander related to base
|
||||
*/
|
||||
static struct gpio_exp_s* find_expander(struct gpio_exp_s *expander, int *gpio) {
|
||||
for (int i = 0; !expander && i < n_expanders; i++) {
|
||||
if (*gpio >= expanders[i].first && *gpio <= expanders[i].last) expander = expanders + i;
|
||||
}
|
||||
|
||||
// normalize GPIO number
|
||||
if (expander && *gpio >= expanders->first) *gpio -= expanders->first;
|
||||
|
||||
return expander;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Configure unused GPIO to output
|
||||
*/
|
||||
static void pca9535_set_direction(union gpio_exp_phy_u *phy, uint32_t r_mask, uint32_t w_mask) {
|
||||
esp_err_t err = i2c_write_word(phy->port, phy->addr, 0x06, r_mask);
|
||||
ESP_LOGD(TAG, "PCA9535 set direction %x %d", r_mask, err);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Configure unused GPIO to output
|
||||
*/
|
||||
static int pca9535_read(union gpio_exp_phy_u *phy) {
|
||||
ESP_LOGD(TAG, "PCA9535 read @%d", phy->addr);
|
||||
return i2c_read_word(phy->port, phy->addr, 0x0);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* INTR low-level handler
|
||||
*/
|
||||
static void IRAM_ATTR intr_isr_handler(void* arg)
|
||||
{
|
||||
struct gpio_exp_s *expander = (struct gpio_exp_s*) arg;
|
||||
BaseType_t woken = pdFALSE;
|
||||
|
||||
for (int i = 0; i < sizeof(expander->isr)/sizeof(*expander->isr); i++) {
|
||||
if (expander->isr[i].handler) woken |= expander->isr[i].handler(expander->isr[i].arg);
|
||||
}
|
||||
|
||||
if (woken) portYIELD_FROM_ISR();
|
||||
|
||||
ESP_EARLY_LOGD(TAG, "INTR for expander %u", expander->first);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
*
|
||||
*/
|
||||
static esp_err_t i2c_write_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint8_t val) {
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, val, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C write failed");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* I2C read one byte
|
||||
*/
|
||||
static uint8_t i2c_read_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg) {
|
||||
uint8_t data = 0xff;
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C read failed");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* I2C read 16 bits word
|
||||
*/
|
||||
static uint16_t i2c_read_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg) {
|
||||
uint16_t data = 0xffff;
|
||||
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_start(cmd);
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_READ, I2C_MASTER_NACK);
|
||||
i2c_master_read(cmd, (uint8_t*) &data, 2, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C read failed");
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* I2C write 16 bits word
|
||||
*/
|
||||
static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, uint16_t data) {
|
||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
||||
i2c_master_start(cmd);
|
||||
|
||||
i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
|
||||
i2c_master_write_byte(cmd, reg, I2C_MASTER_NACK);
|
||||
i2c_master_write(cmd, (uint8_t*) &data, 2, I2C_MASTER_NACK);
|
||||
|
||||
i2c_master_stop(cmd);
|
||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
|
||||
i2c_cmd_link_delete(cmd);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "I2C write failed");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
49
components/services/gpio_exp.h
Normal file
49
components/services/gpio_exp.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* GDS Example
|
||||
|
||||
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.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#define GPIO_EXP_BASE_MIN 100
|
||||
|
||||
struct gpio_exp_s;
|
||||
|
||||
typedef struct {
|
||||
char model[32];
|
||||
uint8_t intr;
|
||||
uint8_t count;
|
||||
uint32_t base;
|
||||
union gpio_exp_phy_u {
|
||||
struct {
|
||||
uint8_t addr, port;
|
||||
};
|
||||
struct {
|
||||
uint8_t cs_pin;
|
||||
};
|
||||
} phy;
|
||||
} gpio_exp_config_t;
|
||||
|
||||
typedef void (*gpio_exp_enumerator)(int gpio, int level, struct gpio_exp_s *expander);
|
||||
typedef BaseType_t (*gpio_exp_isr)(void *arg);
|
||||
|
||||
// set <intr> to -1 and <queue> to NULL if there is no interrupt
|
||||
struct gpio_exp_s* gpio_exp_create(const gpio_exp_config_t *config);
|
||||
bool gpio_exp_add_isr(gpio_exp_isr isr, void *arg, struct gpio_exp_s *expander);
|
||||
uint32_t gpio_exp_base(struct gpio_exp_s *expander);
|
||||
struct gpio_exp_s* gpio_exp_expander(int gpio);
|
||||
|
||||
/* For all functions below when <expander> is provided, GPIO's can be numbered from 0. If <expander>
|
||||
is NULL, then GPIO must start from base */
|
||||
struct gpio_exp_s* gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpio_exp_s *expander);
|
||||
int gpio_exp_get_level(int gpio, uint32_t age, struct gpio_exp_s *expander);
|
||||
/* This can be called to enumerate modified GPIO since last read. Note that <enumerator>
|
||||
can be NULL to initialize all GPIOs */
|
||||
void gpio_exp_enumerate(gpio_exp_enumerator enumerator, struct gpio_exp_s *expander);
|
||||
@@ -100,6 +100,10 @@ void services_init(void) {
|
||||
ESP_LOGW(TAG, "no SPI configured");
|
||||
}
|
||||
|
||||
// create GPIO expander
|
||||
const gpio_exp_config_t * gpio_exp_config = config_gpio_exp_get();
|
||||
if (gpio_exp_config) gpio_exp_create(gpio_exp_config);
|
||||
|
||||
// system-wide PWM timer configuration
|
||||
ledc_timer_config_t pwm_timer = {
|
||||
.duty_resolution = LEDC_TIMER_13_BIT,
|
||||
|
||||
@@ -88,6 +88,9 @@ menu "Squeezelite-ESP32"
|
||||
string
|
||||
default "bck=33,ws=25,do=15" if SQUEEZEAMP
|
||||
default ""
|
||||
config GPIO_EXP_CONFIG
|
||||
string
|
||||
default ""
|
||||
config SPI_CONFIG
|
||||
string
|
||||
default "dc=27,data=19,clk=18" if TWATCH2020
|
||||
@@ -333,6 +336,11 @@ menu "Squeezelite-ESP32"
|
||||
help
|
||||
Set GPIO for rotary encoder (quadrature phase). See README on SqueezeESP32 project's GitHub for more details
|
||||
A=<gpio>,B=<gpio>[,SW=gpio>[[,knobonly[=<ms>]|[,volume][,longpress]]
|
||||
config GPIO_EXP_CONFIG
|
||||
string "GPIO expander configuration"
|
||||
help
|
||||
Set parameters of GPIO extender
|
||||
model=<model>[,addr=<addr>][,base=<100..N>][,count=<0..32>][,intr=<gpio>][,port=dac|system]
|
||||
endmenu
|
||||
menu "LED configuration"
|
||||
visible if !SQUEEZEAMP && !TWATCH2020
|
||||
|
||||
@@ -390,12 +390,15 @@ void register_default_nvs(){
|
||||
|
||||
ESP_LOGD(TAG,"Registering default value for key %s", "dac_config");
|
||||
config_set_default(NVS_TYPE_STR, "dac_config", "", 0);
|
||||
//todo: add dac_config for known targets
|
||||
|
||||
ESP_LOGD(TAG,"Registering default value for key %s", "dac_controlset");
|
||||
config_set_default(NVS_TYPE_STR, "dac_controlset", "", 0);
|
||||
|
||||
ESP_LOGD(TAG,"Registering default value for key %s", "jack_mutes_amp");
|
||||
config_set_default(NVS_TYPE_STR, "jack_mutes_amp", "n", 0);
|
||||
|
||||
ESP_LOGD(TAG,"Registering default value for key %s", "gpio_exp_config");
|
||||
config_set_default(NVS_TYPE_STR, "gpio_exp_config", CONFIG_GPIO_EXP_CONFIG, 0);
|
||||
|
||||
ESP_LOGD(TAG,"Registering default value for key %s", "bat_config");
|
||||
config_set_default(NVS_TYPE_STR, "bat_config", "", 0);
|
||||
|
||||
Reference in New Issue
Block a user