diff --git a/build-scripts/ESP32-A1S-sdkconfig.defaults b/build-scripts/ESP32-A1S-sdkconfig.defaults index 6f59ff6d..d9486440 100644 --- a/build-scripts/ESP32-A1S-sdkconfig.defaults +++ b/build-scripts/ESP32-A1S-sdkconfig.defaults @@ -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 diff --git a/build-scripts/I2S-4MFlash-sdkconfig.defaults b/build-scripts/I2S-4MFlash-sdkconfig.defaults index 03c5e4c3..5c4b24fe 100644 --- a/build-scripts/I2S-4MFlash-sdkconfig.defaults +++ b/build-scripts/I2S-4MFlash-sdkconfig.defaults @@ -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 diff --git a/build-scripts/SqueezeAmp-sdkconfig.defaults b/build-scripts/SqueezeAmp-sdkconfig.defaults index bedf27ba..4a8f70c7 100644 --- a/build-scripts/SqueezeAmp-sdkconfig.defaults +++ b/build-scripts/SqueezeAmp-sdkconfig.defaults @@ -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 diff --git a/components/services/accessors.c b/components/services/accessors.c index a0735654..91bd2716 100644 --- a/components/services/accessors.c +++ b/components/services/accessors.c @@ -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; +} + /**************************************************************************************** * */ diff --git a/components/services/accessors.h b/components/services/accessors.h index 614d39cf..3b731136 100644 --- a/components/services/accessors.h +++ b/components/services/accessors.h @@ -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( ); diff --git a/components/services/buttons.c b/components/services/buttons.c index 2d6e0ce1..487ab68b 100644 --- a/components/services/buttons.c +++ b/components/services/buttons.c @@ -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); diff --git a/components/services/gpio_exp.c b/components/services/gpio_exp.c new file mode 100644 index 00000000..a1e6d33a --- /dev/null +++ b/components/services/gpio_exp.c @@ -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 +#include +#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; +} \ No newline at end of file diff --git a/components/services/gpio_exp.h b/components/services/gpio_exp.h new file mode 100644 index 00000000..14118aa2 --- /dev/null +++ b/components/services/gpio_exp.h @@ -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 +#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 to -1 and 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 is provided, GPIO's can be numbered from 0. If + 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 + can be NULL to initialize all GPIOs */ +void gpio_exp_enumerate(gpio_exp_enumerator enumerator, struct gpio_exp_s *expander); diff --git a/components/services/services.c b/components/services/services.c index c0c11ce2..799dcec8 100644 --- a/components/services/services.c +++ b/components/services/services.c @@ -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, diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index b5b84113..889d558c 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -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=,B=[,SW=gpio>[[,knobonly[=]|[,volume][,longpress]] + config GPIO_EXP_CONFIG + string "GPIO expander configuration" + help + Set parameters of GPIO extender + model=[,addr=][,base=<100..N>][,count=<0..32>][,intr=][,port=dac|system] endmenu menu "LED configuration" visible if !SQUEEZEAMP && !TWATCH2020 diff --git a/main/esp_app_main.c b/main/esp_app_main.c index 4f400e4c..f42adcef 100644 --- a/main/esp_app_main.c +++ b/main/esp_app_main.c @@ -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);