diff --git a/components/services/gpio_exp.c b/components/services/gpio_exp.c index 7f964c27..eb3c6e48 100644 --- a/components/services/gpio_exp.c +++ b/components/services/gpio_exp.c @@ -10,6 +10,8 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_task.h" #include "esp_log.h" #include "driver/gpio.h" #include "driver/i2c.h" @@ -27,11 +29,20 @@ static void pca85xx_set_direction(union gpio_exp_phy_u*, uint32_t, uint32_t); static int pca85xx_read(union gpio_exp_phy_u*); static void pca85xx_write(union gpio_exp_phy_u*, uint32_t, uint32_t); +static void async_handler(void *arg); + 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); +typedef struct { + enum { ASYNC_WRITE } type; + int gpio; + int level; + struct gpio_exp_s *expander; +} async_request_t; + static const struct gpio_exp_model_s { char *model; gpio_int_type_t trigger; @@ -53,13 +64,15 @@ static const struct gpio_exp_model_s { .write = pca85xx_write, } }; -static uint8_t n_expanders; +static EXT_RAM_ATTR uint8_t n_expanders; +static EXT_RAM_ATTR QueueHandle_t async_queue; static EXT_RAM_ATTR struct gpio_exp_s { uint32_t first, last; union gpio_exp_phy_u phy; uint32_t shadow; TickType_t age; + SemaphoreHandle_t mutex; uint32_t r_mask, w_mask; struct { gpio_exp_isr handler; @@ -108,9 +121,20 @@ struct gpio_exp_s* gpio_exp_create(const gpio_exp_config_t *config) { n_expanders++; 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->phy); + // 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 + 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))); + + xTaskCreateStatic(async_handler, "gpio_expander", sizeof(xStack), NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer); + async_queue = xQueueCreate(4, sizeof(async_request_t)); + } + // set interrupt if possible if (config->intr > 0) { gpio_pad_select_gpio(config->intr); @@ -143,15 +167,19 @@ struct gpio_exp_s* gpio_exp_create(const gpio_exp_config_t *config) { * Add ISR handler */ bool gpio_exp_add_isr(gpio_exp_isr isr, void *arg, struct gpio_exp_s *expander) { + xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY)); + 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; } } + xSemaphoreGive(expander->mutex); ESP_LOGE(TAG, "No room left to add new ISR"); return false; } @@ -162,6 +190,9 @@ bool gpio_exp_add_isr(gpio_exp_isr isr, void *arg, struct gpio_exp_s *expander) 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; +int64_t v = esp_timer_get_time(); + xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY)); + if (mode == GPIO_MODE_INPUT) { expander->r_mask |= 1 << gpio; expander->age = ~xTaskGetTickCount(); @@ -170,13 +201,16 @@ struct gpio_exp_s* gpio_exp_set_direction(int gpio, gpio_mode_t mode, struct gpi } if (expander->r_mask & expander->w_mask) { + xSemaphoreGive(expander->mutex); 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); - + + xSemaphoreGive(expander->mutex); +ESP_LOGW(TAG, "set took %lld µs", esp_timer_get_time() - v); return expander; } @@ -187,10 +221,16 @@ int gpio_exp_get_level(int gpio, uint32_t age, struct gpio_exp_s *expander) { 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->phy); expander->age = now; + + xSemaphoreGive(expander->mutex); } + ESP_LOGD(TAG, "Get level for GPIO %u => read %x", expander->first + gpio, expander->shadow); return (expander->shadow >> gpio) & 0x01; @@ -199,34 +239,47 @@ int gpio_exp_get_level(int gpio, uint32_t age, struct gpio_exp_s *expander) { /****************************************************************************** * Set GPIO level with cache */ -void gpio_exp_set_level(int gpio, int level, struct gpio_exp_s *expander) { - if ((expander = find_expander(expander, &gpio)) == NULL) return; +esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, struct gpio_exp_s *expander) { + if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG; uint32_t mask = 1 << gpio; + // 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; + return ESP_ERR_INVALID_ARG; } - level = level ? mask : 0; - mask &= expander->shadow; + if (direct) { + xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY)); - // only write if shadow not up to date - if ((mask ^ level) && expander->model->write) { - expander->shadow = (expander->shadow & ~(mask | level)) | level; - expander->model->write(&expander->phy, expander->r_mask, expander->shadow); - } + level = level ? mask : 0; + mask &= expander->shadow; - ESP_LOGD(TAG, "Set level %x for GPIO %u => wrote %x", level, expander->first + gpio, expander->shadow); + // only write if shadow not up to date + if ((mask ^ level) && expander->model->write) { + expander->shadow = (expander->shadow & ~(mask | level)) | level; + expander->model->write(&expander->phy, expander->r_mask, expander->shadow); + } + + 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; + } + + return ESP_OK; } /****************************************************************************** * Set GPIO pullmode */ -void gpio_exp_set_pull_mode(int gpio, gpio_pull_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) { if ((expander = find_expander(expander, &gpio)) != NULL && expander->model->set_pull_mode) { expander->model->set_pull_mode(gpio, mode); + return ESP_OK; } + return ESP_ERR_INVALID_ARG; } /****************************************************************************** @@ -237,7 +290,9 @@ void gpio_exp_enumerate(gpio_exp_enumerator enumerator, struct gpio_exp_s *expan 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 @@ -248,6 +303,29 @@ void gpio_exp_enumerate(gpio_exp_enumerator enumerator, struct gpio_exp_s *expan } } +/****************************************************************************** + * Wrapper function + */ +esp_err_t gpio_set_pull_mode_u(int gpio, gpio_pull_mode_t mode) { + if (gpio < GPIO_EXP_BASE_MIN) 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) { + if (gpio < GPIO_EXP_BASE_MIN) return gpio_set_direction(gpio, mode); + return gpio_exp_set_direction(gpio, mode, NULL) ? ESP_OK : ESP_ERR_INVALID_ARG; +} + +int gpio_get_level_u(int gpio) { + if (gpio < GPIO_EXP_BASE_MIN) return gpio_get_level(gpio); + return gpio_exp_get_level(gpio, 50, NULL); +} + +esp_err_t gpio_set_level_u(int gpio, int level) { + if (gpio < GPIO_EXP_BASE_MIN) return gpio_set_level(gpio, level); + return gpio_exp_set_level(gpio, level, false, NULL); +} + /**************************************************************************************** * Find the expander related to base */ @@ -282,7 +360,7 @@ static void pca9535_write(union gpio_exp_phy_u *phy, uint32_t r_mask, uint32_t s */ static void pca85xx_set_direction(union gpio_exp_phy_u *phy, uint32_t r_mask, uint32_t w_mask) { // all inputs must be set to 1 (open drain) and output are left open as well - i2c_write_word(phy->port, phy->addr, 0x255, r_mask | w_mask); + i2c_write_word(phy->port, phy->addr, 0xff, r_mask | w_mask); } static int pca85xx_read(union gpio_exp_phy_u *phy) { @@ -311,6 +389,29 @@ static void IRAM_ATTR intr_isr_handler(void* arg) ESP_EARLY_LOGD(TAG, "INTR for expander %u", expander->first); } +/**************************************************************************************** + * Async task + */ +void async_handler(void *arg) { + while (1) { + esp_err_t err; + async_request_t request; + + if (!xQueueReceive(async_queue, &request, portMAX_DELAY)) continue; + + switch (request.type) { + case ASYNC_WRITE: + 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; + } + } +} + + + /**************************************************************************************** * */ diff --git a/components/services/gpio_exp.h b/components/services/gpio_exp.h index 9cef11fb..1b3cea5c 100644 --- a/components/services/gpio_exp.h +++ b/components/services/gpio_exp.h @@ -43,10 +43,16 @@ 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); -void gpio_exp_set_pull_mode(int gpio, gpio_pull_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); -void gpio_exp_set_level(int gpio, int level, struct gpio_exp_s *expander); +esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, 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); diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index 22bc7c9c..bdc54b09 100644 --- a/components/squeezelite/output_i2s.c +++ b/components/squeezelite/output_i2s.c @@ -370,10 +370,11 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch // memory still used but at least task is not created if (stats) { - static DRAM_ATTR StaticTask_t xTaskBuffer __attribute__ ((aligned (4))); + // we allocate TCB but stack is staic 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[STAT_STACK_SIZE] __attribute__ ((aligned (4))); stats_task = xTaskCreateStatic( (TaskFunction_t) output_thread_i2s_stats, "output_i2s_sts", STAT_STACK_SIZE, - NULL, ESP_TASK_PRIO_MIN, xStack, &xTaskBuffer); + NULL, ESP_TASK_PRIO_MIN, xStack, xTaskBuffer); } }