From d2494b73dbed88427f0c8d6896beaa3736c33054 Mon Sep 17 00:00:00 2001 From: Philippe G Date: Sat, 4 Dec 2021 12:40:56 -0800 Subject: [PATCH] Rotary encoder + simplify - GPIO expander works with rotary encoder - Much better mimic real GPIO, including ISR, to minimize impact on clients --- components/services/buttons.c | 153 +++++---------- components/services/gpio_exp.c | 281 +++++++++++++++------------ components/services/gpio_exp.h | 40 ++-- components/services/led.c | 6 +- components/services/rotary_encoder.c | 45 +++-- components/services/services.c | 12 +- components/squeezelite/output_i2s.c | 31 ++- 7 files changed, 285 insertions(+), 283 deletions(-) diff --git a/components/services/buttons.c b/components/services/buttons.c index 2a9ebb7c..fafb8668 100644 --- a/components/services/buttons.c +++ b/components/services/buttons.c @@ -28,7 +28,7 @@ static const char * TAG = "buttons"; -static int n_buttons = 0; +static EXT_RAM_ATTR int n_buttons; #define BUTTON_STACK_SIZE 4096 #define MAX_BUTTONS 32 @@ -69,9 +69,8 @@ static EXT_RAM_ATTR struct { infrared_handler handler; } infrared; -static QueueHandle_t button_queue, button_exp_queue; -static TimerHandle_t button_exp_timer; -static QueueSetHandle_t common_queue_set; +static EXT_RAM_ATTR QueueHandle_t button_queue; +static EXT_RAM_ATTR QueueSetHandle_t common_queue_set; static void buttons_task(void* arg); static void buttons_handler(struct button_s *button, int level); @@ -97,9 +96,16 @@ static void IRAM_ATTR gpio_isr_handler(void* arg) struct button_s *button = (struct button_s*) arg; BaseType_t woken = pdFALSE; - if (xTimerGetPeriod(button->timer) > button->debounce / portTICK_RATE_MS) xTimerChangePeriodFromISR(button->timer, button->debounce / portTICK_RATE_MS, &woken); // does that restart the timer? - else xTimerResetFromISR(button->timer, &woken); + if (xTimerGetPeriod(button->timer) > pdMS_TO_TICKS(button->debounce)) { + if (button->gpio < GPIO_NUM_MAX) xTimerChangePeriodFromISR(button->timer, pdMS_TO_TICKS(button->debounce), &woken); + else xTimerChangePeriod(button->timer, pdMS_TO_TICKS(button->debounce), pdMS_TO_TICKS(10)); + } else { + if (button->gpio < GPIO_NUM_MAX) xTimerResetFromISR(button->timer, &woken); + else xTimerReset(button->timer, portMAX_DELAY); + } + if (woken) portYIELD_FROM_ISR(); + ESP_EARLY_LOGD(TAG, "INT gpio %u level %u", button->gpio, button->level); } @@ -108,36 +114,8 @@ static void IRAM_ATTR gpio_isr_handler(void* arg) */ 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)); -} - -/**************************************************************************************** - * 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; -} - -/**************************************************************************************** - * 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_LOGD(TAG, "Button expander base %u debounced", gpio_exp_get_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; - } + // if this is an expanded GPIO, must give cache a chance + buttons_handler(button, gpio_exp_get_level(button->gpio, (button->debounce * 3) / 2, NULL)); } /**************************************************************************************** @@ -230,25 +208,17 @@ static void buttons_task(void* arg) { // 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 }; // received a rotary event xQueueReceive(rotary.queue, &event, 0); - + ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position, - event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET"); + 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); + ROTARY_RIGHT : ROTARY_LEFT, false); } else { // this is IR infrared_receive(infrared.rb, infrared.handler); @@ -267,8 +237,6 @@ void dummy_handler(void *id, button_event_e event, button_press_e press) { * Create buttons */ void button_create(void *client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio) { - struct gpio_exp_s *expander; - if (n_buttons >= MAX_BUTTONS) return; 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); @@ -307,61 +275,44 @@ void button_create(void *client, int gpio, int type, bool pull, int debounce, bu } } - // creation is different is this is a native or an expanded GPIO - if (gpio < GPIO_NUM_MAX) { - gpio_pad_select_gpio(gpio); - gpio_set_direction(gpio, GPIO_MODE_INPUT); + gpio_pad_select_gpio_x(gpio); + gpio_set_direction_x(gpio, GPIO_MODE_INPUT); - // 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) || gpio >= GPIO_NUM_MAX) { + if (type == BUTTON_LOW) gpio_set_pull_mode_x(gpio, GPIO_PULLUP_ONLY); + else gpio_set_pull_mode_x(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); - - // 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 if ((expander = gpio_exp_get_expander(gpio)) != NULL) { - // set GPIO as an ouptut and acquire value - gpio_exp_set_direction(gpio, GPIO_MODE_INPUT, expander); - 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); - } - } else { - ESP_LOGE(TAG, "Can't create button, GPIO %d does not exist", gpio); } + + // and initialize level ... + buttons[n_buttons].level = gpio_get_level_x(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); + } + + 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_x(gpio, GPIO_INTR_ANYEDGE); + gpio_isr_handler_add_x(gpio, gpio_isr_handler, buttons + n_buttons); + gpio_intr_enable_x(gpio); + } n_buttons++; } diff --git a/components/services/gpio_exp.c b/components/services/gpio_exp.c index a2941bb4..48f99b72 100644 --- a/components/services/gpio_exp.c +++ b/components/services/gpio_exp.c @@ -10,6 +10,7 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/timers.h" #include "freertos/queue.h" #include "esp_task.h" #include "esp_log.h" @@ -17,18 +18,27 @@ #include "driver/i2c.h" #include "gpio_exp.h" +#define GPIO_EXP_INTR 0x100 +#define GPIO_EXP_WRITE 0x200 + +/* + shadow register is both output and input, so we assume that reading to the + ports also reads the value set on output +*/ + typedef struct gpio_exp_s { uint32_t first, last; union gpio_exp_phy_u phy; - uint32_t shadow; + uint32_t shadow, pending; TickType_t age; SemaphoreHandle_t mutex; uint32_t r_mask, w_mask; uint32_t pullup, pulldown; - struct { - gpio_exp_isr handler; + struct gpio_exp_isr_s { + gpio_isr_t handler; void *arg; - } isr[4]; + TimerHandle_t timer; + } isr[32]; struct gpio_exp_model_s const *model; } gpio_exp_t; @@ -37,7 +47,7 @@ typedef struct { int gpio; int level; gpio_exp_t *expander; -} async_request_t; +} queue_request_t; static const char TAG[] = "gpio expander"; @@ -58,11 +68,11 @@ static void mcp23017_set_direction(gpio_exp_t* self); static int mcp23017_read(gpio_exp_t* self); static void mcp23017_write(gpio_exp_t* self); -static void async_handler(void *arg); +static void service_handler(void *arg); +static void debounce_handler( TimerHandle_t xTimer ); 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 uint16_t i2c_read(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, bool word); 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 { @@ -94,8 +104,9 @@ static const struct gpio_exp_model_s { }; static EXT_RAM_ATTR uint8_t n_expanders; -static EXT_RAM_ATTR QueueHandle_t async_queue; +static EXT_RAM_ATTR QueueHandle_t message_queue; static EXT_RAM_ATTR gpio_exp_t expanders[4]; +static EXT_RAM_ATTR TaskHandle_t service_task; /****************************************************************************** * Retrieve base from an expander reference @@ -138,17 +149,18 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) { expander->first = config->base; expander->last = config->base + config->count - 1; expander->mutex = xSemaphoreCreateMutex(); + memcpy(&expander->phy, &config->phy, sizeof(union gpio_exp_phy_u)); if (expander->model->init) expander->model->init(expander); // create a task to handle asynchronous requests (only write at this time) - if (!async_queue) { - // we allocate TCB but stack is staic to avoid SPIRAM fragmentation + if (!message_queue) { + // we allocate TCB but stack is static to avoid SPIRAM fragmentation StaticTask_t* xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - static EXT_RAM_ATTR StackType_t xStack[2*1024] __attribute__ ((aligned (4))); + static EXT_RAM_ATTR StackType_t xStack[4*1024] __attribute__ ((aligned (4))); - async_queue = xQueueCreate(4, sizeof(async_request_t)); - xTaskCreateStatic(async_handler, "gpio_expander", sizeof(xStack), NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer); + message_queue = xQueueCreate(4, sizeof(queue_request_t)); + service_task = xTaskCreateStatic(service_handler, "gpio_expander", sizeof(xStack), NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer); } // set interrupt if possible @@ -180,36 +192,45 @@ gpio_exp_t* gpio_exp_create(const gpio_exp_config_t *config) { } /****************************************************************************** - * Add ISR handler + * Add ISR handler for a GPIO */ -bool gpio_exp_add_isr(gpio_exp_isr isr, void *arg, gpio_exp_t *expander) { - xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY)); +esp_err_t gpio_exp_isr_handler_add(int gpio, gpio_isr_t isr_handler, uint32_t debounce, void *arg, struct gpio_exp_s *expander) { + if (gpio < GPIO_NUM_MAX && !expander) return gpio_isr_handler_add(gpio, isr_handler, arg); + if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG; - 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); - xSemaphoreGive(expander->mutex); - return true; - } - } + expander->isr[gpio].handler = isr_handler; + expander->isr[gpio].arg = arg; + if (debounce) expander->isr[gpio].timer = xTimerCreate("gpioExpDebounce", pdMS_TO_TICKS(debounce), + pdFALSE, expander->isr + gpio, debounce_handler ); - xSemaphoreGive(expander->mutex); - ESP_LOGE(TAG, "No room left to add new ISR"); - return false; + return ESP_OK; +} + +/****************************************************************************** + * Remove ISR handler for a GPIO + */ +esp_err_t gpio_exp_isr_handler_remove(int gpio, struct gpio_exp_s *expander) { + if (gpio < GPIO_NUM_MAX && !expander) return gpio_isr_handler_remove(gpio); + if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG; + + if (expander->isr[gpio].timer) xTimerDelete(expander->isr[gpio].timer, portMAX_DELAY); + memset(expander->isr + gpio, 0, sizeof(struct gpio_exp_isr_s)); + + return ESP_OK; } /****************************************************************************** * Set GPIO direction */ esp_err_t gpio_exp_set_direction(int gpio, gpio_mode_t mode, gpio_exp_t *expander) { + if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_direction(gpio, mode); if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG; xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY)); if (mode == GPIO_MODE_INPUT) { expander->r_mask |= 1 << gpio; + expander->shadow = expander->model->read(expander); expander->age = ~xTaskGetTickCount(); } else { expander->w_mask |= 1 << gpio; @@ -225,27 +246,37 @@ esp_err_t gpio_exp_set_direction(int gpio, gpio_mode_t mode, gpio_exp_t *expande if (expander->model->set_direction) expander->model->set_direction(expander); xSemaphoreGive(expander->mutex); + return ESP_OK; } /****************************************************************************** * Get GPIO level with cache */ -int gpio_exp_get_level(int gpio, uint32_t age, gpio_exp_t *expander) { +int gpio_exp_get_level(int gpio, int age, gpio_exp_t *expander) { + if (gpio < GPIO_NUM_MAX && !expander) return gpio_get_level(gpio); if ((expander = find_expander(expander, &gpio)) == NULL) return -1; uint32_t now = xTaskGetTickCount(); - - // this is a little risk here but that avoids calling scheduler if we are cached - if (now - expander->age >= pdMS_TO_TICKS(age)) { - if (xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50)) == pdFALSE) return -1; - expander->shadow = expander->model->read(expander); - expander->age = now; - - xSemaphoreGive(expander->mutex); + // return last thing we had if we can't get the mutex + if (xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50)) == pdFALSE) { + ESP_LOGW(TAG, "Can't get mutex for GPIO %d", expander->first + gpio); + return (expander->shadow >> gpio) & 0x01; } - + // re-read the expander if data is too old + if (age >= 0 && now - expander->age >= pdMS_TO_TICKS(age)) { + uint32_t value = expander->model->read(expander); + expander->pending |= (expander->shadow ^ value) & expander->r_mask; + expander->shadow = value; + expander->age = now; + } + + // clear pending bit + expander->pending &= ~(1 << gpio); + + xSemaphoreGive(expander->mutex); + ESP_LOGD(TAG, "Get level for GPIO %u => read %x", expander->first + gpio, expander->shadow); return (expander->shadow >> gpio) & 0x01; } @@ -254,10 +285,11 @@ int gpio_exp_get_level(int gpio, uint32_t age, gpio_exp_t *expander) { * Set GPIO level with cache */ esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, gpio_exp_t *expander) { + if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_level(gpio, level); if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG; uint32_t mask = 1 << gpio; - // limited risk with lack of semaphore here + // very limited risk with lack of semaphore here if ((expander->w_mask & mask) == 0) { ESP_LOGW(TAG, "GPIO %d is not set for output", expander->first + gpio); return ESP_ERR_INVALID_ARG; @@ -278,8 +310,11 @@ esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, gpio_exp_t *expan xSemaphoreGive(expander->mutex); ESP_LOGD(TAG, "Set level %x for GPIO %u => wrote %x", level, expander->first + gpio, expander->shadow); } else { - async_request_t request = { .gpio = gpio, .level = level, .type = ASYNC_WRITE, .expander = expander }; - if (xQueueSend(async_queue, &request, 0) == pdFALSE) return ESP_ERR_INVALID_RESPONSE; + queue_request_t request = { .gpio = gpio, .level = level, .type = ASYNC_WRITE, .expander = expander }; + if (xQueueSend(message_queue, &request, 0) == pdFALSE) return ESP_ERR_INVALID_RESPONSE; + + // notify service task that will write it when it can + xTaskNotify(service_task, GPIO_EXP_WRITE, eSetValueWithoutOverwrite); } return ESP_OK; @@ -289,6 +324,7 @@ esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, gpio_exp_t *expan * Set GPIO pullmode */ esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, gpio_exp_t *expander) { + if (gpio < GPIO_NUM_MAX && !expander) return gpio_set_pull_mode(gpio, mode); if ((expander = find_expander(expander, &gpio)) != NULL && expander->model->set_pull_mode) { expander->pullup &= ~(1 << gpio); @@ -303,54 +339,44 @@ esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, gpio_exp_t *ex return ESP_ERR_INVALID_ARG; } -/****************************************************************************** - * Enumerate modified GPIO - */ -void gpio_exp_enumerate(gpio_exp_enumerator enumerator, gpio_exp_t *expander) { - uint32_t value = expander->model->read(expander) ^ expander->shadow; - uint8_t clz; - - // memorize newly read value and just update if requested - xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50)); - expander->shadow ^= value; - xSemaphoreGive(expander->mutex); - if (!enumerator) return; - - // now we have a bitmap of all modified GPIO sinnce 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); - } -} - /****************************************************************************** * Wrapper function */ -esp_err_t gpio_set_pull_mode_u(int gpio, gpio_pull_mode_t mode) { +esp_err_t gpio_set_pull_mode_x(int gpio, gpio_pull_mode_t mode) { if (gpio < GPIO_NUM_MAX) return gpio_set_pull_mode(gpio, mode); return gpio_exp_set_pull_mode(gpio, mode, NULL); } -esp_err_t gpio_set_direction_u(int gpio, gpio_mode_t mode) { +esp_err_t gpio_set_direction_x(int gpio, gpio_mode_t mode) { if (gpio < GPIO_NUM_MAX) return gpio_set_direction(gpio, mode); return gpio_exp_set_direction(gpio, mode, NULL); } -int gpio_get_level_u(int gpio) { +int gpio_get_level_x(int gpio) { if (gpio < GPIO_NUM_MAX) return gpio_get_level(gpio); - return gpio_exp_get_level(gpio, 50, NULL); + return gpio_exp_get_level(gpio, 10, NULL); } -esp_err_t gpio_set_level_u(int gpio, int level) { +esp_err_t gpio_set_level_x(int gpio, int level) { if (gpio < GPIO_NUM_MAX) return gpio_set_level(gpio, level); return gpio_exp_set_level(gpio, level, false, NULL); } +esp_err_t gpio_isr_handler_add_x(int gpio, gpio_isr_t isr_handler, void* args) { + if (gpio < GPIO_NUM_MAX) return gpio_isr_handler_add(gpio, isr_handler, args); + return gpio_exp_isr_handler_add(gpio, isr_handler, 0, args, NULL); +} + +esp_err_t gpio_isr_handler_remove_x(int gpio) { + if (gpio < GPIO_NUM_MAX) return gpio_isr_handler_remove(gpio); + return gpio_exp_isr_handler_remove(gpio, NULL); +} + /**************************************************************************************** * Find the expander related to base */ static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio) { + // a mutex would be better, but risk is so small... for (int i = 0; !expander && i < n_expanders; i++) { if (*gpio >= expanders[i].first && *gpio <= expanders[i].last) expander = expanders + i; } @@ -369,7 +395,7 @@ static void pca9535_set_direction(gpio_exp_t* self) { } static int pca9535_read(gpio_exp_t* self) { - return i2c_read_word(self->phy.port, self->phy.addr, 0x00); + return i2c_read(self->phy.port, self->phy.addr, 0x00, true); } static void pca9535_write(gpio_exp_t* self) { @@ -385,7 +411,7 @@ static void pca85xx_set_direction(gpio_exp_t* self) { } static int pca85xx_read(gpio_exp_t* self) { - return i2c_read_word(self->phy.port, self->phy.addr, 0xff); + return i2c_read(self->phy.port, self->phy.addr, 0xff, true); } static void pca85xx_write(gpio_exp_t* self) { @@ -398,7 +424,7 @@ static void pca85xx_write(gpio_exp_t* self) { */ static void mcp23017_init(gpio_exp_t* self) { /* - 0111 x10x = same bank, mirrot single int, no sequentµial, open drain, active low + 0111 x10x = same bank, mirrot single int, no sequentµial, open drain, active low not sure about this funny change of mapping of the control register itself, really? */ i2c_write_byte(self->phy.port, self->phy.addr, 0x05, 0x74); @@ -421,7 +447,7 @@ static void mcp23017_set_pull_mode(gpio_exp_t* self) { static int mcp23017_read(gpio_exp_t* self) { // read the pin value, not the stored one @interrupt - return i2c_read_word(self->phy.port, self->phy.addr, 0x12); + return i2c_read(self->phy.port, self->phy.addr, 0x12, true); } static void mcp23017_write(gpio_exp_t* self) { @@ -431,43 +457,68 @@ static void mcp23017_write(gpio_exp_t* self) { /**************************************************************************************** * INTR low-level handler */ -static void IRAM_ATTR intr_isr_handler(void* arg) -{ - gpio_exp_t *expander = (gpio_exp_t*) arg; +static void IRAM_ATTR intr_isr_handler(void* 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); - } - + xTaskNotifyFromISR(service_task, GPIO_EXP_INTR, eSetValueWithOverwrite, &woken); if (woken) portYIELD_FROM_ISR(); - ESP_EARLY_LOGD(TAG, "INTR for expander %u", expander->first); + ESP_EARLY_LOGD(TAG, "INTR for expander base", gpio_exp_get_base(arg)); } /**************************************************************************************** - * Async task + * INTR debounce handler */ -void async_handler(void *arg) { +static void debounce_handler( TimerHandle_t xTimer ) { + struct gpio_exp_isr_s *isr = (struct gpio_exp_isr_s*) pvTimerGetTimerID (xTimer); + isr->handler(isr->arg); +} + +/**************************************************************************************** + * Service task + */ +void service_handler(void *arg) { while (1) { - esp_err_t err; - async_request_t request; + queue_request_t request; + uint32_t notif = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - if (!xQueueReceive(async_queue, &request, portMAX_DELAY)) continue; + // we have been notified of an interrupt + if (notif == GPIO_EXP_INTR) { + /* If we want a smarter bitmap of expanders with a pending interrupt + we'll have to disable interrupts while clearing that bitmap. For + now, a loop will do */ + for (int i = 0; i < n_expanders; i++) { + gpio_exp_t *expander = expanders + i; + + xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(50)); - switch (request.type) { - case ASYNC_WRITE: - err = gpio_exp_set_level(request.gpio, request.level, true, request.expander); + // read GPIOs and clear all pending status + uint32_t value = expander->model->read(expander); + uint32_t pending = expander->pending | ((expander->shadow ^ value) & expander->r_mask); + expander->shadow = value; + expander->pending = 0; + expander->age = xTaskGetTickCount(); + + xSemaphoreGive(expander->mutex); + ESP_LOGD(TAG, "Handling GPIO %d reads 0x%04x and has 0x%04x pending", expander->first, expander->shadow, pending); + + for (int gpio = 31, clz; pending; pending <<= (clz + 1)) { + clz = __builtin_clz(pending); + gpio -= clz; + if (expander->isr[gpio].timer) xTimerReset(expander->isr[gpio].timer, 1); // todo 0 + else if (expander->isr[gpio].handler) expander->isr[gpio].handler(expander->isr[gpio].arg); + } + } + } + + // check if we have some other pending requests + if (xQueueReceive(message_queue, &request, 0) == pdTRUE) { + esp_err_t err = gpio_exp_set_level(request.gpio, request.level, true, request.expander); if (err != ESP_OK) ESP_LOGW(TAG, "Can't execute async GPIO %d write request (%d)", request.gpio, err); - break; - default: - break; } } } - - /**************************************************************************************** * */ @@ -491,41 +542,14 @@ static esp_err_t i2c_write_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, } /**************************************************************************************** - * I2C read one byte + * I2C read 8 or 16 bits word */ -static uint8_t i2c_read_byte(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg) { - uint8_t data = 0xff; +static uint16_t i2c_read(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, bool word) { + uint8_t data[2]; 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); // when using a register, write it's value then the device address again @@ -535,7 +559,12 @@ static uint16_t i2c_read_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg) { 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); + if (word) { + i2c_master_read_byte(cmd, data, I2C_MASTER_ACK); + i2c_master_read_byte(cmd, data + 1, I2C_MASTER_NACK); + } else { + 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); @@ -545,7 +574,7 @@ static uint16_t i2c_read_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg) { ESP_LOGW(TAG, "I2C read failed"); } - return data; + return *(uint16_t*) data; } /**************************************************************************************** @@ -563,7 +592,7 @@ static esp_err_t i2c_write_word(uint8_t i2c_port, uint8_t i2c_addr, uint8_t reg, esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS); i2c_cmd_link_delete(cmd); - if (ret != ESP_OK) { + if (ret != ESP_OK) { ESP_LOGW(TAG, "I2C write failed"); } diff --git a/components/services/gpio_exp.h b/components/services/gpio_exp.h index 433dac66..ae554260 100644 --- a/components/services/gpio_exp.h +++ b/components/services/gpio_exp.h @@ -29,28 +29,30 @@ typedef struct { } 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_get_base(struct gpio_exp_s *expander); -struct gpio_exp_s* gpio_exp_get_expander(int gpio); +struct gpio_exp_s* gpio_exp_create(const gpio_exp_config_t *config); +uint32_t gpio_exp_get_base(struct gpio_exp_s *expander); +struct gpio_exp_s* gpio_exp_get_expander(int gpio); +#define gpio_is_expanded(gpio) (gpio < GPIO_NUM_MAX) -/* For all functions below when is provided, GPIO's can be numbered from 0. If - is NULL, then GPIO must start from base */ +/* + For all functions below when is provided, GPIO's can be numbered from 0. If + is NULL, then GPIO must start from base OR be on-chip +*/ esp_err_t gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpio_exp_s *expander); esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, struct gpio_exp_s *expander); -int gpio_exp_get_level(int gpio, uint32_t age, struct gpio_exp_s *expander); +int gpio_exp_get_level(int gpio, int age, struct gpio_exp_s *expander); esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, struct gpio_exp_s *expander); +esp_err_t gpio_exp_isr_handler_add(int gpio, gpio_isr_t isr, uint32_t debounce, void *arg, struct gpio_exp_s *expander); +esp_err_t gpio_exp_isr_handler_remove(int gpio, 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); - -// option to use either built-in or expanded GPIO -esp_err_t gpio_set_direction_u(int gpio, gpio_mode_t mode); -esp_err_t gpio_set_pull_mode_u(int gpio, gpio_pull_mode_t mode); -int gpio_get_level_u(int gpio); -esp_err_t gpio_set_level_u(int gpio, int level); +// unified function to use either built-in or expanded GPIO +esp_err_t gpio_set_direction_x(int gpio, gpio_mode_t mode); +esp_err_t gpio_set_pull_mode_x(int gpio, gpio_pull_mode_t mode); +int gpio_get_level_x(int gpio); +esp_err_t gpio_set_level_x(int gpio, int level); +esp_err_t gpio_isr_handler_add_x(int gpio, gpio_isr_t isr_handler, void* args); +esp_err_t gpio_isr_handler_remove_x(int gpio); +#define gpio_set_intr_type_x(gpio, type) do { if (gpio < GPIO_NUM_MAX) gpio_set_intr_type(gpio, type); } while (0) +#define gpio_intr_enable_x(gpio) do { if (gpio < GPIO_NUM_MAX) gpio_intr_enable(gpio); } while (0) +#define gpio_pad_select_gpio_x(gpio) do { if (gpio < GPIO_NUM_MAX) gpio_pad_select_gpio(gpio); } while (0) diff --git a/components/services/led.c b/components/services/led.c index ae6e3de4..7910f32a 100644 --- a/components/services/led.c +++ b/components/services/led.c @@ -55,7 +55,7 @@ static int led_max = 2; * */ static void set_level(struct led_s *led, bool on) { - if (led->pwm < 0 || led->gpio >= GPIO_NUM_MAX) gpio_set_level_u(led->gpio, on ? led->onstate : !led->onstate); + if (led->pwm < 0 || led->gpio >= GPIO_NUM_MAX) gpio_set_level_x(led->gpio, on ? led->onstate : !led->onstate); else { ledc_set_duty(LEDC_HIGH_SPEED_MODE, led->channel, on ? led->pwm : (led->onstate ? 0 : pwm_system.max)); ledc_update_duty(LEDC_HIGH_SPEED_MODE, led->channel); @@ -182,8 +182,8 @@ bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm) { leds[idx].pwm = -1; if (pwm < 0 || gpio >= GPIO_NUM_MAX) { - if (gpio < GPIO_NUM_MAX) gpio_pad_select_gpio(gpio); - gpio_set_direction_u(gpio, GPIO_MODE_OUTPUT); + gpio_pad_select_gpio_x(gpio); + gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT); } else { leds[idx].channel = pwm_system.base_channel++; leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3); diff --git a/components/services/rotary_encoder.c b/components/services/rotary_encoder.c index d9aa854d..650c0206 100644 --- a/components/services/rotary_encoder.c +++ b/components/services/rotary_encoder.c @@ -91,6 +91,7 @@ #include "esp_log.h" #include "driver/gpio.h" +#include "gpio_exp.h" #define TAG "rotary_encoder" @@ -148,7 +149,7 @@ static uint8_t _process(rotary_encoder_info_t * info) 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); + uint8_t pin_state = (gpio_get_level_x(info->pin_b) << 1) | gpio_get_level_x(info->pin_a); // Determine new state from the pins and state table. #ifdef ROTARY_ENCODER_DEBUG @@ -198,12 +199,18 @@ static void _isr_rotenc(void * args) .direction = info->state.direction, }, }; - BaseType_t task_woken = pdFALSE; - xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken); - if (task_woken) - { - portYIELD_FROM_ISR(); - } + if (info->pin_a < GPIO_NUM_MAX) { + BaseType_t task_woken = pdFALSE; + xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken); + if (task_woken) + { + portYIELD_FROM_ISR(); + } + } + else + { + xQueueOverwrite(info->queue, &queue_event); + } } } @@ -220,19 +227,19 @@ esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gp 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_x(info->pin_a); + gpio_set_pull_mode_x(info->pin_a, GPIO_PULLUP_ONLY); + gpio_set_direction_x(info->pin_a, GPIO_MODE_INPUT); + gpio_set_intr_type_x(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); + gpio_pad_select_gpio_x(info->pin_b); + gpio_set_pull_mode_x(info->pin_b, GPIO_PULLUP_ONLY); + gpio_set_direction_x(info->pin_b, GPIO_MODE_INPUT); + gpio_set_intr_type_x(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); + gpio_isr_handler_add_x(info->pin_a, _isr_rotenc, info); + gpio_isr_handler_add_x(info->pin_b, _isr_rotenc, info); } else { @@ -280,8 +287,8 @@ 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); + gpio_isr_handler_remove_x(info->pin_a); + gpio_isr_handler_remove_x(info->pin_b); } else { diff --git a/components/services/services.c b/components/services/services.c index dc005f60..e3b1eff2 100644 --- a/components/services/services.c +++ b/components/services/services.c @@ -43,13 +43,13 @@ void set_power_gpio(int gpio, char *value) { bool parsed = true; if (!strcasecmp(value, "vcc") ) { - if (gpio < GPIO_NUM_MAX) gpio_pad_select_gpio(gpio); - gpio_set_direction_u(gpio, GPIO_MODE_OUTPUT); - gpio_set_level_u(gpio, 1); + gpio_pad_select_gpio_x(gpio); + gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT); + gpio_set_level_x(gpio, 1); } else if (!strcasecmp(value, "gnd")) { - if (gpio < GPIO_NUM_MAX) gpio_pad_select_gpio(gpio); - gpio_set_direction_u(gpio, GPIO_MODE_OUTPUT); - gpio_set_level_u(gpio, 0); + gpio_pad_select_gpio_x(gpio); + gpio_set_direction_x(gpio, GPIO_MODE_OUTPUT); + gpio_set_level_x(gpio, 0); } else parsed = false; if (parsed) ESP_LOGI(TAG, "set GPIO %u to %s", gpio, value); diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index 931dec20..2c992f07 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -132,10 +132,10 @@ static bool handler(u8_t *data, int len){ if (jack_mutes_amp && jack_inserted_svc()) { adac->speaker(false); - if (amp_control.gpio != -1) gpio_set_level_u(amp_control.gpio, !amp_control.active); + if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, !amp_control.active); } else { adac->speaker(true); - if (amp_control.gpio != -1) gpio_set_level_u(amp_control.gpio, amp_control.active); + if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, amp_control.active); } } @@ -158,7 +158,7 @@ static void jack_handler(bool inserted) { if (jack_mutes_amp) { LOG_INFO("switching amplifier %s", inserted ? "OFF" : "ON"); adac->speaker(!inserted); - if (amp_control.gpio != -1) gpio_set_level_u(amp_control.gpio, inserted ? !amp_control.active : amp_control.active); + if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, inserted ? !amp_control.active : amp_control.active); } // activate headset @@ -178,9 +178,9 @@ static void set_amp_gpio(int gpio, char *value) { amp_control.gpio = gpio; if ((p = strchr(value, ':')) != NULL) amp_control.active = atoi(p + 1); - if (amp_control.gpio < GPIO_NUM_MAX) gpio_pad_select_gpio(amp_control.gpio); - gpio_set_direction_u(amp_control.gpio, GPIO_MODE_OUTPUT); - gpio_set_level_u(amp_control.gpio, !amp_control.active); + gpio_pad_select_gpio_x(amp_control.gpio); + gpio_set_direction_x(amp_control.gpio, GPIO_MODE_OUTPUT); + gpio_set_level_x(amp_control.gpio, !amp_control.active); LOG_INFO("setting amplifier GPIO %d (active:%d)", amp_control.gpio, amp_control.active); } @@ -456,14 +456,14 @@ static void output_thread_i2s(void *arg) { LOG_INFO("Output state is %d", output.state); if (output.state == OUTPUT_OFF) { led_blink(LED_GREEN, 100, 2500); - if (amp_control.gpio != -1) gpio_set_level_u(amp_control.gpio, !amp_control.active); + if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, !amp_control.active); LOG_INFO("switching off amp GPIO %d", amp_control.gpio); } else if (output.state == OUTPUT_STOPPED) { adac->speaker(false); led_blink(LED_GREEN, 200, 1000); } else if (output.state == OUTPUT_RUNNING) { if (!jack_mutes_amp || !jack_inserted_svc()) { - if (amp_control.gpio != -1) gpio_set_level_u(amp_control.gpio, amp_control.active); + if (amp_control.gpio != -1) gpio_set_level_x(amp_control.gpio, amp_control.active); adac->speaker(true); } led_on(LED_GREEN); @@ -493,7 +493,20 @@ static void output_thread_i2s(void *arg) { _output_frames( iframes ); // oframes must be a global updated by the write callback output.frames_in_process = oframes; - + +/* + { + ISAMPLE_T *ptr = (ISAMPLE_T*) obuf; + for (int i = 0; i < oframes; i++) { + *ptr++ = 0; // L +#if BYTES_PER_FRAME == 8 + *ptr++ = rand() >> 4; // R +#else + *ptr++ = (rand() % 65536) >> 4; // R +#endif + } + } +*/ SET_MIN_MAX_SIZED(oframes,rec,iframes); SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size); SET_MIN_MAX_SIZED(_buf_used(streambuf),s,streambuf->size);