Browse Source

Rotary encoder + simplify

- GPIO expander works with rotary encoder
- Much better mimic real GPIO, including ISR, to minimize impact on clients
Philippe G 3 years ago
parent
commit
d2494b73db

+ 50 - 99
components/services/buttons.c

@@ -28,7 +28,7 @@
 
 
 static const char * TAG = "buttons";
 static const char * TAG = "buttons";
 
 
-static int n_buttons = 0;
+static EXT_RAM_ATTR int n_buttons;
 
 
 #define BUTTON_STACK_SIZE	4096
 #define BUTTON_STACK_SIZE	4096
 #define MAX_BUTTONS			32
 #define MAX_BUTTONS			32
@@ -69,9 +69,8 @@ static EXT_RAM_ATTR struct {
 	infrared_handler handler;
 	infrared_handler handler;
 } infrared;
 } 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_task(void* arg);
 static void buttons_handler(struct button_s *button, int level);
 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;
 	struct button_s *button = (struct button_s*) arg;
 	BaseType_t woken = pdFALSE;
 	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();
 	if (woken) portYIELD_FROM_ISR();
+
 	ESP_EARLY_LOGD(TAG, "INT gpio %u level %u", button->gpio, button->level);
 	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 ) {
 static void buttons_timer_handler( TimerHandle_t xTimer ) {
 	struct button_s *button = (struct button_s*) pvTimerGetTimerID (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 is a copy, so need to go to real context
 				button.self->shifting = false;
 				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) {
 		} else if (xActivatedMember == rotary.queue) {
 			rotary_encoder_event_t event = { 0 };
 			rotary_encoder_event_t event = { 0 };
 			
 			
 			// received a rotary event
 			// received a rotary event
 		    xQueueReceive(rotary.queue, &event, 0);
 		    xQueueReceive(rotary.queue, &event, 0);
-			
+
 			ESP_LOGD(TAG, "Event: position %d, direction %s", event.state.position,
 			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.handler(rotary.client, event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? 
-										  ROTARY_RIGHT : ROTARY_LEFT, false);   
+											ROTARY_RIGHT : ROTARY_LEFT, false);   
 		} else {
 		} else {
 			// this is IR
 			// this is IR
 			infrared_receive(infrared.rb, infrared.handler);
 			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 
  * Create buttons 
  */
  */
 void button_create(void *client, int gpio, int type, bool pull, int debounce, button_handler handler, int long_press, int shifter_gpio) { 
 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;
 	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);
 	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);
-
-		// 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);
-			}
+	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) || 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);
+	// 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(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);
+	// 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);
 		}	
 		}	
-	} else {
-		ESP_LOGE(TAG, "Can't create button, GPIO %d does not exist", gpio);
+	
+		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++;
 	n_buttons++;
 }	
 }	

+ 155 - 126
components/services/gpio_exp.c

@@ -10,6 +10,7 @@
 #include <stdlib.h>
 #include <stdlib.h>
 #include "freertos/FreeRTOS.h"
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "freertos/task.h"
+#include "freertos/timers.h"
 #include "freertos/queue.h"
 #include "freertos/queue.h"
 #include "esp_task.h"
 #include "esp_task.h"
 #include "esp_log.h"
 #include "esp_log.h"
@@ -17,18 +18,27 @@
 #include "driver/i2c.h"
 #include "driver/i2c.h"
 #include "gpio_exp.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 {
 typedef struct gpio_exp_s {
 	uint32_t first, last;
 	uint32_t first, last;
 	union gpio_exp_phy_u phy;
 	union gpio_exp_phy_u phy;
-	uint32_t shadow;
+	uint32_t shadow, pending;
 	TickType_t age;
 	TickType_t age;
 	SemaphoreHandle_t mutex;
 	SemaphoreHandle_t mutex;
 	uint32_t r_mask, w_mask;
 	uint32_t r_mask, w_mask;
 	uint32_t pullup, pulldown;
 	uint32_t pullup, pulldown;
-	struct {
-		gpio_exp_isr handler;
+	struct gpio_exp_isr_s {
+		gpio_isr_t handler;
 		void *arg;
 		void *arg;
-	} isr[4];
+		TimerHandle_t timer;
+	} isr[32];
 	struct gpio_exp_model_s const *model;
 	struct gpio_exp_model_s const *model;
 } gpio_exp_t;
 } gpio_exp_t;
 
 
@@ -37,7 +47,7 @@ typedef struct {
 	int gpio;
 	int gpio;
 	int level;
 	int level;
 	gpio_exp_t *expander;
 	gpio_exp_t *expander;
-} async_request_t;
+} queue_request_t;
 
 
 static const char TAG[] = "gpio expander";
 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 int    mcp23017_read(gpio_exp_t* self);
 static void   mcp23017_write(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 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 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 {
 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 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 gpio_exp_t expanders[4];
+static EXT_RAM_ATTR TaskHandle_t service_task;
 
 
 /******************************************************************************
 /******************************************************************************
  * Retrieve base from an expander reference
  * 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->first = config->base;
 	expander->last = config->base + config->count - 1;
 	expander->last = config->base + config->count - 1;
 	expander->mutex = xSemaphoreCreateMutex();
 	expander->mutex = xSemaphoreCreateMutex();
+
 	memcpy(&expander->phy, &config->phy, sizeof(union gpio_exp_phy_u));
 	memcpy(&expander->phy, &config->phy, sizeof(union gpio_exp_phy_u));
 	if (expander->model->init) expander->model->init(expander);
 	if (expander->model->init) expander->model->init(expander);
 
 
 	// create a task to handle asynchronous requests (only write at this time)
 	// 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);
 		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
 	// 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
  * Set GPIO direction
  */
  */
 esp_err_t gpio_exp_set_direction(int gpio, gpio_mode_t mode, gpio_exp_t *expander) {
 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;
 	if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
 
 
 	xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
 	xSemaphoreTake(expander->mutex, pdMS_TO_TICKS(portMAX_DELAY));
 
 
 	if (mode == GPIO_MODE_INPUT) {
 	if (mode == GPIO_MODE_INPUT) {
 		expander->r_mask |= 1 << gpio;
 		expander->r_mask |= 1 << gpio;
+		expander->shadow = expander->model->read(expander);
 		expander->age = ~xTaskGetTickCount();
 		expander->age = ~xTaskGetTickCount();
 	} else {
 	} else {
 		expander->w_mask |= 1 << gpio;
 		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);
 	if (expander->model->set_direction) expander->model->set_direction(expander);
 
 
 	xSemaphoreGive(expander->mutex);
 	xSemaphoreGive(expander->mutex);
+
 	return ESP_OK;
 	return ESP_OK;
 }	
 }	
 
 
 /******************************************************************************
 /******************************************************************************
  * Get GPIO level with cache
  * 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;
 	if ((expander = find_expander(expander, &gpio)) == NULL) return -1;
 	uint32_t now = xTaskGetTickCount();
 	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;
+	// 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;
+	}
 
 
-		xSemaphoreGive(expander->mutex);
+	// 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);
 	ESP_LOGD(TAG, "Get level for GPIO %u => read %x", expander->first + gpio, expander->shadow);
 	return (expander->shadow >> gpio) & 0x01;
 	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
  * Set GPIO level with cache
  */
  */
 esp_err_t gpio_exp_set_level(int gpio, int level, bool direct, gpio_exp_t *expander) {
 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;
 	if ((expander = find_expander(expander, &gpio)) == NULL) return ESP_ERR_INVALID_ARG;
 	uint32_t mask = 1 << gpio;
 	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) {
 	if ((expander->w_mask & mask) == 0) {
 		ESP_LOGW(TAG, "GPIO %d is not set for output", expander->first + gpio);
 		ESP_LOGW(TAG, "GPIO %d is not set for output", expander->first + gpio);
 		return ESP_ERR_INVALID_ARG;
 		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);
 		xSemaphoreGive(expander->mutex);
 		ESP_LOGD(TAG, "Set level %x for GPIO %u => wrote %x", level, expander->first + gpio, expander->shadow);
 		ESP_LOGD(TAG, "Set level %x for GPIO %u => wrote %x", level, expander->first + gpio, expander->shadow);
 	} else {
 	} 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;
 	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
  * Set GPIO pullmode
  */
  */
 esp_err_t gpio_exp_set_pull_mode(int gpio, gpio_pull_mode_t mode, gpio_exp_t *expander) {
 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) {
 	if ((expander = find_expander(expander, &gpio)) != NULL && expander->model->set_pull_mode) {
 
 
 		expander->pullup &= ~(1 << gpio);
 		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;
 	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
  * 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);
 	if (gpio < GPIO_NUM_MAX) return gpio_set_pull_mode(gpio, mode);
 	return gpio_exp_set_pull_mode(gpio, mode, NULL);
 	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);
 	if (gpio < GPIO_NUM_MAX) return gpio_set_direction(gpio, mode);
 	return gpio_exp_set_direction(gpio, mode, NULL);
 	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);
 	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);
 	if (gpio < GPIO_NUM_MAX) return gpio_set_level(gpio, level);
 	return gpio_exp_set_level(gpio, level, false, NULL);
 	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
  * Find the expander related to base
  */
  */
 static gpio_exp_t* find_expander(gpio_exp_t *expander, int *gpio) {
 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++) {
 	for (int i = 0; !expander && i < n_expanders; i++) {
 		if (*gpio >= expanders[i].first && *gpio <= expanders[i].last) expander = 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) {
 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) {
 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) {
 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) {
 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) {
 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?
 	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);
 	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) {
 static int mcp23017_read(gpio_exp_t* self) {
 	// read the pin value, not the stored one @interrupt
 	// 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) {
 static void mcp23017_write(gpio_exp_t* self) {
@@ -431,43 +457,68 @@ static void mcp23017_write(gpio_exp_t* self) {
 /****************************************************************************************
 /****************************************************************************************
  * INTR low-level handler
  * 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;
 	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();
 	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) {
-	while (1) {
-		esp_err_t err;
-		async_request_t request;
+static void debounce_handler( TimerHandle_t xTimer ) {
+	struct gpio_exp_isr_s *isr = (struct gpio_exp_isr_s*) pvTimerGetTimerID (xTimer);
+	isr->handler(isr->arg);
+}
 
 
-		if (!xQueueReceive(async_queue, &request, portMAX_DELAY)) continue;
+/****************************************************************************************
+ * Service task
+ */
+void service_handler(void *arg) {
+	while (1) {
+		queue_request_t request;
+		uint32_t notif = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+
+		// 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));
+
+				// 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);
+				}
+			}
+		}
 
 
-		switch (request.type) {
-		case ASYNC_WRITE:
-			err = gpio_exp_set_level(request.gpio, request.level, true, request.expander);
+		// 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);  
 			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_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_start(cmd);
-	
     i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, I2C_MASTER_NACK);
     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
 	// 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_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);
     i2c_master_stop(cmd);
     esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
     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");
 		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);
 	esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, 100 / portTICK_RATE_MS);
     i2c_cmd_link_delete(cmd);
     i2c_cmd_link_delete(cmd);
 	
 	
-	if (ret != ESP_OK) {
+	if (ret != ESP_OK) {		
 		ESP_LOGW(TAG, "I2C write failed");
 		ESP_LOGW(TAG, "I2C write failed");
 	}
 	}
 	
 	

+ 23 - 21
components/services/gpio_exp.h

@@ -29,28 +29,30 @@ typedef struct {
 	} phy;	
 	} phy;	
 } gpio_exp_config_t;
 } 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
 // 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_get_base(struct gpio_exp_s *expander);
-struct gpio_exp_s* gpio_exp_get_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_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 <expander> is provided, GPIO's can be numbered from 0. If <expander>
+ 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_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);
 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_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 <enumerator>
-   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);
+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);
+
+// 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)

+ 3 - 3
components/services/led.c

@@ -55,7 +55,7 @@ static int led_max = 2;
  * 
  * 
  */
  */
 static void set_level(struct led_s *led, bool on) {
 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 {
 	else {
 		ledc_set_duty(LEDC_HIGH_SPEED_MODE, led->channel, on ? led->pwm : (led->onstate ? 0 : pwm_system.max));
 		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);
 		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;
 	leds[idx].pwm = -1;
 
 
 	if (pwm < 0 || gpio >= GPIO_NUM_MAX) {	
 	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 {	
 	} else {	
 		leds[idx].channel = pwm_system.base_channel++;
 		leds[idx].channel = pwm_system.base_channel++;
 		leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
 		leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);

+ 26 - 19
components/services/rotary_encoder.c

@@ -91,6 +91,7 @@
 
 
 #include "esp_log.h"
 #include "esp_log.h"
 #include "driver/gpio.h"
 #include "driver/gpio.h"
+#include "gpio_exp.h"
 
 
 #define TAG "rotary_encoder"
 #define TAG "rotary_encoder"
 
 
@@ -148,7 +149,7 @@ static uint8_t _process(rotary_encoder_info_t * info)
     if (info != NULL)
     if (info != NULL)
     {
     {
         // Get state of input pins.
         // 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.
         // Determine new state from the pins and state table.
 #ifdef ROTARY_ENCODER_DEBUG
 #ifdef ROTARY_ENCODER_DEBUG
@@ -198,12 +199,18 @@ static void _isr_rotenc(void * args)
                 .direction = info->state.direction,
                 .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;
         info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
 
 
         // configure GPIOs
         // 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
         // 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
     else
     {
     {
@@ -280,8 +287,8 @@ esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info)
     esp_err_t err = ESP_OK;
     esp_err_t err = ESP_OK;
     if (info)
     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
     else
     {
     {

+ 6 - 6
components/services/services.c

@@ -43,13 +43,13 @@ void set_power_gpio(int gpio, char *value) {
 	bool parsed = true;
 	bool parsed = true;
 	
 	
 	if (!strcasecmp(value, "vcc") ) {
 	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")) {
 	} 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;
 	} else parsed = false;
 	
 	
 	if (parsed) ESP_LOGI(TAG, "set GPIO %u to %s", gpio, value);
 	if (parsed) ESP_LOGI(TAG, "set GPIO %u to %s", gpio, value);

+ 22 - 9
components/squeezelite/output_i2s.c

@@ -132,10 +132,10 @@ static bool handler(u8_t *data, int len){
 			
 			
 			if (jack_mutes_amp && jack_inserted_svc()) {
 			if (jack_mutes_amp && jack_inserted_svc()) {
 				adac->speaker(false);
 				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 {
 			} else {
 				adac->speaker(true);
 				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) {
 	if (jack_mutes_amp) {
 		LOG_INFO("switching amplifier %s", inserted ? "OFF" : "ON");
 		LOG_INFO("switching amplifier %s", inserted ? "OFF" : "ON");
 		adac->speaker(!inserted);
 		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
 	// activate headset
@@ -178,9 +178,9 @@ static void set_amp_gpio(int gpio, char *value) {
 		amp_control.gpio = gpio;
 		amp_control.gpio = gpio;
 		if ((p = strchr(value, ':')) != NULL) amp_control.active = atoi(p + 1);
 		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);
 		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);
 			LOG_INFO("Output state is %d", output.state);
 			if (output.state == OUTPUT_OFF) {
 			if (output.state == OUTPUT_OFF) {
 				led_blink(LED_GREEN, 100, 2500);
 				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);
 				LOG_INFO("switching off amp GPIO %d", amp_control.gpio);
 			} else if (output.state == OUTPUT_STOPPED) {
 			} else if (output.state == OUTPUT_STOPPED) {
 				adac->speaker(false);
 				adac->speaker(false);
 				led_blink(LED_GREEN, 200, 1000);
 				led_blink(LED_GREEN, 200, 1000);
 			} else if (output.state == OUTPUT_RUNNING) {
 			} else if (output.state == OUTPUT_RUNNING) {
 				if (!jack_mutes_amp || !jack_inserted_svc()) {
 				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);
 					adac->speaker(true);
 				}	
 				}	
 				led_on(LED_GREEN);
 				led_on(LED_GREEN);
@@ -493,7 +493,20 @@ static void output_thread_i2s(void *arg) {
 		_output_frames( iframes );
 		_output_frames( iframes );
 		// oframes must be a global updated by the write callback
 		// oframes must be a global updated by the write callback
 		output.frames_in_process = oframes;
 		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(oframes,rec,iframes);
 		SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size);
 		SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size);
 		SET_MIN_MAX_SIZED(_buf_used(streambuf),s,streambuf->size);
 		SET_MIN_MAX_SIZED(_buf_used(streambuf),s,streambuf->size);